diff --git a/.gitignore b/.gitignore index 32858aad..d33629a3 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,6 @@ .mtj.tmp/ # Package Files # -*.jar *.war *.ear diff --git a/Figures/Automated Analysis.png b/Figures/Automated Analysis.png new file mode 100644 index 00000000..540c050a Binary files /dev/null and b/Figures/Automated Analysis.png differ diff --git a/Figures/Formal Specification 1.png b/Figures/Formal Specification 1.png new file mode 100644 index 00000000..ca336fd3 Binary files /dev/null and b/Figures/Formal Specification 1.png differ diff --git a/Figures/Formal Specification.png b/Figures/Formal Specification.png new file mode 100644 index 00000000..ec68df9f Binary files /dev/null and b/Figures/Formal Specification.png differ diff --git a/Figures/Reasoning Approches using solvers.png b/Figures/Reasoning Approches using solvers.png new file mode 100644 index 00000000..a96bc0cb Binary files /dev/null and b/Figures/Reasoning Approches using solvers.png differ diff --git a/Figures/Tarski Architecture.png b/Figures/Tarski Architecture.png new file mode 100644 index 00000000..3469e6e9 Binary files /dev/null and b/Figures/Tarski Architecture.png differ diff --git a/Figures/Tarski Features - Intorduction.png b/Figures/Tarski Features - Intorduction.png new file mode 100644 index 00000000..2d7a7315 Binary files /dev/null and b/Figures/Tarski Features - Intorduction.png differ diff --git a/Figures/Tarski_SummerSchool_Poster.pdf b/Figures/Tarski_SummerSchool_Poster.pdf new file mode 100644 index 00000000..80031620 Binary files /dev/null and b/Figures/Tarski_SummerSchool_Poster.pdf differ diff --git a/Figures/Traceability Management.png b/Figures/Traceability Management.png new file mode 100644 index 00000000..bd7ac0ce Binary files /dev/null and b/Figures/Traceability Management.png differ diff --git a/README.md b/README.md index 07fcd038..63417f3d 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,75 @@ -WP3 +Work Package 3 (Tarski Platform) === +1. Introduction +--- -Work Package 3 - Model to/from Knowledge Base (UNIT) + In this work package, we introduce a new approach with its supporting platform which enables the user to interactively configure traceable elements and trace links. The specification is supported by formalizing the semantics of traceability in first-order relational logic in order to perform automated analysis such as consistency checking, reasoning on trace relations and trace element discovery. The usefulness of the approach is demonstrated in the context of application life-cycle platform in software industry and being tested in aviation industry. + + * [`Screen cast that shows Tarski in action (latest version of the platform)`](https://youtu.be/J7qEVOG6bjg) + * [`Installation of Tarski Platform on ModelWriter (older version of the platform)`](https://youtu.be/NE7hESkaLCo) +For an example workspace, you can clone this source code repository [`Demonstrations`](https://github.com/ModelWriter/Demonstrations) and an example configuration file, you can directly get from [`HAVELSAN use case`](https://goo.gl/8Zqxi8). -Product Backlog of WP3 [https://waffle.io/modelwriter/wp3](https://waffle.io/modelwriter/wp3) +[![roject Poster presented in SAT/SMT/AR Summer School 2016](https://github.com/ModelWriter/WP3/raw/master/Screenshots/poster.png)](https://github.com/ModelWriter/WP3/blob/master/Figures/Tarski_SummerSchool_Poster.pdf) + +**Type Hierarchy after loading an Alloy specification to the system (included several annotations)** + +![Type Hierarchy](https://github.com/ModelWriter/WP3/raw/master/Screenshots/RelationNames.png) + +**Management of first-order relational model** + +![Management of first-order relational model](https://github.com/ModelWriter/WP3/raw/master/Screenshots/ModelManagement.png) + +**Assigning type to a unary relation while creating a trace element on a text file** + +![Assigning type to a unary relation while creating a trace element on a text file](https://github.com/ModelWriter/WP3/blob/master/Screenshots/AssigningTypes.png) + +**Selecting a binary relation to create link** + +![Selecting a binary relation to create link](https://github.com/ModelWriter/WP3/raw/master/Screenshots/SelectingBinaryRelation.png) + +**Selecting a range from existing trace elements for the binary relation** + +![Selecting a range from existing trace elements for the binary relation](https://github.com/ModelWriter/WP3/raw/master/Screenshots/SelectingRangeForAssigningTypes.png) + +**Automated Analysis** + +![Automated Analysis](https://github.com/ModelWriter/WP3/raw/master/Screenshots/automatedAnalysis.png) + +Work Package Objectives (from the Final Project Propsal) +--- + +The primary objective of this WP is to provide the `synchronization mechanism` of the ModelWriter platform that will keep the `user-visible models` consistent with the `KB-stored models` and vice versa. This work package addresses all problems related to the "model-to-model transformations" in ModelWriter. + +* By `user-visible models` is meant those models that have been explicitly created by a Technical Author, using e.g. a spreadsheet, a kind of UML diagram, a block diagram, a mind map, etc. or any modelling tool (part of the “Model” side of ModelWriter) he has found the most appropriate for authoring his technical information. +* By `KB-stored model` is meant a part of the Knowledge Base devoted to storing pieces of related information, disregarding whether it is represented in user-visible models, in natural-language documents, or in both. + +This mechanism will be based on `model-to-model (M2M) transformations` of two complementary categories: + +* WP3.1, for transforming a `user-visible model` to a KB-stored model (this "mirrors" WP2.1). +* WP3.2, for transforming a `KB-stored model` into a user-visible model (this "mirrors" WP2.2). + + + +Expected Results +--- + +An Eclipse-based M2M Transformation Framework, extensible so as to accommodate an increasing number of types of (user-visible) models. This will consist of the following main envisioned plug-in components: + +* `Transformation Manager`: provides the infrastructure to register and launch transformations. +* `Configuration Manager`: for personalizing the behaviour of the framework to meet the needs of a specific standard / organization / project / individual. +* `Traceability Manage`: keeps links between elements of user-visible models and elements of the KB. +* `Synchronization Manager`: triggering transformations when synchronization is needed. + +Approach for the Work Package +--- + +The main goal of this WP is to develop a M2M Transformation Framework that supports the synchronization mechanisms for the ModelWriter tool. + +These mechanisms will be based on a requirements synchronization framework that can be extended to support different requirements models (based on both textual and/or visual notations). The framework is made up of three main components: + + 1. A meta-modelling infrastructure, + 2. A DSL for model transformation specifications, and + 3. A model synchronization API. + +*final version* diff --git a/Screenshots/AssigningTypes.png b/Screenshots/AssigningTypes.png new file mode 100644 index 00000000..29748d7d Binary files /dev/null and b/Screenshots/AssigningTypes.png differ diff --git a/Screenshots/ModelManagement.png b/Screenshots/ModelManagement.png new file mode 100644 index 00000000..ed8309ab Binary files /dev/null and b/Screenshots/ModelManagement.png differ diff --git a/Screenshots/RelationNames.png b/Screenshots/RelationNames.png new file mode 100644 index 00000000..fc9a3c30 Binary files /dev/null and b/Screenshots/RelationNames.png differ diff --git a/Screenshots/Screenshot-mixed.png b/Screenshots/Screenshot-mixed.png new file mode 100644 index 00000000..88bb8897 Binary files /dev/null and b/Screenshots/Screenshot-mixed.png differ diff --git a/Screenshots/SelectingBinaryRelation.png b/Screenshots/SelectingBinaryRelation.png new file mode 100644 index 00000000..83fd8199 Binary files /dev/null and b/Screenshots/SelectingBinaryRelation.png differ diff --git a/Screenshots/SelectingRangeForAssigningTypes.png b/Screenshots/SelectingRangeForAssigningTypes.png new file mode 100644 index 00000000..1c6cecd7 Binary files /dev/null and b/Screenshots/SelectingRangeForAssigningTypes.png differ diff --git a/Screenshots/automatedAnalysis.png b/Screenshots/automatedAnalysis.png new file mode 100644 index 00000000..f9a9f448 Binary files /dev/null and b/Screenshots/automatedAnalysis.png differ diff --git a/Screenshots/poster.png b/Screenshots/poster.png new file mode 100644 index 00000000..8dc79724 Binary files /dev/null and b/Screenshots/poster.png differ diff --git a/Source/eu.modelwriter.alloyanalyzer/.classpath b/Source/eu.modelwriter.alloyanalyzer/.classpath new file mode 100644 index 00000000..1e0f342a --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/.classpath @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/Source/eu.modelwriter.alloyanalyzer/.project b/Source/eu.modelwriter.alloyanalyzer/.project new file mode 100644 index 00000000..0cb33778 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/.project @@ -0,0 +1,28 @@ + + + eu.modelwriter.alloyanalyzer + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.pde.PluginNature + + diff --git a/Source/eu.modelwriter.alloyanalyzer/.settings/org.eclipse.jdt.core.prefs b/Source/eu.modelwriter.alloyanalyzer/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 00000000..3a215370 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,11 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=1.8 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.8 diff --git a/Source/eu.modelwriter.alloyanalyzer/META-INF/MANIFEST.MF b/Source/eu.modelwriter.alloyanalyzer/META-INF/MANIFEST.MF new file mode 100644 index 00000000..dd8e122b --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/META-INF/MANIFEST.MF @@ -0,0 +1,73 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: Alloyanalyzer +Bundle-SymbolicName: eu.modelwriter.alloyanalyzer +Bundle-Version: 1.0.0.qualifier +Bundle-ClassPath: alloyanalyzer.jar, + lib/alloy4.2.jar +Export-Package: LICENSES, + apple.awt, + com.apple.eawt, + com.apple.eio, + com.sun.imageio.plugins.tiff, + edu.mit.csail.sdg.alloy4, + edu.mit.csail.sdg.alloy4compiler.ast, + edu.mit.csail.sdg.alloy4compiler.parser, + edu.mit.csail.sdg.alloy4compiler.sim, + edu.mit.csail.sdg.alloy4compiler.translator, + edu.mit.csail.sdg.alloy4graph, + edu.mit.csail.sdg.alloy4viz, + edu.mit.csail.sdg.alloy4whole, + help, + help.image, + icons.ColorIcons, + icons.ShapeIcons, + icons.StyleIcons, + images, + java_cup.runtime, + kodkod.ast, + kodkod.ast.operator, + kodkod.ast.visitor, + kodkod.engine, + kodkod.engine.bool, + kodkod.engine.config, + kodkod.engine.fol2sat, + kodkod.engine.satlab, + kodkod.engine.ucore, + kodkod.instance, + kodkod.util.collections, + kodkod.util.ints, + kodkod.util.nodes, + models.book.appendixA, + models.book.appendixE, + models.book.chapter2, + models.book.chapter4, + models.book.chapter5, + models.book.chapter6, + models.book.chapter6.memory, + models.examples.algorithms, + models.examples.case_studies, + models.examples.puzzles, + models.examples.systems, + models.examples.toys, + models.examples.tutorial, + models.util, + org.sat4j, + org.sat4j.core, + org.sat4j.minisat, + org.sat4j.minisat.constraints, + org.sat4j.minisat.constraints.card, + org.sat4j.minisat.constraints.cnf, + org.sat4j.minisat.core, + org.sat4j.minisat.learning, + org.sat4j.minisat.orders, + org.sat4j.minisat.restarts, + org.sat4j.opt, + org.sat4j.reader, + org.sat4j.specs, + org.sat4j.tools, + org.sat4j.tools.encoding, + org.sat4j.tools.xplain, + target, + tmp +Bundle-RequiredExecutionEnvironment: JavaSE-1.8 diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/.gitignore b/Source/eu.modelwriter.alloyanalyzer/bin/.gitignore new file mode 100644 index 00000000..5e7fcc4a --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/.gitignore @@ -0,0 +1,4 @@ +/edu/ +/java_cup/ +/kodkod/ +/tmp/ diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/META-INF/MANIFEST.MF b/Source/eu.modelwriter.alloyanalyzer/bin/META-INF/MANIFEST.MF new file mode 100644 index 00000000..c10f81ea --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/META-INF/MANIFEST.MF @@ -0,0 +1,4 @@ +Manifest-Version: 1.0 +Created-By: 1.5.0 (Sun Microsystems Inc.) +Main-Class: edu.mit.csail.sdg.alloy4whole.SimpleGUI + diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/README.TXT b/Source/eu.modelwriter.alloyanalyzer/bin/README.TXT new file mode 100644 index 00000000..7cd623ea --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/README.TXT @@ -0,0 +1,63 @@ +The Alloy Analyzer + + The Alloy Analyzer is a tool developed by the Software Design + Group (http://sdg.csail.mit.edu/) for analyzing models written in + Alloy, a simple structural modeling language based on first-order + logic. The tool can generate instances of invariants, simulate + the execution of operations (even those defined implicitly), and + check user-specified properties of a model. Alloy and its + analyzer have been used primarily to explore abstract software + designs. Its use in analyzing code for conformance to a + specification and as an automatic test case generator are being + investigated in ongoing research projects. + + See the web page for a description of what's new in Alloy: + + http://alloy.mit.edu/ + + +Detailed Instructions: + + 1. Java 5 or later + + Java runtimes are available at no economic charge from Sun and + IBM and others. One may have come pre-installed in your OS. + Alloy does not currently work with gcj because of its limited + library support. + + 2. Running Alloy on Mac OS X + + Just double-click on the dmg file, + then drag the Alloy application into your application directory. + + 3. Running Alloy on other platforms + + Just double-click on the jar file, or type: + + java -jar alloy4.jar + +The source code for the Alloy Analyzer is available +under the MIT license. + +The Alloy Analyzer utilizes several third-party packages whose code may +be distributed under a different license (see the various LICENSE files +in the distribution for details). We are extremely grateful to the authors +of these packages for making their source code freely available. + + * Kodkod + http://web.mit.edu/~emina/www/kodkod.html + + * CUP Parser Generator for Java + http://www2.cs.tum.edu/projects/cup/ + + * JFlex scanner generator for Java + http://jflex.de/ + + * The zChaff solver + http://www.princeton.edu/~chaff/zchaff.html + + * The MiniSat solver + http://www.cs.chalmers.se/Cs/Research/FormalMethods/MiniSat/ + + * The SAT4J solver + http://www.sat4j.org/ diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/README.md b/Source/eu.modelwriter.alloyanalyzer/bin/README.md new file mode 100644 index 00000000..9942d4bc --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/README.md @@ -0,0 +1,45 @@ +Alloy Analyzer (source code mirror) +=================================== + +Summary +------- +This is a copy of the source code for [MIT's Alloy Analyzer model checking tool](http://alloy.mit.edu/alloy/). +It also includes an Ant build.xml script, which is not part of the original MIT source code. +This copy was created to facilitate modification to the core Alloy tool (the parts which fall +under the `edu.mit` package structure). + +It was created as follows (not necessarily in this order): + +1. Downloaded the JAR file located at: http://alloy.mit.edu/alloy/downloads/alloy4.2.jar +2. Extracted the JAR file. +3. Added this `README.md` file and a `build.xml` file. +3. Deleted core `.class` files (using the _clean_ target in `build.xml`) + +Building +-------- +The Ant build.xml script contains the following targets: + +- _build_: Compiles the `.java` files under the `edu` directory. + + Other directories are not touched; it is assumed that these contain libraries + which have been pre-compiled. + + The auto-generated parser and lexer `.java` files (located in the `edu/mit/csail/sdg/alloy4compiler/parser` directory) + are neither deleted nor generated by the Ant script. The directory already contains shell scripts + to re-generate them using JFlex and CUP. +- _dist_: Creates an executable JAR file in the `dist` directory. This JAR file looks essentially like the official + Alloy JAR file released by MIT. +- _all_: Runs _dist_. +- _clean_: Deletes the `dist` directory and all class files under the `edu` directory. + +Notes +----- + +- As per the manifest, the main class is `edu.mit.csail.sdg.alloy4whole.SimpleGUI`. +- The version number and build date which the tool displays are not accurate. + These are set in the `edu.mit.csail.sdg.alloy4.Version` class, and are supposed to be + updated by the build script when building a release. + This project was not intended to create official releases, so it was left as-is. +- There is a class `edu.mit.csail.sdg.alloy4.MailBug` which includes logic to email + crash reports to MIT. You should change this class if you are modifying the source code + and creating your own release. diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/about.html b/Source/eu.modelwriter.alloyanalyzer/bin/about.html new file mode 100644 index 00000000..99988c9b --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/about.html @@ -0,0 +1,56 @@ + + + + +About + + +

About This Content

+ +

June, 2010

+

License

+ +

The Eclipse Foundation makes available all content in this plug-in ("Content"). Unless otherwise indicated below, the Content is provided to you under the terms and conditions of the Eclipse Public License Version 1.0 ("EPL"). A copy of the EPL is available at http://www.eclipse.org/legal/epl-v10.html. For purposes of the EPL, "Program" will mean the Content.

+ +

If you did not receive this Content directly from the Eclipse Foundation, the Content is being redistributed by another party ("Redistributor") and different terms and conditions may apply to your use of any object code in the Content. Check the Redistributor’s license that was provided with the Content. If no such license exists, contact the Redistributor. Unless otherwise indicated below, the terms and conditions of the EPL still apply to any source code in the Content and such source code may be obtained at http://www.eclipse.org.

+ +

Third Party Content

+

The Content includes items that have been sourced from third parties as set out below. If you did not receive this Content directly from the Eclipse Foundation, the following is provided for informational purposes only, and you should look to the Redistributor’s license for terms and conditions of use.

+

SAT4J 2.3.2 SUBSET (Core)

+

The SAT4J project makes available all content in this plug-in ("Content"). Your use of the Content is governed by the terms and conditions of the Eclipse Public License Version 1.0 ("EPL"). A copy of the EPL is available at http://www.eclipse.org/legal/epl-v10.html. For purposes of the EPL, "Program" will mean the Content.

+

Alternatively, the Content may be obtained from the SAT4J project website at http://www.sat4j.org/ for use under the terms of either the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), in which case the provisions of the LGPL are applicable instead of those above. If you wish to allow use of your version of the Content only under the terms of the LGPL, and not to allow others to use your version of this Content under the terms of the EPL, indicate your decision by deleting the provisions above and replace them with the notice and other provisions required by the LGPL. If you do not delete the provisions above, a recipient may use your version of this file under the terms of the EPL or the LGPL.

+

The Eclipse Foundation elects to include this software in this distribution under the EPL license. The source code for this plug-in can be obtained from the SAT4J project website at http://www.sat4j.org/

+ +

SAT4J includes content that was obtained under licenses that differ from the SAT4J licenses.
+ The content in the following classes
+ +

+

is based on code obtained from the Minisat 1.1.4 implementation, the source code for which can be found at www.minisat.se, under the following permissive license:
+
+ MiniSat -- Copyright (c) 2003-2005, Niklas Een, Niklas Sorensson
+
+ Permission is hereby granted, free of charge, to any person obtaining a
+ copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+ without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+ permit persons to whom the Software is furnished to do so, subject to
+ the following conditions:
+
+ The above copyright notice and this permission notice shall be included
+ in all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

+ + diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/amd64-linux/berkmin b/Source/eu.modelwriter.alloyanalyzer/bin/amd64-linux/berkmin new file mode 100644 index 00000000..f0aa2cee Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/bin/amd64-linux/berkmin differ diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/amd64-linux/libminisat.so b/Source/eu.modelwriter.alloyanalyzer/bin/amd64-linux/libminisat.so new file mode 100644 index 00000000..d286aa4b Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/bin/amd64-linux/libminisat.so differ diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/amd64-linux/libminisatprover.so b/Source/eu.modelwriter.alloyanalyzer/bin/amd64-linux/libminisatprover.so new file mode 100644 index 00000000..392474ac Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/bin/amd64-linux/libminisatprover.so differ diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/amd64-linux/libzchaff.so b/Source/eu.modelwriter.alloyanalyzer/bin/amd64-linux/libzchaff.so new file mode 100644 index 00000000..dc9c229d Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/bin/amd64-linux/libzchaff.so differ diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/edu/mit/csail/sdg/alloy4/package.html b/Source/eu.modelwriter.alloyanalyzer/bin/edu/mit/csail/sdg/alloy4/package.html new file mode 100644 index 00000000..2087435e --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/edu/mit/csail/sdg/alloy4/package.html @@ -0,0 +1,5 @@ + + +This package contains general data structures and helper classes. + + diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/edu/mit/csail/sdg/alloy4compiler/ast/package.html b/Source/eu.modelwriter.alloyanalyzer/bin/edu/mit/csail/sdg/alloy4compiler/ast/package.html new file mode 100644 index 00000000..b5f3ab20 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/edu/mit/csail/sdg/alloy4compiler/ast/package.html @@ -0,0 +1,5 @@ + + +This package contains the definition of AST nodes. + + diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/edu/mit/csail/sdg/alloy4compiler/parser/Alloy.cup b/Source/eu.modelwriter.alloyanalyzer/bin/edu/mit/csail/sdg/alloy4compiler/parser/Alloy.cup new file mode 100644 index 00000000..1796efb0 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/edu/mit/csail/sdg/alloy4compiler/parser/Alloy.cup @@ -0,0 +1,1038 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2008, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* + * Warning: this file alone is not enough to correctly parse Alloy4 since the actual + * language is not LALR(1); instead, we have to pre-process the token stream + * using CompFilter.java to rearrange/transform the token stream, and then we can + * parse the transformed token stream using this LALR(1) grammar. For more information, + * please refer to CompFilter.java + */ + +import java.util.Stack; +import java.util.List; +import java.util.ArrayList; +import java.util.TreeSet; +import java.util.Map; +import java.util.LinkedHashMap; +import java.io.BufferedReader; +import java.io.FileNotFoundException; +import java.io.Reader; +import java.io.IOException; +import java.io.StringReader; +import java_cup.runtime.*; +import edu.mit.csail.sdg.alloy4.Err; +import edu.mit.csail.sdg.alloy4.ErrorFatal; +import edu.mit.csail.sdg.alloy4.ErrorSyntax; +import edu.mit.csail.sdg.alloy4.Pos; +import edu.mit.csail.sdg.alloy4.Pair; +import edu.mit.csail.sdg.alloy4.Util; +import edu.mit.csail.sdg.alloy4.Version; +import edu.mit.csail.sdg.alloy4compiler.ast.Attr.AttrType; +import edu.mit.csail.sdg.alloy4compiler.ast.CommandScope; +import edu.mit.csail.sdg.alloy4compiler.ast.Decl; +import edu.mit.csail.sdg.alloy4compiler.ast.Expr; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprBadJoin; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprITE; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprLet; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprBinary; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprList; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprConstant; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprQt; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprUnary; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprVar; +import edu.mit.csail.sdg.alloy4compiler.ast.Sig; +import edu.mit.csail.sdg.alloy4compiler.ast.Sig.PrimSig; + +//===========================================================================// + +parser code {: + + public CompModule alloymodule=null; + + @Override public Symbol parse() throws java.lang.Exception { + int act; // current action code + Symbol lhs_sym = null; // the Symbol/stack element returned by a reduce + short handle_size, lhs_sym_num; // information about production being reduced with + boolean logging = "yes".equals(System.getProperty("debug")); + production_tab = production_table(); + action_tab = action_table(); + reduce_tab = reduce_table(); + init_actions(); + user_init(); + // start + cur_token = scan(); + stack.removeAllElements(); + stack.push(getSymbolFactory().startSymbol("START", 0, start_state())); + tos = 0; + for (_done_parsing = false; !_done_parsing; ) { + act = get_action(((Symbol)stack.peek()).parse_state, cur_token.sym); + if (act > 0) { // "shift"; thus, we shift to the encoded state by pushing it on the stack + if (logging) System.out.println("shift " + cur_token.sym); + cur_token.parse_state = act-1; + stack.push(cur_token); + tos++; + cur_token = scan(); + } else if (act<0) { // "reduce" + if (logging) System.out.println("reduce " + ((-act)-1)); + lhs_sym = do_action((-act)-1, this, stack, tos); + lhs_sym_num = production_tab[(-act)-1][0]; + handle_size = production_tab[(-act)-1][1]; + for (int i = 0; i < handle_size; i++) { stack.pop(); tos--; } + act = get_reduce(((Symbol)stack.peek()).parse_state, lhs_sym_num); + lhs_sym.parse_state = act; + stack.push(lhs_sym); + tos++; + } else { // "error" + if (logging) System.out.println("error"); + syntax_error(cur_token); + done_parsing(); + } + } + return lhs_sym; + } + + public void syntax_error(Symbol x) throws Err { + Map ch = new LinkedHashMap(); + ch.put(CompSym.ARROW, "->"); + ch.put(CompSym.ANY_ARROW_SOME, "->"); + ch.put(CompSym.ANY_ARROW_ONE, "->"); + ch.put(CompSym.ANY_ARROW_LONE, "->"); + ch.put(CompSym.SOME_ARROW_ANY, "some"); + ch.put(CompSym.SOME_ARROW_SOME, "some"); + ch.put(CompSym.SOME_ARROW_ONE, "some"); + ch.put(CompSym.SOME_ARROW_LONE, "some"); + ch.put(CompSym.ONE_ARROW_ANY, "one"); + ch.put(CompSym.ONE_ARROW_SOME, "one"); + ch.put(CompSym.ONE_ARROW_ONE, "one"); + ch.put(CompSym.ONE_ARROW_LONE, "one"); + ch.put(CompSym.LONE_ARROW_ANY, "lone"); + ch.put(CompSym.LONE_ARROW_SOME, "lone"); + ch.put(CompSym.LONE_ARROW_ONE, "lone"); + ch.put(CompSym.LONE_ARROW_LONE, "lone"); + ch.put(CompSym.INTADD, "fun"); + ch.put(CompSym.INTSUB, "fun"); + ch.put(CompSym.INTMUL, "fun"); + ch.put(CompSym.INTDIV, "fun"); + ch.put(CompSym.INTREM, "fun"); + ch.put(CompSym.INTMIN, "fun"); + ch.put(CompSym.INTMAX, "fun"); + ch.put(CompSym.INTNEXT, "fun"); + ch.put(CompSym.TOTALORDER, "pred"); + ch.put(CompSym.ABSTRACT, "abstract"); + ch.put(CompSym.ALL, "all"); + ch.put(CompSym.ALL2, "all"); + ch.put(CompSym.AMPERSAND, "&"); + ch.put(CompSym.AND, "&&"); + ch.put(CompSym.AS, "as"); + ch.put(CompSym.ASSERT, "assert"); + ch.put(CompSym.AT, "@"); + ch.put(CompSym.BAR, "|"); + ch.put(CompSym.BUT, "but"); + ch.put(CompSym.CARET, "^"); + ch.put(CompSym.CHECK, "check"); + ch.put(CompSym.COLON, ":"); + ch.put(CompSym.COMMA, ", "); + ch.put(CompSym.DISJ, "disj"); + ch.put(CompSym.DOMAIN, "<:"); + ch.put(CompSym.DOT, "."); + ch.put(CompSym.ELSE, "else"); + ch.put(CompSym.ENUM, "enum"); + ch.put(CompSym.EQUALS, "="); + ch.put(CompSym.EXACTLY, "exactly"); + ch.put(CompSym.EXH, "exh"); + ch.put(CompSym.EXPECT, "expect"); + ch.put(CompSym.EXTENDS, "extends"); + ch.put(CompSym.FACT, "fact"); + ch.put(CompSym.FOR, "for"); + ch.put(CompSym.FUN, "fun"); + ch.put(CompSym.GT, ">"); + ch.put(CompSym.GTE, ">="); + ch.put(CompSym.HASH, "#"); + ch.put(CompSym.IDEN, "iden"); + ch.put(CompSym.IFF, "iff"); + ch.put(CompSym.IMPLIES, "=>"); + ch.put(CompSym.IN, "in"); + ch.put(CompSym.INT, "int"); + ch.put(CompSym.LBRACE, "{"); + ch.put(CompSym.LBRACKET, "["); + ch.put(CompSym.LET, "let"); + ch.put(CompSym.LONE2, "lone"); + ch.put(CompSym.LONE, "lone"); + ch.put(CompSym.LPAREN, "("); + ch.put(CompSym.LT, "<"); + ch.put(CompSym.LTE, "<="); + ch.put(CompSym.MINUS, "-"); + ch.put(CompSym.MODULE, "module"); + ch.put(CompSym.NO2, "no"); + ch.put(CompSym.NO, "no"); + ch.put(CompSym.NONE, "none"); + ch.put(CompSym.NOT, "!"); + ch.put(CompSym.NOTEQUALS, "!"); + ch.put(CompSym.NOTGT, "!"); + ch.put(CompSym.NOTGTE, "!"); + ch.put(CompSym.NOTIN, "!"); + ch.put(CompSym.NOTLT, "!"); + ch.put(CompSym.NOTLTE, "!"); + ch.put(CompSym.ONE2, "one"); + ch.put(CompSym.ONE, "one"); + ch.put(CompSym.OPEN, "open"); + ch.put(CompSym.OR, "||"); + ch.put(CompSym.PART, "part"); + ch.put(CompSym.PLUS, "+"); + ch.put(CompSym.PLUSPLUS, "++"); + ch.put(CompSym.PRED, "pred"); + ch.put(CompSym.PRIVATE, "private"); + ch.put(CompSym.RANGE, ":>"); + ch.put(CompSym.RBRACE, "}"); + ch.put(CompSym.RBRACKET, "]"); + ch.put(CompSym.RPAREN, ")"); + ch.put(CompSym.RUN, "run"); + ch.put(CompSym.SEQ, "seq"); + ch.put(CompSym.SET, "set"); + ch.put(CompSym.SHL, "<<"); + ch.put(CompSym.SHR, ">>>"); + ch.put(CompSym.SHA, ">>"); + ch.put(CompSym.SIG, "sig"); + ch.put(CompSym.SIGINT, "Int"); + ch.put(CompSym.SLASH, "/"); + ch.put(CompSym.SOME2, "some"); + ch.put(CompSym.SOME, "some"); + ch.put(CompSym.STAR, "*"); + ch.put(CompSym.STRING, "String"); + ch.put(CompSym.SUM2, "sum"); + ch.put(CompSym.SUM, "sum"); + ch.put(CompSym.THIS, "this"); + ch.put(CompSym.TILDE, "~"); + ch.put(CompSym.UNIV, "univ"); + ch.put(CompSym.ID, "NAME"); + ch.put(CompSym.NUMBER, "NUMBER"); + ch.put(CompSym.STR, "STRING"); + TreeSet list = new TreeSet(); + Pos p=Pos.UNKNOWN; + if (x!=null && x.value instanceof Pos) p=(Pos)(x.value); + else if (x!=null && x.value instanceof Expr) p=((Expr)(x.value)).pos; + else if (x!=null) p=x.pos; + if (!stack.empty()) for(Map.Entry e:ch.entrySet()) { + int key=e.getKey(), act=get_action(((Symbol)stack.peek()).parse_state, key); + if (act==0) continue; + try { + if (act>0 || alloy_confirm(key)) list.add(e.getValue()); + } catch(Throwable ex) { + // If the parser is really really confused, alloy_confirm() could fail with array out-of-bound exception, etc. + } + } + String result="There are "+list.size()+" possible tokens that can appear here:\n"; + for(String item:list) result=result+item+" "; + throw new ErrorSyntax(p, (list.size()!=0)?result:""); + } + + private boolean alloy_confirm(int key) { + int state = ((Symbol)stack.peek()).parse_state; + Stack newstack=new Stack(); for(Object x:stack) newstack.push(x); + while(true) { + int act = get_action(state, key); + if (act>0) return true; + if (act==0) return false; + int lhs_sym_num = production_tab[(-act)-1][0]; + int handle_size = production_tab[(-act)-1][1]; + for (int i = 0; i < handle_size; i++) { if (newstack.empty()) return false; newstack.pop(); } + if (newstack.empty()) return false; + if (newstack.peek() instanceof Symbol) state=((Symbol)newstack.peek()).parse_state; + state=get_reduce(state, lhs_sym_num); + newstack.push(null); + } + } + + static CompModule alloy_parseStream (List seenDollar, + Map loaded, Map fc, CompModule root, + int lineOffset, String filename, String prefix, int initialResolutionMode) throws Err, FileNotFoundException, IOException { + Reader isr=null; + try { + if (root==null && prefix.length()!=0) throw new ErrorFatal("Internal error (parse subfile with root==null)"); + if (root!=null && prefix.length()==0) throw new ErrorFatal("Internal error (parse topfile with root!=null)"); + CompModule u = new CompModule(root, filename, prefix); + if (root == null) + u.addOpen(null, null, ExprVar.make(null, "util/integer"), null, ExprVar.make(null, "integer")); + u.resolution = initialResolutionMode; + String content = fc!=null ? fc.get(filename) : null; + if (content==null && loaded!=null) content = loaded.get(filename); + if (content==null) content = Util.readAll(filename); + if (loaded!=null) loaded.put(filename,content); + content = Util.convertLineBreak(content); + isr = new StringReader(content); + CompFilter s = new CompFilter(u, seenDollar, filename, lineOffset, new BufferedReader(isr)); + CompParser p = new CompParser(s); + p.alloymodule=u; + try {p.parse();} catch(Throwable ex) {if (ex instanceof Err) throw (Err)ex; throw new ErrorFatal("Parser Exception", ex);} + // if no sigs are defined by the user, add one + if (root == null && u.getAllSigs().isEmpty()) { + u.addGhostSig(); + } + return u; + } finally { + Util.close(isr); + } + } + +:}; + +action code {: + /** This function is needed to handle a difficult parsing ambiguity. + * + *

+ * "some EXPR", "one EXPR", and "lone EXPR" + * can be either formulas (saying the EXPR has at least 1, exactly 1, or at most 1 tuple), + * or multiplicity constraints (saying something else has this multiplicity). + * + *

+ * So we let the parser generate the former by default. + * And whenever we construct a Decl "x: y" object, + * or an binary expression "x in y", or a function return type, + * we call this method on y to convert it into a multiplicity constraint. + * + *

+ * This is safe, because in all 3 cases, a formula would be illegal. + * So the first form is always wrong. + * + *

+ * And this is sufficient, because those are the only 3 places + * where a mulitplicity constraint is allowed to appear. + * + * @return a newly formed multiplciity constraint (if this.op==SOME or LONE or ONE), + * otherwise it just returns the original node. + */ + private Expr mult(Expr x) throws Err { + if (x instanceof ExprUnary) { + ExprUnary y=(ExprUnary)x; + if (y.op==ExprUnary.Op.SOME) return ExprUnary.Op.SOMEOF.make(y.pos, y.sub); + if (y.op==ExprUnary.Op.LONE) return ExprUnary.Op.LONEOF.make(y.pos, y.sub); + if (y.op==ExprUnary.Op.ONE) return ExprUnary.Op.ONEOF.make(y.pos, y.sub); + } + return x; + } + private void nod(ExprVar name) throws Err { + if (name.label.indexOf('$')>=0) throw new ErrorSyntax(name.pos, "The name cannot contain the '$' symbol."); + } + private void nod(List names) throws Err { + if (names!=null) for(ExprVar n:names) if (n!=null && n.label.indexOf('$')>=0) throw new ErrorSyntax(n.pos, "The name cannot contain the '$' symbol."); + } + private void c(boolean follow, ExprVar o, ExprVar x, ExprVar n, Expr e, List s, ExprConstant c) throws Err { + if (n!=null) nod(n); + int bitwidth=(-1), maxseq=(-1), overall=(-1), expects=(c==null ? -1 : c.num); + Pos p = o.pos.merge(n!=null ? n.span() : e.span()); + for(int i=s.size()-1; i>=0; i--) { + Sig j=s.get(i).sig; int k=s.get(i).startingScope; + p=p.merge(j.pos); + if (j.label.equals("univ")) { overall=k; s.remove(i); continue; } + if (j.label.equals("int")) { if (bitwidth>=0) throw new ErrorSyntax(j.pos, "The bitwidth cannot be specified more than once."); bitwidth=k; s.remove(i); continue; } + if (j.label.equals("seq")) { if (maxseq>=0) throw new ErrorSyntax(j.pos, "The maximum sequence length cannot be specified more than once."); maxseq=k; s.remove(i); continue; } + } + if (n!=null) + parser.alloymodule.addCommand(follow, p, n.label, o.label.equals("c"), overall, bitwidth, maxseq, expects, s, x); + else + parser.alloymodule.addCommand(follow, p, e, o.label.equals("c"), overall, bitwidth, maxseq, expects, s, x); + } + private Expr t(Pos pos, Pos oldClosing, Expr left, Expr right, Pos close) throws Err { + if (right instanceof ExprVar) { + String n = ((ExprVar)right).label; + if (n.equals("int")) return ExprUnary.Op.CAST2INT.make(pos, left); + if (n.equals("disj")) return ExprList.makeDISJOINT(pos, close, Util.asList(left)); + if (n.equals("pred/totalOrder")) return ExprList.makeTOTALORDER(pos, close, Util.asList(left)); + } + else if (right instanceof ExprList) { + return ((ExprList)right).addArg(left); + } + return ExprBadJoin.make(pos, oldClosing, left, right); + } +:}; + +//===========================================================================// + +terminal Pos ARROW; // -> +terminal Pos ANY_ARROW_SOME; // ->some // The filter allows whitespace/comment in these 15 "*->*" tokens +terminal Pos ANY_ARROW_ONE; // ->one // The filter allows whitespace/comment in these 15 "*->*" tokens +terminal Pos ANY_ARROW_LONE; // ->lone // The filter allows whitespace/comment in these 15 "*->*" tokens +terminal Pos SOME_ARROW_ANY; // some-> // The filter allows whitespace/comment in these 15 "*->*" tokens +terminal Pos SOME_ARROW_SOME; // some->some // The filter allows whitespace/comment in these 15 "*->*" tokens +terminal Pos SOME_ARROW_ONE; // some->one // The filter allows whitespace/comment in these 15 "*->*" tokens +terminal Pos SOME_ARROW_LONE; // some->lone // The filter allows whitespace/comment in these 15 "*->*" tokens +terminal Pos ONE_ARROW_ANY; // one-> // The filter allows whitespace/comment in these 15 "*->*" tokens +terminal Pos ONE_ARROW_SOME; // one->some // The filter allows whitespace/comment in these 15 "*->*" tokens +terminal Pos ONE_ARROW_ONE; // one->one // The filter allows whitespace/comment in these 15 "*->*" tokens +terminal Pos ONE_ARROW_LONE; // one->lone // The filter allows whitespace/comment in these 15 "*->*" tokens +terminal Pos LONE_ARROW_ANY; // lone->any // The filter allows whitespace/comment in these 15 "*->*" tokens +terminal Pos LONE_ARROW_SOME; // lone->some // The filter allows whitespace/comment in these 15 "*->*" tokens +terminal Pos LONE_ARROW_ONE; // lone->one // The filter allows whitespace/comment in these 15 "*->*" tokens +terminal Pos LONE_ARROW_LONE; // lone->lone // The filter allows whitespace/comment in these 15 "*->*" tokens + +terminal Pos INTADD; +terminal Pos INTSUB; +terminal Pos INTMUL; +terminal Pos INTDIV; +terminal Pos INTREM; +terminal Pos INTMIN; +terminal Pos INTMAX; +terminal Pos INTNEXT; +terminal Pos TOTALORDER; + +terminal Pos ABSTRACT; // abstract +terminal Pos ALL; // all // The filter enables us to disambiguate +terminal Pos ALL2; // all // The filter enables us to disambiguate +terminal Pos AMPERSAND; // & +terminal Pos AND; // && and +terminal Pos AS; // as +terminal Pos ASSERT; // assert +terminal Pos AT; // @ +terminal Pos BAR; // | +terminal Pos BUT; // but +terminal Pos CARET; // ^ +terminal Pos CHECK; // check +terminal Pos COLON; // : +terminal Pos COMMA; // , +terminal Pos DISJ; // disj disjoint +terminal Pos DOMAIN; // <: +terminal Pos DOT; // . +terminal Pos ELSE; // else +terminal Pos ENUM; // enum +terminal Pos EQUALS; // = == +terminal Pos EXACTLY; // exactly +terminal Pos EXH; // exh exhaustive +terminal Pos EXPECT; // expect +terminal Pos EXTENDS; // extends +terminal Pos FACT; // fact +terminal Pos FOR; // for +terminal Pos FUN; // fun +terminal Pos GT; // > +terminal Pos GTE; // >= +terminal Pos HASH; // # +terminal Pos IDEN; // iden +terminal Pos IFF; // <=> iff +terminal Pos IMPLIES; // => implies +terminal Pos IN; // in +terminal Pos INT; // int +terminal Pos LBRACE; // { +terminal Pos LBRACKET; // [ +terminal Pos LET; // let +terminal Pos LONE2; // lone // The filter enables us to disambiguate +terminal Pos LONE; // lone // The filter enables us to disambiguate +terminal Pos LPAREN; // ( +terminal Pos LT; // < +terminal Pos LTE; // <= =< +terminal Pos MINUS; // - +terminal Pos MODULE; // module +terminal Pos NO2; // no // The filter enables us to disambiguate +terminal Pos NO; // no // The filter enables us to disambiguate +terminal Pos NONE; // none +terminal Pos NOT; // ! not +terminal Pos NOTEQUALS; // != not= // The filter allows whitespace/comment in between +terminal Pos NOTGT; // !> not> // The filter allows whitespace/comment in between +terminal Pos NOTGTE; // !>= not>= // The filter allows whitespace/comment in between +terminal Pos NOTIN; // !in notin // The filter allows whitespace/comment in between +terminal Pos NOTLT; // !< not< // The filter allows whitespace/comment in between +terminal Pos NOTLTE; // !=< not=< // The filter allows whitespace/comment in between +terminal Pos ONE2; // one // The filter enables us to disambiguate +terminal Pos ONE; // one // The filter enables us to disambiguate +terminal Pos OPEN; // open +terminal Pos OR; // || or +terminal Pos PART; // part partition +terminal Pos PLUS; // + +terminal Pos PLUSPLUS; // ++ +terminal Pos PRED; // pred +terminal Pos PRIVATE; // private +terminal Pos RANGE; // :> +terminal Pos RBRACE; // } +terminal Pos RBRACKET; // ] +terminal Pos RPAREN; // ) +terminal Pos RUN; // run +terminal Pos SEQ; // seq +terminal Pos SET; // set +terminal Pos SHL; // << +terminal Pos SHR; // >>> +terminal Pos SHA; // >> +terminal Pos SIG; // sig +terminal Pos SIGINT; // Int +terminal Pos SLASH; // / +terminal Pos SOME2; // some // The filter enables us to disambiguate +terminal Pos SOME; // some // The filter enables us to disambiguate +terminal Pos STAR; // * +terminal Pos STRING; // String +terminal Pos SUM2; // sum // The filter enables us to disambiguate +terminal Pos SUM; // sum // The filter enables us to disambiguate +terminal Pos THIS; // this +terminal Pos TILDE; // ~ +terminal Pos UNIV; // univ + +terminal ExprVar ID; + +terminal ExprConstant NUMBER, STR; + +//===========================================================================// + +nonterminal Expr AndExprA; +nonterminal Expr AndExprB; +nonterminal Expr BaseExpr; +nonterminal Expr Bind; +nonterminal Expr BracketExprA; +nonterminal Expr BracketExprB; +nonterminal Expr CompareExprA; +nonterminal Expr CompareExprB; +nonterminal Command; +nonterminal ExprVar CommandPrefix; +nonterminal Decl Decla; +nonterminal Decl Declb; +nonterminal List Declp; +nonterminal List Decls; +nonterminal List Declz; +nonterminal Expr DomainExprA; +nonterminal Expr DomainExprB; +nonterminal Expr DotExprA; +nonterminal Expr DotExprB; +nonterminal Expr EquivExprA; +nonterminal Expr EquivExprB; +nonterminal ExprConstant Expects; +nonterminal Expr Expr; +nonterminal Expr Super; +nonterminal Expr SuperOpt; +nonterminal Expr SuperP; +nonterminal Expr SuperOrBar; +nonterminal List Exprs; +nonterminal List Exprp; +nonterminal Function; +nonterminal Expr ImpliesExprA; +nonterminal Expr ImpliesExprB; +nonterminal Expr ImpliesExprCloseA; +nonterminal Expr ImpliesExprCloseB; +nonterminal Expr ImpliesExprOpenA; +nonterminal Expr ImpliesExprOpenB; +nonterminal Expr IntersectExprA; +nonterminal Expr IntersectExprB; +nonterminal Expr Let; +nonterminal Macro; +nonterminal Expr MacroBody; +nonterminal ExprVar Name; +nonterminal ExprVar NameHelper; +nonterminal List Names; +nonterminal List Namex; +nonterminal Expr NegExprA; +nonterminal Expr NegExprB; +nonterminal Expr NumUnopExprA; +nonterminal Expr NumUnopExprB; +nonterminal Expr OrExprA; +nonterminal Expr OrExprB; +nonterminal Expr OverrideExprA; +nonterminal Expr OverrideExprB; +nonterminal Predicate; +nonterminal Expr RangeExprA; +nonterminal Expr RangeExprB; +nonterminal Pair RelOp; +nonterminal Expr RelationExprA; +nonterminal Expr RelationExprB; +nonterminal List Scope; +nonterminal Sig; +nonterminal List SigIn; +nonterminal List SigQual; +nonterminal List SigQuals; +nonterminal ExprVar SigRef; +nonterminal List SigRefp; +nonterminal List SigRefs; +nonterminal List SigRefu; +nonterminal File; +nonterminal Spec; +nonterminal CommandScope TypeNumber; +nonterminal CommandScope Typescope; +nonterminal List Typescopes; +nonterminal Expr ShiftExprA; +nonterminal Expr ShiftExprB; +nonterminal Expr MulExprA; +nonterminal Expr MulExprB; +nonterminal Expr UnionDiffExprA; +nonterminal Expr UnionDiffExprB; +nonterminal Expr UnopExprA; +nonterminal Expr UnopExprB; +nonterminal Pos Vis; + +//===========================================================================// + +File ::= Spec {: parser.alloymodule.doneParsing(); :}; + +Spec ::= Spec MODULE:o Name:n {: nod(n); parser.alloymodule.addModelName(o.merge(n.pos) , n.label , new ArrayList()); :}; +Spec ::= Spec MODULE:o Name:n LBRACKET Namex:b RBRACKET:r {: nod(n); nod(b); parser.alloymodule.addModelName(o.merge(r) , n.label , b ); :}; +Spec ::= Spec Vis:p OPEN:o Name:a {: nod(a); parser.alloymodule.addOpen(o.merge(a.pos), p, a, null, null); :}; +Spec ::= Spec Vis:p OPEN:o Name:a AS Name:c {: nod(a); nod(c); parser.alloymodule.addOpen(o.merge(c.pos), p, a, null, c); :}; +Spec ::= Spec Vis:p OPEN:o Name:a LBRACKET SigRefs:b RBRACKET:c {: nod(a); parser.alloymodule.addOpen(o.merge(c), p, a, b, null); :}; +Spec ::= Spec Vis:p OPEN:o Name:a LBRACKET SigRefs:b RBRACKET AS Name:c {: nod(a); nod(c); parser.alloymodule.addOpen(o.merge(c.pos), p, a, b, c); :}; +Spec ::= Spec Vis:p ENUM:o Name:a LBRACE Names:n RBRACE:c {: nod(a); parser.alloymodule.addEnum(o.merge(c), p, a, n, c); :}; +Spec ::= Spec Vis:p ENUM:o Name:a LBRACE RBRACE:c {: nod(a); parser.alloymodule.addEnum(o.merge(c), p, a, null, c); :}; +Spec ::= Spec FACT:o Super:e {: parser.alloymodule.addFact (o , "" , e); :}; +Spec ::= Spec FACT:o Name:n Super:e {: nod(n); parser.alloymodule.addFact (o , n.label , e); :}; +Spec ::= Spec FACT:o STR:n Super:e {: parser.alloymodule.addFact (o , n.string , e); :}; +Spec ::= Spec ASSERT:o Super:e {: parser.alloymodule.addAssertion (o , "" , e); :}; +Spec ::= Spec ASSERT:o Name:n Super:e {: nod(n); parser.alloymodule.addAssertion (o , n.label , e); :}; +Spec ::= Spec ASSERT:o STR:n Super:e {: parser.alloymodule.addAssertion (o , n.string , e); :}; +Spec ::= Spec Sig ; +Spec ::= Spec Function ; +Spec ::= Spec Predicate ; +Spec ::= Spec Macro ; +Spec ::= Spec Command ; +Spec ::= ; + +CommandPrefix ::= CHECK:c {: RESULT = ExprVar.make(c, "c"); :}; +CommandPrefix ::= RUN:r {: RESULT = ExprVar.make(r, "r"); :}; + +Command ::= CommandPrefix:o Name:x Super:e Scope:s Expects:c {: c(false,o,x ,null,e ,s,c); :}; +Command ::= CommandPrefix:o Super:e Scope:s Expects:c {: c(false,o,null,null,e ,s,c); :}; +Command ::= Command IMPLIES CommandPrefix:o Name:x Super:e Scope:s Expects:c {: c(true ,o,x ,null,e ,s,c); :}; +Command ::= Command IMPLIES CommandPrefix:o Super:e Scope:s Expects:c {: c(true ,o,null,null,e ,s,c); :}; +Command ::= CommandPrefix:o Name:x Name:n Scope:s Expects:c {: c(false,o,x ,n ,null,s,c); :}; +Command ::= CommandPrefix:o Name:n Scope:s Expects:c {: c(false,o,null,n ,null,s,c); :}; +Command ::= Command IMPLIES CommandPrefix:o Name:x Name:n Scope:s Expects:c {: c(true ,o,x ,n ,null,s,c); :}; +Command ::= Command IMPLIES CommandPrefix:o Name:n Scope:s Expects:c {: c(true ,o,null,n ,null,s,c); :}; + +Expects ::= {: RESULT=null; :}; +Expects ::= EXPECT NUMBER:a {: RESULT=a; :}; + +Scope ::= FOR NUMBER:a {: RESULT=new ArrayList(); RESULT.add(new CommandScope(a.pos, new PrimSig("univ", AttrType.WHERE.make(a.pos)), true, a.num, a.num, 1)); :}; +Scope ::= FOR NUMBER:a BUT Typescopes:b {: RESULT=b; b.add(new CommandScope(a.pos, new PrimSig("univ", AttrType.WHERE.make(a.pos)), true, a.num, a.num, 1)); :}; +Scope ::= FOR Typescopes:b {: RESULT=b; :}; +Scope ::= {: RESULT=new ArrayList(); :}; + +Typescopes ::= Typescope:a {: RESULT=new ArrayList(); RESULT.add(a); :}; +Typescopes ::= Typescopes:a COMMA Typescope:b {: RESULT=a; a.add(b); :}; + +Typescope ::= TypeNumber:a Name:b {: + nod(b); + RESULT = new CommandScope(a.pos.merge(b.pos), new PrimSig(b.label, AttrType.WHERE.make(a.pos.merge(b.pos))), a.isExact, a.startingScope, a.endingScope, a.increment); +:}; + +//[AM]: INT -> SIGINT +Typescope ::= TypeNumber:a SIGINT:b {: + Pos p = a.pos.merge(b); + if (a.endingScope>a.startingScope) throw new ErrorSyntax(p, "Cannot specify a growing scope for \"Int\""); + if (a.isExact) throw new ErrorSyntax(p, "The exactly keyword is redundant here since the integer bitwidth must be exact."); + RESULT = new CommandScope(p, new PrimSig("int", AttrType.WHERE.make(p)), a.isExact, a.startingScope, a.startingScope, 1); +:}; + +Typescope ::= TypeNumber:a INT:b {: + Pos p = a.pos.merge(b); + if (a.endingScope>a.startingScope) throw new ErrorSyntax(p, "Cannot specify a growing scope for \"Int\""); + if (a.isExact) throw new ErrorSyntax(p, "The exactly keyword is redundant here since the integer bitwidth must be exact."); + RESULT = new CommandScope(p, new PrimSig("int", AttrType.WHERE.make(p)), a.isExact, a.startingScope, a.startingScope, 1); +:}; + +Typescope ::= TypeNumber:a SEQ:b {: + Pos p = a.pos.merge(b); + if (a.endingScope>a.startingScope) throw new ErrorSyntax(p, "Cannot specify a growing scope for \"seq\""); + if (a.isExact) throw new ErrorSyntax(p, "The exactly keyword is redundant here since the number of sequence index has to be exact."); + RESULT = new CommandScope(p, new PrimSig("seq", AttrType.WHERE.make(p)), a.isExact, a.startingScope, a.startingScope, 1); +:}; + +Typescope ::= TypeNumber:e UNIV:f {: if (1==1) throw new ErrorSyntax(e.pos.merge(f), "You cannot set a scope on univ."); :}; + +Typescope ::= TypeNumber:a STRING:b {: RESULT = new CommandScope(a.pos.merge(b), new PrimSig("String", AttrType.WHERE.make(a.pos.merge(b))), a.isExact, a.startingScope, a.endingScope, a.increment); :}; + +//[AM] Typescope ::= TypeNumber:e SIGINT:f {: if (1==1) throw new ErrorSyntax(e.pos.merge(f), "You can no longer set a scope on Int; the number of Int atoms is always exactly equal to 2^(integer bitwidth).\n"); :}; + +Typescope ::= TypeNumber:e NONE:f {: if (1==1) throw new ErrorSyntax(e.pos.merge(f), "You cannot set a scope on none."); :}; + +TypeNumber ::= EXACTLY:e NUMBER:a {: RESULT = new CommandScope( e.merge(a.pos), Sig.NONE, true, a.num, a.num, 1 ); :}; +TypeNumber ::= EXACTLY:e NUMBER:a DOT DOT NUMBER:b {: if (!Version.experimental) throw new ErrorSyntax(a.pos, "Syntax error here."); RESULT = new CommandScope( e.merge(b.pos), Sig.NONE, true, a.num, b.num, 1 ); :}; +TypeNumber ::= EXACTLY:e NUMBER:a DOT DOT NUMBER:b COLON NUMBER:i {: if (!Version.experimental) throw new ErrorSyntax(a.pos, "Syntax error here."); RESULT = new CommandScope( e.merge(i.pos), Sig.NONE, true, a.num, b.num, i.num); :}; +TypeNumber ::= EXACTLY:e NUMBER:a COLON NUMBER:i {: if (!Version.experimental) throw new ErrorSyntax(a.pos, "Syntax error here."); RESULT = new CommandScope( e.merge(i.pos), Sig.NONE, true, a.num, Integer.MAX_VALUE, i.num); :}; +TypeNumber ::= NUMBER:a {: RESULT = new CommandScope(a.pos , Sig.NONE, false, a.num, a.num, 1 ); :}; +TypeNumber ::= NUMBER:a DOT DOT NUMBER:b {: if (!Version.experimental) throw new ErrorSyntax(a.pos, "Syntax error here."); RESULT = new CommandScope(a.pos.merge(b.pos), Sig.NONE, false, a.num, b.num, 1 ); :}; +TypeNumber ::= NUMBER:a DOT DOT NUMBER:b COLON NUMBER:i {: if (!Version.experimental) throw new ErrorSyntax(a.pos, "Syntax error here."); RESULT = new CommandScope(a.pos.merge(i.pos), Sig.NONE, false, a.num, b.num, i.num); :}; +TypeNumber ::= NUMBER:a COLON NUMBER:i {: if (!Version.experimental) throw new ErrorSyntax(a.pos, "Syntax error here."); RESULT = new CommandScope(a.pos.merge(i.pos), Sig.NONE, false, a.num, Integer.MAX_VALUE, i.num); :}; + +Macro ::= Vis:p LET:o Name:n LPAREN Names:d RPAREN MacroBody:v {: nod(n); parser.alloymodule.addMacro(o.merge(v.span()), p, n.label, d , v); :}; +Macro ::= Vis:p LET:o Name:n LPAREN RPAREN MacroBody:v {: nod(n); parser.alloymodule.addMacro(o.merge(v.span()), p, n.label, null , v); :}; +Macro ::= Vis:p LET:o Name:n LBRACKET Names:d RBRACKET MacroBody:v {: nod(n); parser.alloymodule.addMacro(o.merge(v.span()), p, n.label, d , v); :}; +Macro ::= Vis:p LET:o Name:n LBRACKET RBRACKET MacroBody:v {: nod(n); parser.alloymodule.addMacro(o.merge(v.span()), p, n.label, null , v); :}; +Macro ::= Vis:p LET:o Name:n MacroBody:v {: nod(n); parser.alloymodule.addMacro(o.merge(v.span()), p, n.label, null , v); :}; + +MacroBody ::= Super:a {: RESULT=a; :}; +MacroBody ::= EQUALS Expr:a {: RESULT=a; :}; + +Function ::= Vis:p FUN:o Name:n LPAREN Decls:d RPAREN COLON Expr:r Super:v {: nod(n); parser.alloymodule.addFunc(o.merge(v.span()), p, n.label, null, d , mult(r), v); :}; +Function ::= Vis:p FUN:o Name:n LBRACKET Decls:d RBRACKET COLON Expr:r Super:v {: nod(n); parser.alloymodule.addFunc(o.merge(v.span()), p, n.label, null, d , mult(r), v); :}; +Function ::= Vis:p FUN:o Name:n COLON Expr:r Super:v {: nod(n); parser.alloymodule.addFunc(o.merge(v.span()), p, n.label, null, null , mult(r), v); :}; +Function ::= Vis:p FUN:o SigRef:f DOT Name:n LPAREN Decls:d RPAREN COLON Expr:r Super:v {: nod(n); parser.alloymodule.addFunc(o.merge(v.span()), p, n.label, f , d , mult(r), v); :}; +Function ::= Vis:p FUN:o SigRef:f DOT Name:n LBRACKET Decls:d RBRACKET COLON Expr:r Super:v {: nod(n); parser.alloymodule.addFunc(o.merge(v.span()), p, n.label, f , d , mult(r), v); :}; +Function ::= Vis:p FUN:o SigRef:f DOT Name:n COLON Expr:r Super:v {: nod(n); parser.alloymodule.addFunc(o.merge(v.span()), p, n.label, f , null , mult(r), v); :}; + +Predicate ::= Vis:p PRED:o Name:n LPAREN Decls:d RPAREN Super:v {: nod(n); parser.alloymodule.addFunc(o.merge(v.span()), p, n.label, null, d , null, v); :}; +Predicate ::= Vis:p PRED:o Name:n LBRACKET Decls:d RBRACKET Super:v {: nod(n); parser.alloymodule.addFunc(o.merge(v.span()), p, n.label, null, d , null, v); :}; +Predicate ::= Vis:p PRED:o Name:n Super:v {: nod(n); parser.alloymodule.addFunc(o.merge(v.span()), p, n.label, null, null , null, v); :}; +Predicate ::= Vis:p PRED:o SigRef:f DOT Name:n LPAREN Decls:d RPAREN Super:v {: nod(n); parser.alloymodule.addFunc(o.merge(v.span()), p, n.label, f , d , null, v); :}; +Predicate ::= Vis:p PRED:o SigRef:f DOT Name:n LBRACKET Decls:d RBRACKET Super:v {: nod(n); parser.alloymodule.addFunc(o.merge(v.span()), p, n.label, f , d , null, v); :}; +Predicate ::= Vis:p PRED:o SigRef:f DOT Name:n Super:v {: nod(n); parser.alloymodule.addFunc(o.merge(v.span()), p, n.label, f , null , null, v); :}; + +Vis ::= {: RESULT=null; :}; +Vis ::= PRIVATE:p {: RESULT=p; :}; + +Sig ::= SigQuals:a Names:b SigIn:c LBRACE Decls:d RBRACE:o SuperOpt:e + {: + if (e==null) e = ExprConstant.Op.TRUE.make(o, 0); + ExprVar cc = (c!=null && c.size()>0) ? c.remove(c.size()-1) : null; + for(ExprVar bb:b) { + parser.alloymodule.addSig(bb.label, cc, c, d, e, + AttrType.WHERE .makenull(bb.pos.merge(e==null ? o : e.span())), + AttrType.ABSTRACT.makenull(a.get(0)), + AttrType.LONE .makenull(a.get(1)), + AttrType.ONE .makenull(a.get(2)), + AttrType.SOME .makenull(a.get(3)), + AttrType.PRIVATE .makenull(a.get(4))); + } + :}; + +SigQual ::= ABSTRACT:x {: RESULT=new ArrayList(5); RESULT.add(x); RESULT.add(null); RESULT.add(null); RESULT.add(null); RESULT.add(null); :}; +SigQual ::= LONE:x {: RESULT=new ArrayList(5); RESULT.add(null); RESULT.add(x); RESULT.add(null); RESULT.add(null); RESULT.add(null); :}; +SigQual ::= ONE:x {: RESULT=new ArrayList(5); RESULT.add(null); RESULT.add(null); RESULT.add(x); RESULT.add(null); RESULT.add(null); :}; +SigQual ::= SOME:x {: RESULT=new ArrayList(5); RESULT.add(null); RESULT.add(null); RESULT.add(null); RESULT.add(x); RESULT.add(null); :}; +SigQual ::= PRIVATE:x {: RESULT=new ArrayList(5); RESULT.add(null); RESULT.add(null); RESULT.add(null); RESULT.add(null); RESULT.add(x); :}; + +SigQuals ::= SIG {: RESULT=new ArrayList(5); RESULT.add(null); RESULT.add(null); RESULT.add(null); RESULT.add(null); RESULT.add(null); :}; +SigQuals ::= SigQual:a SigQuals:b {: RESULT=a; for(int i=0;i<5;i++) if (a.get(i)==null) a.set(i,b.get(i)); else if (b.get(i)!=null) throw new ErrorSyntax(b.get(i), "The same qualifer cannot be specified more than once for the same sig."); :}; + +SigIn ::= EXTENDS:a SigRef:x {: RESULT=new ArrayList(2); RESULT.add(x); RESULT.add(ExprVar.make(a, "extends")); :}; +SigIn ::= IN:a SigRefu:x {: RESULT=x; x.add(ExprVar.make(a,"in")); :}; +SigIn ::= EQUALS:a SigRefu:x {: RESULT=x; x.add(ExprVar.make(a,"=")); :}; +SigIn ::= {: RESULT=null; :}; + +SigRef ::= Name:x {: RESULT=x; :}; +SigRef ::= UNIV:x {: RESULT=ExprVar.make(x, "univ"); :}; +SigRef ::= STRING:x {: RESULT=ExprVar.make(x, "String"); :}; +SigRef ::= SIGINT:x {: RESULT=ExprVar.make(x, "Int"); :}; +SigRef ::= SEQ:a SLASH SIGINT:b {: RESULT=ExprVar.make(a.merge(b), "seq/Int"); :}; +SigRef ::= NONE:x {: RESULT=ExprVar.make(x, "none"); :}; + +SigRefs ::= {: RESULT=new ArrayList(); :}; +SigRefs ::= SigRefp:x {: RESULT=x; :}; + +SigRefp ::= SigRef:x {: RESULT=new ArrayList(); RESULT.add(x); :}; +SigRefp ::= SigRefp:a COMMA SigRef:b {: a.add(b); RESULT=a; :}; + +SigRefu ::= SigRef:x {: RESULT=new ArrayList(); RESULT.add(x); :}; +SigRefu ::= SigRefu:a PLUS SigRef:b {: a.add(b); RESULT=a; :}; + +Name ::= NameHelper:x {: RESULT=x; :}; +Name ::= THIS:a SLASH NameHelper:b {: RESULT=ExprVar.make(a.merge(b.pos), "this/"+b.label); :}; +Name ::= SEQ:a SLASH NameHelper:b {: RESULT=ExprVar.make(a.merge(b.pos), "seq/"+b.label); :}; + +NameHelper ::= ID:x {: RESULT=x; :}; +NameHelper ::= NameHelper:a SLASH ID:b {: RESULT=ExprVar.make(a.pos.merge(b.pos), a.label+"/"+b.label); :}; + +Names ::= Name:x {: nod(x); RESULT=new ArrayList(); RESULT.add(x); :}; +Names ::= Names:a COMMA Name:b {: nod(b); a.add(b); RESULT=a; :}; + +Namex ::= Name:x {: nod(x); RESULT=new ArrayList(); RESULT.add(x); :}; +Namex ::= EXACTLY Name:x {: nod(x); RESULT=new ArrayList(); RESULT.add(null); RESULT.add(x); :}; +Namex ::= Namex:a COMMA Name:b {: nod(b); a.add(b); RESULT=a; :}; +Namex ::= Namex:a COMMA EXACTLY Name:b {: nod(b); a.add(null); a.add(b); RESULT=a; :}; + +Decla ::= PART:k Names COLON Expr {: if (1==1) throw CompModule.hint(k, "part"); :}; +Decla ::= EXH:k Names COLON Expr {: if (1==1) throw CompModule.hint(k, "exh"); :}; +Decla ::= DISJ:k Names:a COLON Expr:b {: RESULT=new Decl(null, k, null, a, mult(b)); :}; +Decla ::= PRIVATE:p DISJ:k Names:a COLON Expr:b {: RESULT=new Decl(p, k, null, a, mult(b)); :}; +Decla ::= PRIVATE:p Names:a COLON Expr:b {: RESULT=new Decl(p, null, null, a, mult(b)); :}; +Decla ::= Names:a COLON Expr:b {: RESULT=new Decl(null, null, null, a, mult(b)); :}; + +Decla ::= PART:k Names COLON DISJ Expr {: if (1==1) throw CompModule.hint(k, "part"); :}; +Decla ::= EXH:k Names COLON DISJ Expr {: if (1==1) throw CompModule.hint(k, "exh"); :}; +Decla ::= DISJ:k Names:a COLON DISJ:d Expr:b {: RESULT=new Decl(null, k, d, a, mult(b)); :}; +Decla ::= PRIVATE:p DISJ:k Names:a COLON DISJ:d Expr:b {: RESULT=new Decl(p, k, d, a, mult(b)); :}; +Decla ::= PRIVATE:p Names:a COLON DISJ:d Expr:b {: RESULT=new Decl(p, null, d, a, mult(b)); :}; +Decla ::= Names:a COLON DISJ:d Expr:b {: RESULT=new Decl(null, null, d, a, mult(b)); :}; + +Declb ::= Decla:x {: RESULT=x; :}; + +Declb ::= PART:k Names EQUALS Expr {: if (1==1) throw CompModule.hint(k, "part"); :}; +Declb ::= EXH:k Names EQUALS Expr {: if (1==1) throw CompModule.hint(k, "exh"); :}; +Declb ::= DISJ:d Names EQUALS Expr {: if (1==1) throw new ErrorSyntax(d, "Defined fields cannot be disjoint."); :}; +Declb ::= PRIVATE DISJ:d Names EQUALS Expr {: if (1==1) throw new ErrorSyntax(d, "Defined fields cannot be disjoint."); :}; +Declb ::= PRIVATE:p Names:a EQUALS Expr:b {: RESULT=new Decl(p, null, null, a, ExprUnary.Op.EXACTLYOF.make(null, b)); :}; +Declb ::= Names:a EQUALS Expr:b {: RESULT=new Decl(null, null, null, a, ExprUnary.Op.EXACTLYOF.make(null, b)); :}; + +Declb ::= PART:k Names EQUALS DISJ Expr {: if (1==1) throw CompModule.hint(k, "part"); :}; +Declb ::= EXH:k Names EQUALS DISJ Expr {: if (1==1) throw CompModule.hint(k, "exh"); :}; +Declb ::= DISJ Names EQUALS DISJ:d Expr {: if (1==1) throw new ErrorSyntax(d, "Defined fields cannot be disjoint."); :}; +Declb ::= PRIVATE DISJ Names EQUALS DISJ:d Expr {: if (1==1) throw new ErrorSyntax(d, "Defined fields cannot be disjoint."); :}; +Declb ::= PRIVATE Names EQUALS DISJ:d Expr {: if (1==1) throw new ErrorSyntax(d, "Defined fields cannot be disjoint."); :}; +Declb ::= Names EQUALS DISJ:d Expr {: if (1==1) throw new ErrorSyntax(d, "Defined fields cannot be disjoint."); :}; + +Declz ::= Declz:x COMMA Decla:y {: RESULT=x; RESULT.add(y); :}; +Declz ::= Decla:y {: RESULT=new ArrayList(); RESULT.add(y); :}; + +Declp ::= Declp:x COMMA Declb:y {: RESULT=x; RESULT.add(y); :}; +Declp ::= Declb:y {: RESULT=new ArrayList(); RESULT.add(y); :}; + +Decls ::= {: RESULT=new ArrayList(); :}; +Decls ::= Declb:x {: RESULT=new ArrayList(); RESULT.add(x); :}; +Decls ::= Declb:x COMMA Decls:y {: RESULT=y; RESULT.add(0,x); :}; +Decls ::= COMMA Decls:y {: RESULT=y; :}; + +Let ::= Name:a EQUALS:o Expr:b SuperOrBar:x {: + nod(a); + if (a.label.indexOf('/')>=0) throw new ErrorSyntax(a.pos, "Let variable name cannot contain \'/\'"); + if (a.label.indexOf('@')>=0) throw new ErrorSyntax(a.pos, "Let variable name cannot contain \'@\'"); + RESULT = ExprLet.make(o, ExprVar.make(a.pos, a.label), b, x); +:}; + +Let ::= Name:a EQUALS:o Expr:b COMMA Let:x {: + nod(a); + if (a.label.indexOf('/')>=0) throw new ErrorSyntax(a.pos, "Let variable name cannot contain \'/\'"); + if (a.label.indexOf('@')>=0) throw new ErrorSyntax(a.pos, "Let variable name cannot contain \'@\'"); + RESULT = ExprLet.make(o, ExprVar.make(a.pos, a.label), b, x); +:}; + +SuperOpt ::= {: RESULT=null; :}; +SuperOpt ::= Super:x {: RESULT=x; :}; +Super ::= LBRACE:a SuperP:x RBRACE:b {: RESULT=ExprUnary.Op.NOOP.make(a.merge(b), x); :}; +Super ::= LBRACE:a RBRACE:b {: RESULT=ExprConstant.Op.TRUE.make(a.merge(b), 0); :}; +SuperP ::= Expr:a {: RESULT=a; :}; +SuperP ::= SuperP:a Expr:b {: RESULT=ExprBinary.Op.AND.make(null, null, a, b); :}; + +SuperOrBar ::= BAR Expr:x {: RESULT=x; :}; +SuperOrBar ::= Super:x {: RESULT=x; :}; + +Exprs ::= {: RESULT=new ArrayList(); :}; +Exprs ::= Exprp:x {: RESULT=x; :}; +Exprp ::= Expr:x {: RESULT=new ArrayList(); RESULT.add(x); :}; +Exprp ::= Exprp:a COMMA Expr:b {: a.add(b); RESULT=a; :}; + +//============================================================================= + +Expr ::= OrExprA:x {: RESULT = x; :}; +Expr ::= OrExprB:x {: RESULT = x; :}; +Expr ::= Bind:x {: RESULT = x; :}; +Bind ::= LET Let:x {: RESULT = x; :}; +Bind ::= ALL2:o Declp:a SuperOrBar:b {: RESULT = ExprQt.Op.ALL .make(o, null, a, b); :}; +Bind ::= NO2:o Declp:a SuperOrBar:b {: RESULT = ExprQt.Op.NO .make(o, null, a, b); :}; +Bind ::= SOME2:o Declp:a SuperOrBar:b {: RESULT = ExprQt.Op.SOME.make(o, null, a, b); :}; +Bind ::= LONE2:o Declp:a SuperOrBar:b {: RESULT = ExprQt.Op.LONE.make(o, null, a, b); :}; +Bind ::= ONE2:o Declp:a SuperOrBar:b {: RESULT = ExprQt.Op.ONE .make(o, null, a, b); :}; +Bind ::= SUM2:o Declp:a SuperOrBar:b {: RESULT = ExprQt.Op.SUM .make(o, null, a, b); :}; + +OrExprA ::= EquivExprA:a {: RESULT=a; :}; +OrExprA ::= OrExprB:a OR:o Bind:b {: RESULT=ExprBinary.Op.OR.make(o, null, a, b); :}; +OrExprB ::= EquivExprB:b {: RESULT=b; :}; +OrExprB ::= OrExprB:a OR:o EquivExprB:b {: RESULT=ExprBinary.Op.OR.make(o, null, a, b); :}; + +EquivExprA ::= ImpliesExprA:b {: RESULT=b; :}; +EquivExprA ::= EquivExprB:a IFF:o Bind:b {: RESULT=ExprBinary.Op.IFF.make(o, null, a, b); :}; +EquivExprB ::= ImpliesExprB:b {: RESULT=b; :}; +EquivExprB ::= EquivExprB:a IFF:o ImpliesExprB:b {: RESULT=ExprBinary.Op.IFF.make(o, null, a, b); :}; + +ImpliesExprA ::= ImpliesExprCloseA:a {: RESULT=a; :}; +ImpliesExprA ::= ImpliesExprOpenA:a {: RESULT=a; :}; +ImpliesExprCloseA ::= AndExprA:a {: RESULT=a; :}; +ImpliesExprCloseA ::= AndExprB:a IMPLIES:o ImpliesExprCloseB:b ELSE ImpliesExprCloseA:c {: RESULT = ExprITE.make(o,a,b,c); :}; +ImpliesExprOpenA ::= AndExprB:a IMPLIES:o ImpliesExprCloseB:b ELSE ImpliesExprOpenA:c {: RESULT = ExprITE.make(o,a,b,c); :}; +ImpliesExprOpenA ::= AndExprB:a IMPLIES:o ImpliesExprA:b {: RESULT = ExprBinary.Op.IMPLIES.make(o, null, a, b); :}; + +ImpliesExprCloseA ::= AndExprB:a IMPLIES:o ImpliesExprCloseB:b ELSE Bind:c {: RESULT = ExprITE.make(o,a,b,c); :}; +ImpliesExprOpenA ::= AndExprB:a IMPLIES:o Bind:b {: RESULT = ExprBinary.Op.IMPLIES.make(o, null, a, b); :}; + +ImpliesExprB ::= ImpliesExprCloseB:a {: RESULT=a; :}; +ImpliesExprB ::= ImpliesExprOpenB:a {: RESULT=a; :}; +ImpliesExprCloseB ::= AndExprB:a {: RESULT=a; :}; +ImpliesExprCloseB ::= AndExprB:a IMPLIES:o ImpliesExprCloseB:b ELSE ImpliesExprCloseB:c {: RESULT = ExprITE.make(o,a,b,c); :}; +ImpliesExprOpenB ::= AndExprB:a IMPLIES:o ImpliesExprCloseB:b ELSE ImpliesExprOpenB:c {: RESULT = ExprITE.make(o,a,b,c); :}; +ImpliesExprOpenB ::= AndExprB:a IMPLIES:o ImpliesExprB:b {: RESULT = ExprBinary.Op.IMPLIES.make(o, null, a, b); :}; + +AndExprA ::= NegExprA:a {: RESULT=a; :}; +AndExprA ::= AndExprB:a AND:o Bind:b {: RESULT=ExprBinary.Op.AND.make(o, null, a, b); :}; +AndExprB ::= NegExprB:b {: RESULT=b; :}; +AndExprB ::= AndExprB:a AND:o NegExprB:b {: RESULT=ExprBinary.Op.AND.make(o, null, a, b); :}; + +NegExprA ::= CompareExprA:b {: RESULT=b; :}; +NegExprA ::= NOT:o Bind:b {: RESULT=ExprUnary.Op.NOT.make(o, b); :}; +NegExprA ::= NOT:o NegExprA:b {: RESULT=ExprUnary.Op.NOT.make(o, b); :}; +NegExprB ::= CompareExprB:b {: RESULT=b; :}; +NegExprB ::= NOT:o NegExprB:b {: RESULT=ExprUnary.Op.NOT.make(o, b); :}; + +CompareExprA ::= CompareExprB:a IN:o ShiftExprA:b {: RESULT=ExprBinary.Op.IN .make(o, null, a, mult(b)); :}; +CompareExprA ::= CompareExprB:a EQUALS:o ShiftExprA:b {: RESULT=ExprBinary.Op.EQUALS .make(o, null, a, b); :}; +CompareExprA ::= CompareExprB:a LT:o ShiftExprA:b {: RESULT=ExprBinary.Op.LT .make(o, null, a, b); :}; +CompareExprA ::= CompareExprB:a GT:o ShiftExprA:b {: RESULT=ExprBinary.Op.GT .make(o, null, a, b); :}; +CompareExprA ::= CompareExprB:a LTE:o ShiftExprA:b {: RESULT=ExprBinary.Op.LTE .make(o, null, a, b); :}; +CompareExprA ::= CompareExprB:a GTE:o ShiftExprA:b {: RESULT=ExprBinary.Op.GTE .make(o, null, a, b); :}; +CompareExprA ::= CompareExprB:a NOTIN:o ShiftExprA:b {: RESULT=ExprBinary.Op.NOT_IN .make(o, null, a, mult(b)); :}; +CompareExprA ::= CompareExprB:a NOTEQUALS:o ShiftExprA:b {: RESULT=ExprBinary.Op.NOT_EQUALS.make(o, null, a, b); :}; +CompareExprA ::= CompareExprB:a NOTLT:o ShiftExprA:b {: RESULT=ExprBinary.Op.NOT_LT .make(o, null, a, b); :}; +CompareExprA ::= CompareExprB:a NOTGT:o ShiftExprA:b {: RESULT=ExprBinary.Op.NOT_GT .make(o, null, a, b); :}; +CompareExprA ::= CompareExprB:a NOTLTE:o ShiftExprA:b {: RESULT=ExprBinary.Op.NOT_LTE .make(o, null, a, b); :}; +CompareExprA ::= CompareExprB:a NOTGTE:o ShiftExprA:b {: RESULT=ExprBinary.Op.NOT_GTE .make(o, null, a, b); :}; +CompareExprA ::= ALL:o ShiftExprA {: if (1==1) throw new ErrorSyntax(o,"The \"all x\" construct is no longer supported. If you know the range of possible values of x, consider rewriting it as \"x == set_of_all_possible_values\"."); :}; +CompareExprA ::= NO:o ShiftExprA:b {: RESULT=ExprUnary.Op.NO .make(o, b); :}; +CompareExprA ::= SOME:o ShiftExprA:b {: RESULT=ExprUnary.Op.SOME .make(o, b); :}; +CompareExprA ::= LONE:o ShiftExprA:b {: RESULT=ExprUnary.Op.LONE .make(o, b); :}; +CompareExprA ::= ONE:o ShiftExprA:b {: RESULT=ExprUnary.Op.ONE .make(o, b); :}; +CompareExprA ::= SET:o ShiftExprA:b {: RESULT=ExprUnary.Op.SETOF.make(o, b); :}; +CompareExprA ::= SEQ:o ShiftExprA:b {: RESULT=ExprBinary.Op.ISSEQ_ARROW_LONE.make(o, null, ExprVar.make(o, "seq/Int"), b); parser.alloymodule.addSeq(o); :}; +CompareExprA ::= ShiftExprA:b {: RESULT=b; :}; + +CompareExprB ::= CompareExprB:a IN:o ShiftExprB:b {: RESULT=ExprBinary.Op.IN .make(o, null, a, mult(b)); :}; +CompareExprB ::= CompareExprB:a EQUALS:o ShiftExprB:b {: RESULT=ExprBinary.Op.EQUALS .make(o, null, a, b); :}; +CompareExprB ::= CompareExprB:a LT:o ShiftExprB:b {: RESULT=ExprBinary.Op.LT .make(o, null, a, b); :}; +CompareExprB ::= CompareExprB:a GT:o ShiftExprB:b {: RESULT=ExprBinary.Op.GT .make(o, null, a, b); :}; +CompareExprB ::= CompareExprB:a LTE:o ShiftExprB:b {: RESULT=ExprBinary.Op.LTE .make(o, null, a, b); :}; +CompareExprB ::= CompareExprB:a GTE:o ShiftExprB:b {: RESULT=ExprBinary.Op.GTE .make(o, null, a, b); :}; +CompareExprB ::= CompareExprB:a NOTIN:o ShiftExprB:b {: RESULT=ExprBinary.Op.NOT_IN .make(o, null, a, mult(b)); :}; +CompareExprB ::= CompareExprB:a NOTEQUALS:o ShiftExprB:b {: RESULT=ExprBinary.Op.NOT_EQUALS.make(o, null, a, b); :}; +CompareExprB ::= CompareExprB:a NOTLT:o ShiftExprB:b {: RESULT=ExprBinary.Op.NOT_LT .make(o, null, a, b); :}; +CompareExprB ::= CompareExprB:a NOTGT:o ShiftExprB:b {: RESULT=ExprBinary.Op.NOT_GT .make(o, null, a, b); :}; +CompareExprB ::= CompareExprB:a NOTLTE:o ShiftExprB:b {: RESULT=ExprBinary.Op.NOT_LTE .make(o, null, a, b); :}; +CompareExprB ::= CompareExprB:a NOTGTE:o ShiftExprB:b {: RESULT=ExprBinary.Op.NOT_GTE .make(o, null, a, b); :}; +CompareExprB ::= ALL:o ShiftExprB {: if (1==1) throw new ErrorSyntax(o,"The \"all x\" construct is no longer supported. If you know the range of possible values of x, consider rewriting it as \"x == set_of_all_possible_values\"."); :}; +CompareExprB ::= NO:o ShiftExprB:b {: RESULT=ExprUnary.Op.NO .make(o, b); :}; +CompareExprB ::= SOME:o ShiftExprB:b {: RESULT=ExprUnary.Op.SOME .make(o, b); :}; +CompareExprB ::= LONE:o ShiftExprB:b {: RESULT=ExprUnary.Op.LONE .make(o, b); :}; +CompareExprB ::= ONE:o ShiftExprB:b {: RESULT=ExprUnary.Op.ONE .make(o, b); :}; +CompareExprB ::= SET:o ShiftExprB:b {: RESULT=ExprUnary.Op.SETOF.make(o, b); :}; +CompareExprB ::= SEQ:o ShiftExprB:b {: RESULT=ExprBinary.Op.ISSEQ_ARROW_LONE.make(o, null, ExprVar.make(o,"seq/Int"), b); parser.alloymodule.addSeq(o); :}; +CompareExprB ::= ShiftExprB:b {: RESULT=b; :}; + +ShiftExprA ::= UnionDiffExprA:b {: RESULT=b; :}; +ShiftExprA ::= ShiftExprB:a SHL:o Bind:b {: RESULT=ExprBinary.Op.SHL.make(o, null, a, b); :}; +ShiftExprA ::= ShiftExprB:a SHR:o Bind:b {: RESULT=ExprBinary.Op.SHR.make(o, null, a, b); :}; +ShiftExprA ::= ShiftExprB:a SHA:o Bind:b {: RESULT=ExprBinary.Op.SHA.make(o, null, a, b); :}; +ShiftExprB ::= UnionDiffExprB:b {: RESULT=b; :}; +ShiftExprB ::= ShiftExprB:a SHL:o UnionDiffExprB:b {: RESULT=ExprBinary.Op.SHL.make(o, null, a, b); :}; +ShiftExprB ::= ShiftExprB:a SHR:o UnionDiffExprB:b {: RESULT=ExprBinary.Op.SHR.make(o, null, a, b); :}; +ShiftExprB ::= ShiftExprB:a SHA:o UnionDiffExprB:b {: RESULT=ExprBinary.Op.SHA.make(o, null, a, b); :}; + +UnionDiffExprA ::= MulExprA:b {: RESULT=b; :}; +UnionDiffExprA ::= UnionDiffExprB:a PLUS:o Bind:b {: RESULT=ExprBinary.Op.PLUS .make(o, null, a, b); :}; +UnionDiffExprA ::= UnionDiffExprB:a MINUS:o Bind:b {: RESULT=ExprBinary.Op.MINUS.make(o, null, a, b); :}; +UnionDiffExprA ::= UnionDiffExprB:a INTADD:o Bind:b {: RESULT=ExprBinary.Op.IPLUS.make(o, null, a, b); :}; +UnionDiffExprA ::= UnionDiffExprB:a INTSUB:o Bind:b {: RESULT=ExprBinary.Op.IMINUS.make(o, null, a, b); :}; +UnionDiffExprB ::= MulExprB:b {: RESULT=b; :}; +UnionDiffExprB ::= UnionDiffExprB:a PLUS:o MulExprB:b {: RESULT=ExprBinary.Op.PLUS .make(o, null, a, b); :}; +UnionDiffExprB ::= UnionDiffExprB:a MINUS:o MulExprB:b {: RESULT=ExprBinary.Op.MINUS.make(o, null, a, b); :}; +UnionDiffExprB ::= UnionDiffExprB:a INTADD:o MulExprB:b {: RESULT=ExprBinary.Op.IPLUS.make(o, null, a, b); :}; +UnionDiffExprB ::= UnionDiffExprB:a INTSUB:o MulExprB:b {: RESULT=ExprBinary.Op.IMINUS.make(o, null, a, b); :}; + +MulExprA ::= NumUnopExprA:b {: RESULT=b; :}; +MulExprA ::= MulExprB:a INTMUL:o Bind:b {: RESULT=ExprBinary.Op.MUL .make(o, null, a, b); :}; +MulExprA ::= MulExprB:a INTDIV:o Bind:b {: RESULT=ExprBinary.Op.DIV .make(o, null, a, b); :}; +MulExprA ::= MulExprB:a INTREM:o Bind:b {: RESULT=ExprBinary.Op.REM .make(o, null, a, b); :}; +MulExprB ::= NumUnopExprB:b {: RESULT=b; :}; +MulExprB ::= MulExprB:a INTMUL:o NumUnopExprB:b {: RESULT=ExprBinary.Op.MUL .make(o, null, a, b); :}; +MulExprB ::= MulExprB:a INTDIV:o NumUnopExprB:b {: RESULT=ExprBinary.Op.DIV .make(o, null, a, b); :}; +MulExprB ::= MulExprB:a INTREM:o NumUnopExprB:b {: RESULT=ExprBinary.Op.REM .make(o, null, a, b); :}; + +NumUnopExprA ::= OverrideExprA:b {: RESULT=b; :}; +NumUnopExprA ::= HASH:o Bind:b {: RESULT=ExprUnary.Op.CARDINALITY.make(o, b); :}; +NumUnopExprA ::= SUM:o Bind:b {: RESULT=ExprUnary.Op.CAST2SIGINT.make(o, ExprUnary.Op.CAST2INT.make(o, b)); :}; +//[AM]: INT->SIGINT +NumUnopExprA ::= INT:o Bind:b {: RESULT=ExprUnary.Op.CAST2SIGINT.make(o, ExprUnary.Op.CAST2INT.make(o, b)); :}; +NumUnopExprA ::= HASH:o NumUnopExprA:b {: RESULT=ExprUnary.Op.CARDINALITY.make(o, b); :}; +NumUnopExprA ::= SUM:o NumUnopExprA:b {: RESULT=ExprUnary.Op.CAST2SIGINT.make(o, ExprUnary.Op.CAST2INT.make(o, b)); :}; +//[AM]: INT->SIGINT +NumUnopExprA ::= INT:o NumUnopExprA:b {: RESULT=ExprUnary.Op.CAST2SIGINT.make(o, ExprUnary.Op.CAST2INT.make(o, b)); :}; +NumUnopExprB ::= OverrideExprB:b {: RESULT=b; :}; +NumUnopExprB ::= HASH:o NumUnopExprB:b {: RESULT=ExprUnary.Op.CARDINALITY.make(o, b); :}; +NumUnopExprB ::= SUM:o NumUnopExprB:b {: RESULT=ExprUnary.Op.CAST2SIGINT.make(o, ExprUnary.Op.CAST2INT.make(o, b)); :}; +//[AM]: INT->SIGINT +NumUnopExprB ::= INT:o NumUnopExprB:b {: RESULT=ExprUnary.Op.CAST2SIGINT.make(o, ExprUnary.Op.CAST2INT.make(o, b)); :}; + +OverrideExprA ::= IntersectExprA:b {: RESULT=b; :}; +OverrideExprA ::= OverrideExprB:a PLUSPLUS:o Bind:b {: RESULT=ExprBinary.Op.PLUSPLUS.make(o, null, a, b); :}; +OverrideExprB ::= IntersectExprB:b {: RESULT=b; :}; +OverrideExprB ::= OverrideExprB:a PLUSPLUS:o IntersectExprB:b {: RESULT=ExprBinary.Op.PLUSPLUS.make(o, null, a, b); :}; + +IntersectExprA ::= RelationExprA:b {: RESULT=b; :}; +IntersectExprA ::= IntersectExprB:a AMPERSAND:o Bind:b {: RESULT=ExprBinary.Op.INTERSECT.make(o, null, a, b); :}; +IntersectExprB ::= RelationExprB:b {: RESULT=b; :}; +IntersectExprB ::= IntersectExprB:a AMPERSAND:o RelationExprB:b {: RESULT=ExprBinary.Op.INTERSECT.make(o, null, a, b); :}; + +RelOp ::= ARROW:o {: RESULT=new Pair(o, ExprBinary.Op.ARROW ); :}; +RelOp ::= ANY_ARROW_SOME:o {: RESULT=new Pair(o, ExprBinary.Op.ANY_ARROW_SOME ); :}; +RelOp ::= ANY_ARROW_ONE:o {: RESULT=new Pair(o, ExprBinary.Op.ANY_ARROW_ONE ); :}; +RelOp ::= ANY_ARROW_LONE:o {: RESULT=new Pair(o, ExprBinary.Op.ANY_ARROW_LONE ); :}; +RelOp ::= SOME_ARROW_ANY:o {: RESULT=new Pair(o, ExprBinary.Op.SOME_ARROW_ANY ); :}; +RelOp ::= SOME_ARROW_SOME:o {: RESULT=new Pair(o, ExprBinary.Op.SOME_ARROW_SOME); :}; +RelOp ::= SOME_ARROW_ONE:o {: RESULT=new Pair(o, ExprBinary.Op.SOME_ARROW_ONE ); :}; +RelOp ::= SOME_ARROW_LONE:o {: RESULT=new Pair(o, ExprBinary.Op.SOME_ARROW_LONE); :}; +RelOp ::= ONE_ARROW_ANY:o {: RESULT=new Pair(o, ExprBinary.Op.ONE_ARROW_ANY ); :}; +RelOp ::= ONE_ARROW_SOME:o {: RESULT=new Pair(o, ExprBinary.Op.ONE_ARROW_SOME ); :}; +RelOp ::= ONE_ARROW_ONE:o {: RESULT=new Pair(o, ExprBinary.Op.ONE_ARROW_ONE ); :}; +RelOp ::= ONE_ARROW_LONE:o {: RESULT=new Pair(o, ExprBinary.Op.ONE_ARROW_LONE ); :}; +RelOp ::= LONE_ARROW_ANY:o {: RESULT=new Pair(o, ExprBinary.Op.LONE_ARROW_ANY ); :}; +RelOp ::= LONE_ARROW_SOME:o {: RESULT=new Pair(o, ExprBinary.Op.LONE_ARROW_SOME); :}; +RelOp ::= LONE_ARROW_ONE:o {: RESULT=new Pair(o, ExprBinary.Op.LONE_ARROW_ONE ); :}; +RelOp ::= LONE_ARROW_LONE:o {: RESULT=new Pair(o, ExprBinary.Op.LONE_ARROW_LONE); :}; + +RelationExprA ::= DomainExprA:a {: RESULT=a; :}; +RelationExprA ::= DomainExprB:a RelOp:o Bind:b {: RESULT=o.b.make(o.a, null, a, b); :}; +RelationExprB ::= DomainExprB:a {: RESULT=a; :}; +RelationExprB ::= DomainExprB:a RelOp:o RelationExprB:b {: RESULT=o.b.make(o.a, null, a, b); :}; + +DomainExprA ::= RangeExprA:b {: RESULT=b; :}; +DomainExprA ::= DomainExprB:a DOMAIN:o Bind:b {: RESULT=ExprBinary.Op.DOMAIN.make(o, null, a, b); :}; +DomainExprB ::= RangeExprB:b {: RESULT=b; :}; +DomainExprB ::= DomainExprB:a DOMAIN:o RangeExprB:b {: RESULT=ExprBinary.Op.DOMAIN.make(o, null, a, b); :}; + +RangeExprA ::= BracketExprA:b {: RESULT=b; :}; +RangeExprA ::= RangeExprB:a RANGE:o Bind:b {: RESULT=ExprBinary.Op.RANGE.make(o, null, a, b); :}; +RangeExprB ::= BracketExprB:b {: RESULT=b; :}; +RangeExprB ::= RangeExprB:a RANGE:o BracketExprB:b {: RESULT=ExprBinary.Op.RANGE.make(o, null, a, b); :}; + +BracketExprA ::= DotExprA:b {: RESULT=b; :}; +BracketExprB ::= DotExprB:b {: RESULT=b; :}; +BracketExprB ::= BracketExprB:a LBRACKET Exprs:b RBRACKET:c {: Expr aa=a; for(Expr bb:b) aa=t(aa.span().merge(bb.span()), c, bb, aa, c); RESULT=aa; :}; +BracketExprB ::= DISJ:a LBRACKET Exprs:b RBRACKET:c {: Expr aa=ExprVar.make(a, "disj"); for(Expr bb:b) aa=t(aa.span().merge(bb.span()), c, bb, aa, c); RESULT=aa; :}; +BracketExprB ::= TOTALORDER:a LBRACKET Exprs:b RBRACKET:c {: Expr aa=ExprVar.make(a, "pred/totalOrder"); for(Expr bb:b) aa=t(aa.span().merge(bb.span()), c, bb, aa, c); RESULT=aa; :}; +//[AM]: INT->SIGINT +BracketExprB ::= INT:a LBRACKET Exprs:b RBRACKET:c {: Expr aa=ExprVar.make(a, "int"); for(Expr bb:b) aa=t(aa.span().merge(bb.span()), c, bb, aa, c); RESULT=ExprUnary.Op.CAST2SIGINT.make(a, aa); :}; +BracketExprB ::= SUM:a LBRACKET Exprs:b RBRACKET:c {: Expr aa=ExprVar.make(a, "int"); for(Expr bb:b) aa=t(aa.span().merge(bb.span()), c, bb, aa, c); RESULT=ExprUnary.Op.CAST2SIGINT.make(a, aa); :}; + +DotExprA ::= UnopExprA:b {: RESULT=b; :}; +DotExprA ::= BracketExprB:a DOT:o Bind:b {: RESULT=t(o, null, a, b, null); :}; +DotExprB ::= UnopExprB:b {: RESULT=b; :}; +DotExprB ::= BracketExprB:a DOT:o UnopExprB:b {: RESULT=t(o, null, a, b, null); :}; +DotExprB ::= BracketExprB:a DOT:o DISJ:b {: RESULT=t(o, null, a, ExprVar.make(b, "disj"), null); :}; +DotExprB ::= BracketExprB:a DOT:o TOTALORDER:b {: RESULT=t(o, null, a, ExprVar.make(b, "pred/totalOrder"), null); :}; +//[AM]: INT->SIGINT +DotExprB ::= BracketExprB:a DOT:o INT {: RESULT=ExprUnary.Op.CAST2SIGINT.make(o, ExprUnary.Op.CAST2INT.make(o, a)); :}; +DotExprB ::= BracketExprB:a DOT:o SUM {: RESULT=ExprUnary.Op.CAST2SIGINT.make(o, ExprUnary.Op.CAST2INT.make(o, a)); :}; + +UnopExprA ::= TILDE:o Bind:b {: RESULT=ExprUnary.Op.TRANSPOSE.make(o,b); :}; +UnopExprA ::= STAR:o Bind:b {: RESULT=ExprUnary.Op.RCLOSURE .make(o,b); :}; +UnopExprA ::= CARET:o Bind:b {: RESULT=ExprUnary.Op.CLOSURE .make(o,b); :}; +UnopExprA ::= TILDE:o UnopExprA:b {: RESULT=ExprUnary.Op.TRANSPOSE.make(o,b); :}; +UnopExprA ::= STAR:o UnopExprA:b {: RESULT=ExprUnary.Op.RCLOSURE .make(o,b); :}; +UnopExprA ::= CARET:o UnopExprA:b {: RESULT=ExprUnary.Op.CLOSURE .make(o,b); :}; +UnopExprB ::= BaseExpr:b {: RESULT=b; :}; +UnopExprB ::= TILDE:o UnopExprB:b {: RESULT=ExprUnary.Op.TRANSPOSE.make(o,b); :}; +UnopExprB ::= STAR:o UnopExprB:b {: RESULT=ExprUnary.Op.RCLOSURE .make(o,b); :}; +UnopExprB ::= CARET:o UnopExprB:b {: RESULT=ExprUnary.Op.CLOSURE .make(o,b); :}; + +BaseExpr ::= NUMBER:x {: RESULT = x; :}; +BaseExpr ::= STR:x {: RESULT = x; :}; +BaseExpr ::= IDEN:o {: RESULT = ExprVar.make(o, "iden"); :}; +BaseExpr ::= THIS:o {: RESULT = ExprVar.make(o, "this"); :}; +BaseExpr ::= INTMIN:o {: RESULT = ExprConstant.Op.MIN.make(o, 0); :}; +BaseExpr ::= INTMAX:o {: RESULT = ExprConstant.Op.MAX.make(o, 0); :}; +BaseExpr ::= INTNEXT:o {: RESULT = ExprConstant.Op.NEXT.make(o, 0); :}; +BaseExpr ::= LPAREN Expr:x RPAREN {: RESULT = x; :}; +BaseExpr ::= SigRef:x {: RESULT = x; :}; +BaseExpr ::= AT:o Name:x {: nod(x); RESULT = ExprVar.make(o.merge(x.pos), "@"+x.label); :}; +BaseExpr ::= Super:x {: RESULT = x; :}; +BaseExpr ::= LBRACE:o Declz:a SuperOrBar:b RBRACE:c {: RESULT = ExprQt.Op.COMPREHENSION.make(o, c, a, b); :}; +BaseExpr ::= LBRACE:o Declz:a RBRACE:c {: RESULT = ExprQt.Op.COMPREHENSION.make(o, c, a, ExprConstant.TRUE); :}; + +//============================================================================= diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/edu/mit/csail/sdg/alloy4compiler/parser/Alloy.lex b/Source/eu.modelwriter.alloyanalyzer/bin/edu/mit/csail/sdg/alloy4compiler/parser/Alloy.lex new file mode 100644 index 00000000..b6f2af29 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/edu/mit/csail/sdg/alloy4compiler/parser/Alloy.lex @@ -0,0 +1,218 @@ +// Alloy Analyzer 4 -- Copyright (c) 2006-2008, Felix Chang +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files +// (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, +// merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF +// OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +package edu.mit.csail.sdg.alloy4compiler.parser; + +import edu.mit.csail.sdg.alloy4.Err; +import edu.mit.csail.sdg.alloy4.ErrorSyntax; +import edu.mit.csail.sdg.alloy4.Pos; +import edu.mit.csail.sdg.alloy4.Version; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprConstant; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprVar; +import java.util.List; +import java_cup.runtime.*; + +/** Autogenerated by JFlex 1.4.1 */ + +%% + +// There are 3 sets of "special tokens" that the lexer will not output. +// But the Parser expects them. +// So a special Filter class is written that sits between Lexer and Parser. +// The Filter class observes the stream of tokens, and intelligently +// merges or changes some primitive tokens into special tokens. +// For more details, refer to the main documentation. +// +// But, very briefly, here are the 3 groups: +// +// (1) The lexer will generate only ALL, NO, LONE, ONE, SUM, SOME. +// It will not output ALL2, NO2, LONE2, ONE2, SUM2, SOME2. +// (The Filter class will change some ONE into ONE2, etc) +// +// (2) The lexer won't output NOTEQUALS, NOTIN, NOTLT, NOTLTE, NOTGT, NOTGTE. +// Instead it outputs them as separate tokens (eg. "NOT" "EQUALS"). +// (The Filter class is used to merge them into a single "NOTEQUALS" token) +// +// (3) The lexer willn't output the 15 special arrows (eg. ONE_ARROW_ONE) +// Instead it outputs them as separate tokens (eg. "ONE", "ARROW", "ONE") +// (The Filter class is used to merge them into a single "ONE_ARROW_ONE" token) + +%class CompLexer // The ordering of these directives is important +%cupsym CompSym +%cup +%eofval{ + return new Symbol(CompSym.EOF, alloy_here(" "), alloy_here(" ")); +%eofval} +%public +%final +%unicode +%line +%column +%pack + +%{ + public String alloy_filename=""; + public int alloy_lineoffset=0; // If not zero, it is added to the current LINE NUMBER + public List alloy_seenDollar; + public CompModule alloy_module; + private final Pos alloy_here(String txt) { + return new Pos(alloy_filename,yycolumn+1,yyline+1+alloy_lineoffset,yycolumn+txt.length(),yyline+1); + } + private final Symbol alloy_sym(String txt, int type) { + Pos p = alloy_here(txt); return new Symbol(type, p, p); + } + private final Symbol alloy_string(String txt) throws Err { + Pos p = alloy_here(txt); + if (!Version.experimental) throw new ErrorSyntax(p, "String literal is not currently supported."); + StringBuilder sb = new StringBuilder(txt.length()); + for(int i=0; i=txt.length()) throw new ErrorSyntax(p, "String literal cannot end with a single \\"); + c = txt.charAt(i); + if (c=='n') c='\n'; else if (c!='\'' && c!='\"' && c!='\\') throw new ErrorSyntax(p, "String literal currenty only supports\nfour escape sequences: \\\\, \\n, \\\', and \\\""); + } + sb.append(c); + } + txt = sb.toString(); + if (txt.length()==2) throw new ErrorSyntax(p, "Empty string is not allowed; try rewriting your model to use an empty set instead."); + return new Symbol(CompSym.STR, p, ExprConstant.Op.STRING.make(p, txt)); + } + private final Symbol alloy_id(String txt) throws Err { + Pos p=alloy_here(txt); + if (alloy_seenDollar.size()==0 && txt.indexOf('$')>=0) alloy_seenDollar.add(null); + return new Symbol(CompSym.ID, p, ExprVar.make(p,txt)); + } + private final Symbol alloy_num(String txt) throws Err { + Pos p=alloy_here(txt); + int n=0; + try { + n=Integer.parseInt(txt); + } catch(NumberFormatException ex) { + throw new ErrorSyntax(p, "The number "+txt+" is too large to be stored in a Java integer"); + } + return new Symbol(CompSym.NUMBER, p, ExprConstant.Op.NUMBER.make(p, n)); + } +%} + +%% + +"!" { return alloy_sym(yytext(), CompSym.NOT );} +"#" { return alloy_sym(yytext(), CompSym.HASH );} +"&&" { return alloy_sym(yytext(), CompSym.AND );} +"&" { return alloy_sym(yytext(), CompSym.AMPERSAND );} +"(" { return alloy_sym(yytext(), CompSym.LPAREN );} +")" { return alloy_sym(yytext(), CompSym.RPAREN );} +"*" { return alloy_sym(yytext(), CompSym.STAR );} +"++" { return alloy_sym(yytext(), CompSym.PLUSPLUS );} +"+" { return alloy_sym(yytext(), CompSym.PLUS );} +"," { return alloy_sym(yytext(), CompSym.COMMA );} +"->" { return alloy_sym(yytext(), CompSym.ARROW );} +"-" { return alloy_sym(yytext(), CompSym.MINUS );} +"." { return alloy_sym(yytext(), CompSym.DOT );} +"/" { return alloy_sym(yytext(), CompSym.SLASH );} +"::" { return alloy_sym(yytext(), CompSym.DOT );} +":>" { return alloy_sym(yytext(), CompSym.RANGE );} +":" { return alloy_sym(yytext(), CompSym.COLON );} +"<=>" { return alloy_sym(yytext(), CompSym.IFF );} +"<=" { return alloy_sym(yytext(), CompSym.LTE );} +"<:" { return alloy_sym(yytext(), CompSym.DOMAIN );} +"<<" { return alloy_sym(yytext(), CompSym.SHL );} +"<" { return alloy_sym(yytext(), CompSym.LT );} +"=<" { return alloy_sym(yytext(), CompSym.LTE );} +"=>" { return alloy_sym(yytext(), CompSym.IMPLIES );} +"=" { return alloy_sym(yytext(), CompSym.EQUALS );} +">>>" { return alloy_sym(yytext(), CompSym.SHR );} +">>" { return alloy_sym(yytext(), CompSym.SHA );} +">=" { return alloy_sym(yytext(), CompSym.GTE );} +">" { return alloy_sym(yytext(), CompSym.GT );} +"@" { return alloy_sym(yytext(), CompSym.AT );} +"[" { return alloy_sym(yytext(), CompSym.LBRACKET );} +"]" { return alloy_sym(yytext(), CompSym.RBRACKET );} +"^" { return alloy_sym(yytext(), CompSym.CARET );} +"{" { return alloy_sym(yytext(), CompSym.LBRACE );} +"||" { return alloy_sym(yytext(), CompSym.OR );} +"|" { return alloy_sym(yytext(), CompSym.BAR );} +"}" { return alloy_sym(yytext(), CompSym.RBRACE );} +"~" { return alloy_sym(yytext(), CompSym.TILDE );} +"abstract" { return alloy_sym(yytext(), CompSym.ABSTRACT );} +"all" { return alloy_sym(yytext(), CompSym.ALL );} +"and" { return alloy_sym(yytext(), CompSym.AND );} +"assert" { return alloy_sym(yytext(), CompSym.ASSERT );} +"as" { return alloy_sym(yytext(), CompSym.AS );} +"but" { return alloy_sym(yytext(), CompSym.BUT );} +"check" { return alloy_sym(yytext(), CompSym.CHECK );} +"disjoint" { return alloy_sym(yytext(), CompSym.DISJ );} +"disj" { return alloy_sym(yytext(), CompSym.DISJ );} +"else" { return alloy_sym(yytext(), CompSym.ELSE );} +"enum" { return alloy_sym(yytext(), CompSym.ENUM );} +"exactly" { return alloy_sym(yytext(), CompSym.EXACTLY );} +"exhaustive" { return alloy_sym(yytext(), CompSym.EXH );} +"exh" { return alloy_sym(yytext(), CompSym.EXH );} +"expect" { return alloy_sym(yytext(), CompSym.EXPECT );} +"extends" { return alloy_sym(yytext(), CompSym.EXTENDS );} +"fact" { return alloy_sym(yytext(), CompSym.FACT );} +"for" { return alloy_sym(yytext(), CompSym.FOR );} +"fun" { return alloy_sym(yytext(), CompSym.FUN );} +"iden" { return alloy_sym(yytext(), CompSym.IDEN );} +"iff" { return alloy_sym(yytext(), CompSym.IFF );} +"implies" { return alloy_sym(yytext(), CompSym.IMPLIES );} +"Int" { return alloy_sym(yytext(), CompSym.SIGINT );} +"int" { return alloy_sym(yytext(), CompSym.INT );} +"in" { return alloy_sym(yytext(), CompSym.IN );} +"let" { return alloy_sym(yytext(), CompSym.LET );} +"lone" { return alloy_sym(yytext(), CompSym.LONE );} +"module" { return alloy_sym(yytext(), CompSym.MODULE );} +"none" { return alloy_sym(yytext(), CompSym.NONE );} +"not" { return alloy_sym(yytext(), CompSym.NOT );} +"no" { return alloy_sym(yytext(), CompSym.NO );} +"one" { return alloy_sym(yytext(), CompSym.ONE );} +"open" { return alloy_sym(yytext(), CompSym.OPEN );} +"or" { return alloy_sym(yytext(), CompSym.OR );} +"partition" { return alloy_sym(yytext(), CompSym.PART );} +"part" { return alloy_sym(yytext(), CompSym.PART );} +"pred" { return alloy_sym(yytext(), CompSym.PRED );} +"private" { return alloy_sym(yytext(), CompSym.PRIVATE );} +"run" { return alloy_sym(yytext(), CompSym.RUN );} +"seq" { return alloy_sym(yytext(), CompSym.SEQ );} +"set" { return alloy_sym(yytext(), CompSym.SET );} +"sig" { return alloy_sym(yytext(), CompSym.SIG );} +"some" { return alloy_sym(yytext(), CompSym.SOME );} +"String" { return alloy_sym(yytext(), CompSym.STRING );} +"sum" { return alloy_sym(yytext(), CompSym.SUM );} +"this" { return alloy_sym(yytext(), CompSym.THIS );} +"univ" { return alloy_sym(yytext(), CompSym.UNIV );} + +[\"] ([^\\\"] | ("\\" .))* [\"] [\$0-9a-zA-Z_\'\"] [\$0-9a-zA-Z_\'\"]* { throw new ErrorSyntax(alloy_here(yytext()),"String literal cannot be followed by a legal identifier character."); } +[\"] ([^\\\"] | ("\\" .))* [\"] { return alloy_string(yytext()); } +[\"] ([^\\\"] | ("\\" .))* { throw new ErrorSyntax(alloy_here(yytext()),"String literal is missing its closing \" character"); } +[0-9][0-9]*[\$a-zA-Z_\'\"][\$0-9a-zA-Z_\'\"]* { throw new ErrorSyntax(alloy_here(yytext()),"Name cannot start with a number."); } +[0-9][0-9]* { return alloy_num (yytext()); } +[:jletter:][[:jletterdigit:]\'\"]* { return alloy_id (yytext()); } +//[\$a-zA-Z][\$0-9a-zA-Z_\'\"]* { return alloy_id (yytext()); } + +"/**" ~"*/" { } + +"/*" ~"*/" { } + +("//"|"--") [^\r\n]* [\r\n] { } + +("//"|"--") [^\r\n]* { } // This rule is shorter than the previous rule, + // so it will only apply if the final line of a file is missing the \n or \r character. + +[ \t\f\r\n] { } + +. { throw new ErrorSyntax(alloy_here(" "), "Syntax error at the "+yytext()+" character."); } diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/edu/mit/csail/sdg/alloy4compiler/parser/generate-lexer.sh b/Source/eu.modelwriter.alloyanalyzer/bin/edu/mit/csail/sdg/alloy4compiler/parser/generate-lexer.sh new file mode 100644 index 00000000..de50c662 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/edu/mit/csail/sdg/alloy4compiler/parser/generate-lexer.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +java -cp $LIB_SDG/jars-external/JFlex.jar JFlex.Main --nobak -d . Alloy.lex + +sed -i 's/public java_cup.runtime.Symbol next_token() throws java.io.IOException/public java_cup.runtime.Symbol next_token() throws java.io.IOException, Err/' CompLexer.java + diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/edu/mit/csail/sdg/alloy4compiler/parser/generate-parser.sh b/Source/eu.modelwriter.alloyanalyzer/bin/edu/mit/csail/sdg/alloy4compiler/parser/generate-parser.sh new file mode 100644 index 00000000..f27fddeb --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/edu/mit/csail/sdg/alloy4compiler/parser/generate-parser.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +java -cp $LIB_SDG/jars-external/java-cup-11a.jar java_cup.Main \ + -package edu.mit.csail.sdg.alloy4compiler.parser \ + -parser CompParser \ + -progress -time -compact_red \ + -symbols CompSym < Alloy.cup \ No newline at end of file diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/edu/mit/csail/sdg/alloy4compiler/parser/generate-parsing-trace-from-log.sh b/Source/eu.modelwriter.alloyanalyzer/bin/edu/mit/csail/sdg/alloy4compiler/parser/generate-parsing-trace-from-log.sh new file mode 100644 index 00000000..4c640e8d --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/edu/mit/csail/sdg/alloy4compiler/parser/generate-parsing-trace-from-log.sh @@ -0,0 +1,48 @@ +#!/bin/bash + +## ---------------------------------------------------------------------- +## +## This script takes a parsing log and from it generates a human readable +## parsing trace, i.g. a sequence of shifts and reduces that took place. +## +## To get the parsing log just set the "debug" environment variable to +## "yes" before runngin Alloy (or add "-Ddebug=yes" to the list of JVM +## arguments) +## +## This script must be invoked from exactly the folder where it resides +## because it needs to find the CompParser.java and CompSym.java files +## in the same folder. +## +## ---------------------------------------------------------------------- + +logFile=$1 + +if [[ -z $logFile ]] +then + echo "usage: $0 " + exit +fi + +if [[ ! -f $logFile ]] +then + echo "file $logFile doesn't exist" + exit +fi + +while read line +do + if [[ ! -z $(echo $line | grep "^reduce ") ]] + then + act=$(echo $line | sed 's/reduce //') + echo "reduce" + grep "case $act:" CompParser.java + elif [[ ! -z $(echo $line | grep "^shift ") ]] + then + sym=$(echo $line | sed 's/shift //') + sgn=$(grep " = $sym;" CompSym.java | sed 's/ public static final int //' | sed 's/ = '$sym';//') + echo "shift" + echo " $sgn" + else + echo $line + fi +done < $logFile \ No newline at end of file diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/edu/mit/csail/sdg/alloy4compiler/parser/package.html b/Source/eu.modelwriter.alloyanalyzer/bin/edu/mit/csail/sdg/alloy4compiler/parser/package.html new file mode 100644 index 00000000..cbf480d3 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/edu/mit/csail/sdg/alloy4compiler/parser/package.html @@ -0,0 +1,5 @@ + + +This package contains the compiler + + diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/edu/mit/csail/sdg/alloy4compiler/sim/package.html b/Source/eu.modelwriter.alloyanalyzer/bin/edu/mit/csail/sdg/alloy4compiler/sim/package.html new file mode 100644 index 00000000..e717547f --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/edu/mit/csail/sdg/alloy4compiler/sim/package.html @@ -0,0 +1,5 @@ + + +This package contains a pure-Java evaluator/simulator for Alloy4 instances. + + diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/edu/mit/csail/sdg/alloy4compiler/translator/instance.txt b/Source/eu.modelwriter.alloyanalyzer/bin/edu/mit/csail/sdg/alloy4compiler/translator/instance.txt new file mode 100644 index 00000000..dda19365 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/edu/mit/csail/sdg/alloy4compiler/translator/instance.txt @@ -0,0 +1,26 @@ +WHOLEFILE = exactly 1 INSTANCE + 0 or more SOURCE + +INSTANCE = 0 or more PRIMSIG + 0 or more SUBSETSIG + 0 or more FIELD + 0 or more SKOLEM + +PRIMSIG = 0 or more ATOM + +SUBSETSIG = 0 or more ATOM + 1 or more TYPE + +FIELD = 0 or more TUPLE + 1 or more TYPES + +SKOLEM = 0 or more TUPLE + 1 or more TYPES + +TUPLE = 1 or more ATOM +ATOM = + +TYPES = 1 or more TYPE +TYPE = + +SOURCE = diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/edu/mit/csail/sdg/alloy4compiler/translator/package.html b/Source/eu.modelwriter.alloyanalyzer/bin/edu/mit/csail/sdg/alloy4compiler/translator/package.html new file mode 100644 index 00000000..d1ddf4a8 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/edu/mit/csail/sdg/alloy4compiler/translator/package.html @@ -0,0 +1,5 @@ + + +This package contains the translator from Alloy4 to CNF (using kodkod). + + diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/edu/mit/csail/sdg/alloy4graph/package.html b/Source/eu.modelwriter.alloyanalyzer/bin/edu/mit/csail/sdg/alloy4graph/package.html new file mode 100644 index 00000000..da710d0f --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/edu/mit/csail/sdg/alloy4graph/package.html @@ -0,0 +1,5 @@ + + +This package performs graph layout. + + diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/edu/mit/csail/sdg/alloy4viz/package.html b/Source/eu.modelwriter.alloyanalyzer/bin/edu/mit/csail/sdg/alloy4viz/package.html new file mode 100644 index 00000000..dd708ff9 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/edu/mit/csail/sdg/alloy4viz/package.html @@ -0,0 +1,5 @@ + + +This package displays Alloy4 instances. + + diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/edu/mit/csail/sdg/alloy4whole/package.html b/Source/eu.modelwriter.alloyanalyzer/bin/edu/mit/csail/sdg/alloy4whole/package.html new file mode 100644 index 00000000..0f604622 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/edu/mit/csail/sdg/alloy4whole/package.html @@ -0,0 +1,6 @@ + + +This package contains a simple GUI client, +as well as several examples on using the API. + + diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/help/Nav.html b/Source/eu.modelwriter.alloyanalyzer/bin/help/Nav.html new file mode 100644 index 00000000..918c1039 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/help/Nav.html @@ -0,0 +1,44 @@ + + +The Alloy Analyzer + + + + + + +

Quit

+ +

1. Running the Alloy Analyzer + +

2. The Alloy Analyzer GUI + +

3. Performing +Analyses on Alloy Models + +

3.1 Determining the Module Path + +

3.1.1 Built-in Utility Modules + +

3.2 Visualizing the Result + +

3.2.1 The Viz View + +

3.2.2 The Tree View + +

3.2.3 The XML View + +

3.3 Skolemization Relations + +

4. New Syntactic Features in Alloy 4 + +

4.1 sequence of atoms + +

4.2 private namespace + + + diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/help/a4.html b/Source/eu.modelwriter.alloyanalyzer/bin/help/a4.html new file mode 100644 index 00000000..309faf5b --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/help/a4.html @@ -0,0 +1,347 @@ + + +New Syntactic Features in Alloy 4 + + + + + + +

New Syntactic Features in Alloy 4

+ +The syntax of Alloy 4 differs very slightly from the syntax of Alloy 3. Changes +were made for three reasons: to make the syntax more uniform; to add some new +features for greater convenience; to simplify the grammar to allow faster +parsing and to make it easier for others to implement tools for Alloy. The +grammar is now LALR(1), and compilation is instantaneous for all but the +largest models. + +

+ +The changes are explained in detail below. For each change, the rationale is +explained, and short comments highlight the small changes that users are likely +to need to make to Alloy 3 models. We expect that, for most users, the only +changes needed will be replacing round by square brackets in invocations, and +adding aliases for imported modules. + +

+ +1. To cast between integers and Int atoms, use Int[ ] and int[ ] + +

+ +

Change: + To cast from integer to an Int atom, + you must use the new "Int[ ]" function.
+ Likewise, to cast from Int to integer, + you should use the new "int[ ]" function.

+ +

Rationale: + This simplifies the grammar by using the function invocation syntax + to do type casts.

+ +

Impact: + To update an Alloy3 model, you need to replace sum X and int X with int[X], and replace Int X with Int[X].

+ + + + + + + + +
Alloy 3    int SomeIntegerSet    Int 2
Alloy 4    int[SomeIntegerSet]    Int[2]
+
+ +2. The ":" symbol can only be used to declare a variable or field. + +
+

Change: To say an expression has + a particular multiplicity, you must now use the "in" operator + rather than the ":" operator.

+ +

Rationale: + The ":" symbol in Alloy 3 has two meanings.
+ The first meaning is to introduce a new name.
+ For example: "some a:A | a!=b".
+ The second meaning is to say that an expression + has a particular multiplicity.
+ For example: "bank.accounts : Person -> one Account".
+ The second usage intuitively fits better with the existing meaning of + the "in" keyword. +

+ +

Impact: Inside a formula, the ":" operator must be changed + to the "in" operator. +

+ +

+ Alloy 3:    bank.accounts : Person -> one Account
+ Alloy 4:    bank.accounts in Person -> one Account +

+
+ +3. if-then-else is now written as "condition=>x else y" + +
+ In Alloy 3, if-then-else formulas are written as "condition=>formula1,formula2" + where as if-then-else expressions are written as "if condition then x else y". +

In Alloy 4, both forms are now replaced by "condition=>x else y". + + + + + + + + + +
Alloy 3 condition
=> formula
 condition
=> formula1,
formula2
 if condition
then expression1
else expression2
Alloy 4 condition
=> formula
 condition
=> formula1
else formula2
 condition
=> expression1
else expression2
+

+ +4. Function/predicate calls must use the + same operators [ ] and . as relational joins. + +
+ To invoke f(a,b), you must write it as f[a,b], + f[a][b], a.f[b], + or b.(a.f) +

+ To invoke f(a), you must write it as f[a] or a.f +

+ To invoke f(), you must write it as f[ ] or simply f +

+ In particular, note that + a.add[b].sub[c] is equivalent to sub[add[a,b],c] +

+ +
+ Likewise, a function or predicate can be declared using [ ]: +
    pred contains [ m:Map, k:Key, v:Value ] {...} +

Furthermore, if the list of arguments is empty, the [ ] can be omitted: +
    pred acyclic {...} +

Finally, the first argument can be declared using the receiver syntax: +
    pred List.contains [ e:Element ] {...} +
is internally converted into +
    pred contains [ this:List, e:Element ] {...} +
+

+ +5. Grammar for int expressions, set/relation expressions, and formulas + are unified. + +
+ This means some expressions legal in Alloy 3 may require + additional parentheses for it to parse. +

+ Operator Precedence (from low to high)

+   let    all a:X|F   no a:X|F   some a:X|F   lone a:X|F   one a:x|F   sum a:x|F
+   ||
+   <=>
+   =>     => else
+   &&
+   !
+   in     =        <        >       <=      >=      !in   !=   !<   !>  !<=  !>=
+   no X   some X   lone X   one X   set X   seq X
+   <<     >>       >>>
+   +      -
+   #X
+   ++
+   &
+   ->
+   <:
+   :>
+   []
+   .
+   ~    *     ^
+   
+ +6. You can no longer set a separate scope on the number of Int atoms. + +
+ Its scope is always exactly equal to the number of possible integers + corresponding to the current bitwidth (default is 4). +

+ To set the bitwidth, use the "int" keyword in a run or check command. +

+ For example, if you write "check MyAssertion for 4 int", + the assertion + will be checked with integer bitwidth + of 4. That means there are exactly 16 Int atoms ranging + from -8 to 7. +

+ +7. You can no longer declare a signature that extends Int, + or declare a signature to be a subset of Int. + +

+ +8. We don't allow "part" and "exh" in declarations any more. + +

Additional Changes

+ +9. Module Search Path: + +
+ When importing a module, Alloy 4 first searches in the installation + directory. + If not found, it will attempt to derive a relative path based on the + current module's name and the name of the module being imported. +

+ For example, if the following model is /Desktop/MyProject/main.als,
+ then we will infer that the "helper" module is + located at /Desktop/MyProject/additonal/helper.als +

+

+   module MyProject/main
+   open MyProject/additional/helper
+   ...
+   
+
+ +10. "module" declaration is now optional. + +
+ If a model is not parametric, you can omit the "module" declaration. +

+ Example 1: +

+ In this example, the first line is require, since it lists + the parameters: +

+ module MyProject/main[T]
+ ... +
+

+ Example 2: +

In this example, the first line is optional. But its presense + or absense will affect where Alloy 4 searches for imported modules. +

+ module MyProject/main
+ open MyProject/library/helper
+ ... +
+ If the module line is specified, then Alloy 4 will infer + that the helper module is located in a subdirectory called + library. +

+ If the module line is omitted, then Alloy 4 assumes the main file + has no path. Thus, the helper module is assumed + to be in the subdirectory MyProject/library. +

+ +11. You can now write "check {...}" and "run {...}" + +
+ Instead of declaring an assertion X and then write check X,
+ you can now combine them by just writing check {some formula}. +

+ Likewise, instead of declaring a predicate X and then write run X,
+ you can now combine them by just writing run {some formula}. +

+ For example:
+   check { A!=B } for 3
+ is equivalent to
+   assert NOTEQUAL { A!=B }
+   check NOTEQUAL for 3 +

+ These are called "anonymous" assertions and predicates.
+ Alternatively, you can prepend an explcit label if you wish.
+ For example: +

+ somelabel: check { A != B } for 3
+ somelabel: run { some a:A, b:B | a=b } for 3 +

+ +12. predicates, functions, and fields can now overload each other. + +
+ That is, you can declare functions, predicates, and fields + with the same name. + When there's an ambiguity, we'll use the following rule to determine + whether each candidate is compatible: +

+ (a) First of all, its value must be relevant to the overall expression. +

+ (b) Furthermore, if it's a predicate or function, then + the type of each parameter must have nonempty intersection with + the type of each argument. +

+ If exactly one function, predicate, or field is compatible, Alloy 4 + will choose it automatically. Otherwise, an ambiguity error will be reported. +

+ +13. When necessary, Alloy4 will add int->Int and Int->int casts automatically. +
+

+ For example, given an atom X, then the relational product X->3 is illegal,
+ since both operands of -> must be set or relation values.
+ Alloy4 knows the only way for this to be legal is to add an int-to-Int cast,
+ so Alloy4 will parse it as if the user wrote X->Int[3] +

+

 

+

+ Likewise, given an Int atom X, then the left shift expression X<<2 is illegal,
+ since both operands of << must be int values.
+ Alloy4 knows the only way for this to be legal is to add an Int-to-int cast,
+ so Alloy4 will parse it as if the user wrote int[X]<<2 +

+

 

+

+ Note: When there are two ways to make an expression legal,
+ by adding either Int-to-int cast or int-to-Int cast,
+ then Alloy4 prefers the Int-to-int cast since it is cheaper. +

+

 

+ For example, given an Int atom X, then the expression X+3 is illegal,
+ since both operands of + must be of the same type.
+ Alloy4 knows there are two ways for this to be legal:
+ 1)   convert X to int[X], and you get the int value representing the sum of X and 3.
+ 2)   convert 3 to Int[3], and you get the union containing two atoms ("X" and "3").
+ Since Alloy4 prefers the Int-to-int cast since it is cheaper,
+ so Alloy4 will parse it as if the user wrote int[X]+3 +

+
+ +14. Alloy4 now has syntax support for "sequence of atoms". +
+ For more information, please click this. +
+ +15. Alloy4 now has syntax support for "private namespace". +
+ Alloy4 allows you to declare a sig, a field, a function, or a predicate as "private" to the module, and not visible from other modules.
+ For more information, please click this. +
+ +16. Alloy4 now supports all the standard operations on int values. +
+ + + + + + + + + + + + + + + + + + + +
addition   a.add[b] (If unambiguous, you can shorten this to be a+b)
subtraction   a.sub[b] (If unambiguous, you can shorten this to be a-b)
multiplication   a.mul[b]  
division   a.div[b]  
remainder   a.rem[b]  
negation   - a  
 
equal   a = b  
not equal   a != b  
less than   a < b  
greater than   a > b  
less than or equal to   a <= b  
greater than or equal to   a >= b  
 
left-shift   a << b  
sign-extended right-shift   a >> b  
zero-extended right-shift   a >>> b  
 
+ Note: The first five operators (add, sub, mul, div, and rem) requires that you add "open util/integer" to your model. +
+ + + diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/help/analyses.html b/Source/eu.modelwriter.alloyanalyzer/bin/help/analyses.html new file mode 100644 index 00000000..5a656ab7 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/help/analyses.html @@ -0,0 +1,94 @@ + + +Performing Analyses on Alloy Models + + + + + + +

Performing Analyses on Alloy Models

+ +

Loading the Model

+ +

When executing commands in an Alloy model, Alloy always uses +the current content in the text editor as the model to compile.
+Therefore, if you made some changes to the model, +you do not have to save them before running the model. +

+ +

Performing an Analysis on Alloy Models

+ +

A run command is used to search for solutions that satisfy the specification and a predicate, +while a check command is used to search for solutions that satisfy the specification but violate an assertion.

+ +

To run either type of analysis, select the command to be run from +the run menu, or click the "run" toolbar button.

+ +

The run menu will display the list of check and run commands present +in the model. You can execute the commands one at a time, +or click "run all" to execute them all.

+ +

The run toolbar button will executes the most recently executed command. +If no command has been executed, it will execute the first command in +the model.

+ +

The analysis will either terminate with a solution or indicate that one cannot be found within the search space specified by the type scopes of the command.
+If a solution is found, it can be displayed by clicking on the blue clickable hyperlink in the message panel.
+Or, if you enable "visualize automatically" in the Options menu, then the solution will be displayed automatically. +

+ +

Troubleshooting

+ +

Here are some of the common errors that may be encountered:

+ +
    + +
  • Higher-order quantification:
    + The declaration for + a variable in a quantified formula is higher-order and cannot be reduced via + skolemization.
    + Solving such formulas will always take + a significant amount of time and memory + unless the scope is very, very small, + since every combination must be enumerated.
    + Thus, the Alloy Analyzer will not attempt to solve such a formula. +

    +

  • + +
  • The module X cannot be found:
    + The Alloy Analyzer cannot + find the desired model to import in an "open" statement.
    + For details + on the module finding mechanism used, see the page + on module paths. +

    +

  • + +
  • No scope specified for top-level type X in command: +
    A top level type is one that has no supertype (other than the implicit universal type univ).
    + All such types must be given a scope for execution, with the following exceptions:
    + 1) The command has no explicit scopes at all -- all top-level types are given an implicit default scope of 3.
    + 2) Type X is defined as "abstract" -- in this case, if all children + of X are scoped, then the scope of X is inferred and need not be set explicitly. +

    +

  • + +
+ +

Things that may affect execution speed

+ +

Various things may affect the speed of analysis. These include:

+
    +
  • The size of the type scopes specified in the command. The difficulty of the analysis increases with scope in a quicker than linear fashion.

  • +
  • The SAT solver used. By default, the SAT4J solver is used.
    + However, other solvers may fare better in specific models.
    + The SAT solver may changed by clicking the Options menu.

  • +
+ + + diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/help/gui.html b/Source/eu.modelwriter.alloyanalyzer/bin/help/gui.html new file mode 100644 index 00000000..a2a4170b --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/help/gui.html @@ -0,0 +1,149 @@ + + +About the Alloy Analyzer Layout + + + + + + +

The Alloy Analyzer Layout

+ +

Here is a screenshot of the main window of the Analyzer:

+ +

+ +

Toolbar

+ +

The main toolbar of the Alloy Analyzer provides +quick access to the most commonly used operations:

+ +

+ +
    + +
  • New +Creates a new text file in the editor. +
  • + +
  • Open +Opens an existing Alloy model in the editor. +
  • + +
  • Reload +For each file currently open in the editor, reload its content from the file system. +
  • + +
  • Save +Saves the currently active model in the editor. +
  • + +
  • Execute +Executes the most recently executed command.
    +Executes the first command from the file if no command has been executed so far. +
  • + +
  • Show +Displays the most recent counterexample or instance. +
  • + +
+ +

Editor Panel and Message Panel

+ +

The user interface consists of the editor panel and the message panel.
+ The relative sizes of panels may be adjusted by clicking and dragging the split bars that separate the panels.

+ +
    +
  • Editor panel: contains a tabbed text editor for modifying Alloy models.
    + It supports tabbing so you can edit multiple text files simultaneously.
    + It also supports error highlighting during model compilation. +
  • +

    +

  • Message panel: displays the results of analysis.
    + Each counterexample and each satisfying instance will have a clickable hyperlink.
    + Clicking on it will launch the Alloy Visualizer to display the counterexample or instance. +
  • +
+ +

+The message panel is also used for general status messages and error messages.
+For example, if a model cannot be compiled, an error message is displayed,
+and the error will be highlighted in the source Alloy model (see the figure below): +

+ +

+ +

Options and Preferences

+ +

The preferences can be set by +clicking the Options menu (see the figure below):

+ +

+ +
    + +
  • SAT Solver: + Alloy4 comes prepackaged with a selection of SAT solvers.
    + By default, the pure Java solver "SAT4J" is chosen since it runs on every platform and operating system.
    + If you require faster performance, you can try one of the native solver such as MiniSat or ZChaff.
    + But if MiniSat or ZChaff crashes due to platform or operating system incompatibility, then change the solver back to SAT4J. +

  • + +
  • Warnings are Fatal: + By default, a model that contains one or more compilation warnings cannot be executed. +

  • + +
  • Maximum Memory to Use: + The amount of memory to allocate for Alloy4; larger and more complicated models require more memory. +

  • + +
  • Message Verbosity: + This controls how verbose the messages will be. +

  • + +
  • Font Size: + This controls the font size in the editor panel and the message panel. +

  • + +
  • Font: + This controls the font in the editor panel and the message panel. +

  • + +
  • Tab Size: + This controls the tab size in the editor panel. +

  • + +
  • Skolem Depth: + This controls the maximum depth of alternating universal-vs-existential
    + quantifier that we will permit when generating a skolem function.
    + If a formula exceeds this depth, we will not generate a skolem function for it. +

  • + +
  • Unsat Core Minimization Strategy: + This controls the strategy used to minimize the unsat core.
    + The fast strategy performs no minimization at all.
    + The medium strategy uses a hybrid algorithm that attempts to reduce the core size.
    + The slow strategy guarantees that, at the logic level, the core is a locally minimum core. +

  • + +
  • Visualize Automatically: + If this option is enabled, after executing any command,
    + the Alloy Analyzer will automatically load the visualizer
    + to visualize the counterexample or instance (if any). +

  • + +
  • Record the Kodkod Input/output: + If this option is enabled, after executing any command,
    + then Alloy Analyzer will record the Kodkod input model generated for that command,
    + as well as the Kodkod solution correspoding to that command. +

  • + +
+ + + diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/help/image/error.gif b/Source/eu.modelwriter.alloyanalyzer/bin/help/image/error.gif new file mode 100644 index 00000000..d37e28b4 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/bin/help/image/error.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/help/image/general.gif b/Source/eu.modelwriter.alloyanalyzer/bin/help/image/general.gif new file mode 100644 index 00000000..52976128 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/bin/help/image/general.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/help/image/pref.gif b/Source/eu.modelwriter.alloyanalyzer/bin/help/image/pref.gif new file mode 100644 index 00000000..eb2fd296 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/bin/help/image/pref.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/help/image/theme.gif b/Source/eu.modelwriter.alloyanalyzer/bin/help/image/theme.gif new file mode 100644 index 00000000..ad9e58d1 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/bin/help/image/theme.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/help/image/toolbar.gif b/Source/eu.modelwriter.alloyanalyzer/bin/help/image/toolbar.gif new file mode 100644 index 00000000..f3cbda1b Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/bin/help/image/toolbar.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/help/image/toolbarviz.gif b/Source/eu.modelwriter.alloyanalyzer/bin/help/image/toolbarviz.gif new file mode 100644 index 00000000..98abc4f5 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/bin/help/image/toolbarviz.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/help/image/tree.gif b/Source/eu.modelwriter.alloyanalyzer/bin/help/image/tree.gif new file mode 100644 index 00000000..40bd56a8 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/bin/help/image/tree.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/help/image/viz.gif b/Source/eu.modelwriter.alloyanalyzer/bin/help/image/viz.gif new file mode 100644 index 00000000..0f27b631 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/bin/help/image/viz.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/help/image/xml.gif b/Source/eu.modelwriter.alloyanalyzer/bin/help/image/xml.gif new file mode 100644 index 00000000..45316860 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/bin/help/image/xml.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/help/index.html b/Source/eu.modelwriter.alloyanalyzer/bin/help/index.html new file mode 100644 index 00000000..bccdfe8c --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/help/index.html @@ -0,0 +1,46 @@ + + +The Alloy Analyzer + + + + + + +

The Alloy Analyzer Quick Guide

+

This user guide is divided into the following sections:

+ +

1. Running the Alloy Analyzer

+ +

2. The Alloy Analyzer GUI

+ +

3. Performing Analyses on Alloy Models +

+

+ +

4. New Syntactic Features in Alloy 4

+ + + + diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/help/path.html b/Source/eu.modelwriter.alloyanalyzer/bin/help/path.html new file mode 100644 index 00000000..cfc1ede0 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/help/path.html @@ -0,0 +1,67 @@ + + +About Module Paths + + + + + + +

About Module Paths

+ +

Alloy models may contain "open" statements, like this one:

+ +
 open util/integer
+ +

The Alloy "open" statement is roughly similar + to the Java "import" + or the C/C++ "include" in that it tells + the tool to look at some source + in another file. +

+ +

+ Alloy will look for util/integer.als in two places: +

+ +

+ First of all, if the module is one of the sample model that comes with Alloy 4, it will load it.
+ This includes all the "util" modules + such as util/ordering and util/boolean, as well as the example models such as examples/algorithms/dijkstra.
+ You can see the list of sample models by clicking on the File menu, then click on "open sample models". +

+ +

+ Failing that, it will infer the location of the file based on the location + of the current model and the name of the current model. +

+ +

+ For example, let's suppose we are analyzing the model "C:\Desktop\main.als" + with the following content: +
+

+    model filesystem/main
+
+    open filesystem/debug as DEBUG
+    open filesystem/library/dirmodel as DIR
+    open filesystem/library/filemodel as FILE
+   
+

+ +

+ The "filesystem/debug" module is on the same level as "filesystem/main", + so we will infer that it is at C:\Desktop\debug.als +

+ +

+ The dirmodel and filemodel are in a subdirectory called "library", + so we will infer that they are at C:\Desktop\library\dirmodel.als + and C:\Desktop\library\filemodel.als +

+ + diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/help/private.html b/Source/eu.modelwriter.alloyanalyzer/bin/help/private.html new file mode 100644 index 00000000..f4e4e4c1 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/help/private.html @@ -0,0 +1,129 @@ + + +Skolemization Relations + + + + + + +

private namespace

+ +

+A new reserved keyword "private" has been added for declaring a sig, fun, pred, field, or an entire imported module +as being "private" to this module (and does not show up in the namespace of other modules that import this module). +

+ +

Example 1: private sig, private field, private function, and private predicate

+ + + + + + + + + + + +
+
+  module moduleA
+
+  open moduleB
+
+
+
+  module moduleB
+
+  sig Person {
+      favorite: Book,
+      knows: set Person,
+      private likes: set Person
+  }
+
+  private sig Book { year: Int }
+
+  fun allBook: set Book { Book }
+
+  private fun union[a, b: set univ]: set univ { a+b }
+
+
+ +

+From moduleA, you can refer to "Person", "favorite", "knows", and "allBook". +
+You cannot refer to "likes" because it is a private field, and you cannot refer +to "union" because it is a private function. +

+ +

+From moduleA, you cannot refer to "Book" since it is a private sig, +
+and you cannot refer to "year" because it is a field in a private sig (and thus +it is automatically private). +

+ +

+The important thing to note here is this is purely a namespace management mechanism, and you may still +be able to access these "hidden" values via other names. For example, if you have Person X, you can access +X.favorite to see the Book atom corresponding to his favorite book, even though you cannot refer to the Book signature +by name. Likewise, you can get every atom in the Book sig by calling the "allBook" function. +

+ +

Example 2: private open

+ + + + + + + + + + + + + + + +
+
+  module moduleA
+
+  open moduleB
+
+
+
+  module moduleB
+
+  private open moduleC
+
+  open moduleD
+
+  sig B { }
+
+
+
+  module moduleC
+
+  sig C { }
+
+
+
+  module moduleD
+
+  sig D { }
+
+
+ +

In moduleB, you can certainly refer to B, C, and D.

+ +

In moduleA, you can refer to B and D, but you cannot refer to C, +since moduleB imported moduleC as a "private import".

+ + diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/help/run.html b/Source/eu.modelwriter.alloyanalyzer/bin/help/run.html new file mode 100644 index 00000000..0bd8f774 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/help/run.html @@ -0,0 +1,32 @@ + + +Running the Alloy Analyzer + + + + + + +

Running the Alloy Analyzer on Mac OS X

+ +

Just open the downloaded alloy4.dmg file (available at alloy.mit.edu), and double click on the Alloy4 icon.
+To keep the application, drag the icon out of the dmg window and place it somewhere in your home directory or on your desktop.

+ +

Running the Alloy Analyzer on other platforms

+ +

Just download the alloy4.jar file (available at alloy.mit.edu) then double-click on the jar file, +or type:

+
 java -jar alloy4.jar
+

+in the console. +

+ +

Please note: +Platform-dependent libraries are currently available only for Mac OS X, Windows (x86), Linux (x86), and FreeBSD (x86).
+On other platforms, you can still run the Alloy Analyzer, but you will need to select the pure Java solver "SAT4J" (through the Options menu). + + diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/help/seq.html b/Source/eu.modelwriter.alloyanalyzer/bin/help/seq.html new file mode 100644 index 00000000..9a14affe --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/help/seq.html @@ -0,0 +1,172 @@ + + +Skolemization Relations + + + + + + +

sequence of atoms

+ +

+A new reserved keyword "seq" has been added for declaring a field as a sequence of atoms.
+In the following example, for each person p, "p.books" is a sequence of Book: +

+ +
+  sig Book { }
+  sig Person {
+      books: seq Book
+  }
+
+ +

+The actual type of a sequence of Book is "Int->Book".
+So if s is a sequence of Book, then the first element is s[0]
+and you can get the set of all elements by writing "univ.s"
+

+ +

+You can also use "seq" in quantifications, like this: +

+ +
some s: seq Book | FORMULA
+ +

+You can also use "seq" in function argument declaration, like this: +

+ +
+  fun getAllElements [s: seq Book] : set Book {
+      univ.s
+  }
+
+ +

+Just like the other multiplicity symbols, when you use "seq" +in a function argument declaration, we do not enforce that +you always call the function/predicate with a well-formed sequence. +So it is only for documentation purpose, to denote s +is a binary relation from Int->Book. +

+ +

+Note: for effifiency, we bound the length of allowed sequences. +You can change this bound by setting the scope on "seq".
+For example, if you want to allow sequences of up to 4 elements, +you write +

+ +
  check SomeAssertion for 4 seq
+
+ +

+To make it easier to manipulate sequences, +we provide a number of helper functions: (these are +defined in the pre-included util/sequniv.als file) +

+ +

#s
+ Return the number of elements in sequence s.
+

+ +

s.elems
+ Return the set of elements in sequence s.
+

+ +

s.first
+ If #s > 0, it returns the first element of s
+ Otherwise, it returns the empty set
+

+ +

s.last
+ If #s > 0, it returns the last element of s
+ Otherwise, it returns the empty set
+

+ +

s.rest
+ If #s > 1, it returns s with its first element removed
+ Otherwise, it returns the empty sequence
+

+ +

s.butLast
+ If #s > 1, it returns s with its last element removed
+ Otherwise, it returns the empty sequence
+

+ +

s.isEmpty
+ It returns true if #s==0.
+

+ +

s.hasDups
+ It returns true if s contains duplicate elements.
+

+ +

s.inds
+ If #s > 0, it returns the set of integers {0 .. (#s)-1}
+ Otherwise, it returns the empty set
+

+ +

s.lastIdx
+ If #s > 0, it returns the integer (#s)-1
+ Otherwise, it returns the empty set
+

+ +

s.afterLastIdx
+ If (#s < the longest allowed sequence length), it returns #s
+ Otherwise, it returns the empty set
+

+ +

s.idxOf [x]
+ If x does not appear in s, it returns the empty set.
+ Otherwise, it returns the first index where x appears in s.
+

+ +

s.lastIdxOf [x]
+ If x does not appear in s, it returns the empty set.
+ Otherwise, it returns the last index where x appears in s.
+

+ +

s.indsOf [x]
+ If x does not appear in s, it returns the empty set.
+ Otherwise, it returns the set of indices where x appears in s.
+

+ +

s.add [x]
+ If (#s < the longest allowed sequence length), it appends x to s.
+ Otherwise, it returns s.
+

+ +

s.setAt [i, x]
+ Precondition: 0 <= i < #s
+ It returns a new sequence where the i-th entry is changed to x.
+

+ +

s.insert [i, x]
+ Precondition: 0 <= i <= #s
+ It returns a new sequence where x is inserted at index i.
+ Note: if #s was already equal to the longest allowed sequence + length, then the last element of s will be removed first.
+

+ +

s.delete [i]
+ Precondition: 0 <= i < #s
+ It returns the result of deleting the element at index i
+

+ +

a.append [b]
+ Returns the result of concatenating sequence a and sequence b
+ (If the resulting sequence is too longer, it will be truncated)
+

+ +

s.subseq [from, to]
+ Precondition: 0 <= from <= to < #s
+ Returns the subsequence between from and to, inclusively.
+

+ + diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/help/skolem.html b/Source/eu.modelwriter.alloyanalyzer/bin/help/skolem.html new file mode 100644 index 00000000..d77a2c24 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/help/skolem.html @@ -0,0 +1,38 @@ + + +Skolemization Relations + + + + + + +

Skolemization Relations

+

Introduction

+

Often times, quantified formulas can be reduced to equivalent formulas without the use of quantifiers.
+This reduction is called skolemization and is based on the introduction of one or more skolem constants or functions
+that capture the constraint of the quantified formula in their values.

+

Consider the following example:

+
sig A { r: lone B }
+sig B {}
+
fact {
+    some x: A | no x.r
+}
+

The "some" formula may be equivalently expressed as:

+
    x' in A && no x'.r 
+

x' is the skolem reation in this case. The existential quantifier "some" is not needed because the analysis will search for the existence of the skolem realtion x'.

+

 

+

Determining the names of skolem relations

+

The Alloy Analyzer automatically generates and assigns names to skolem relations.
+The names can be determined by looking at the solved instance using any of the three (Text, Tree, Viz) views.
+For instance, in the above example, the name $x may have been assigned to the skolem relation x'.

+

 

+

Using skolem relations

+

Skolem relations are displayed in the output like any other relation in the original model. Hence, its visualization may be customized to be made more conspicuous. +

+ + diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/help/treeview.html b/Source/eu.modelwriter.alloyanalyzer/bin/help/treeview.html new file mode 100644 index 00000000..9d19b0dd --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/help/treeview.html @@ -0,0 +1,23 @@ + + +Tree View + + + + + + +

Tree View

+

As implied by its name, the Tree view displays an instance in a tree. +The nodes of the tree may be expanded/collapsed to reveal/hide the values +of relations. A relation node (such as that for a signature or a field) +is expanded to reveal its tuples, and an atom may be expanded to reveal +the value of joining the atom with the fields defined in its correponding +signature.

+

+ + diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/help/util.html b/Source/eu.modelwriter.alloyanalyzer/bin/help/util.html new file mode 100644 index 00000000..1e3153da --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/help/util.html @@ -0,0 +1,72 @@ + + +Alloy Models + + + + + + +

Utility Modules

+ +

+Alloy 4 contains a number of utility modules that provide common +operations on graphs, integers, etc. Here is a list of the modules +and a short description for each module: +

+ +module util/boolean + +
+ Creates a Bool type with two singleton subtypes: True and False. +
+ +module util/graph[node] + +
+ Utilities for common operations and contraints on graphs. +
+ +module util/integer + +
+ Utilities for using integers in Alloy. +
+ +module util/natural + +
+ Utilities for using the set of nonnegative integers (0, 1, 2, ...). +
+ +module util/ordering[element] + +
+ Creates a single linear ordering over the atoms in elem. +
+ +module util/relation + +
+ Utilities for common operations and constraints on binary relations. +
+ +module util/sequniv + +
+ This module models each sequence of elements using a relation.
+ (This module is imported automatically if your model uses the new seq keyword. +
+ +module util/ternary + +
+ Utilities for common operations and constraints on ternary relations. +
+ + + diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/help/viz.html b/Source/eu.modelwriter.alloyanalyzer/bin/help/viz.html new file mode 100644 index 00000000..f7d25cf2 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/help/viz.html @@ -0,0 +1,42 @@ + + +About the Alloy Analyzer Layout + + + + + + +

Visualizer

+ +

The visualizer offers 3 views, which can be selected + in its toolbar at the top (see the figure below).

+ +

+ +

Visualization Modes

+ +
    + +
  • Viz: brings up the graphical view. + The labels, colors, and various other settings can be configured + by clicking the Theme button.
    + +
  • + +
  • Tree: brings up the tree view.
    + +
  • + +
  • XML: shows the instance as an XML document.
    + +
  • + +
+ + + diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/help/vizview.html b/Source/eu.modelwriter.alloyanalyzer/bin/help/vizview.html new file mode 100644 index 00000000..95cfe06d --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/help/vizview.html @@ -0,0 +1,23 @@ + + +Visualization View + + + + + + +

Visualization View

+

The Visualization ("Viz") view displays instances as a graph, + where each node is an atom (member of a signature) and arcs represent the + relations between atoms. The graph may be customized by +clicking on the theme toolbar button.

+

+

+ + + diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/help/xmlview.html b/Source/eu.modelwriter.alloyanalyzer/bin/help/xmlview.html new file mode 100644 index 00000000..d7bb965f --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/help/xmlview.html @@ -0,0 +1,57 @@ + + +XML View + + + + + + +

XML View

+ +

As implied by its name, the XML view displays an instance +in XML format. If you save the XML text, you can load the instance +later from the visualizer.

+ +

Here is a sample XML file:

+ +
+
+<alloy>
+
+<sig name="Name" extends="univ">
+  <atom name="Name$0"/>
+  <atom name="Name$1"/>
+</sig>
+
+<sig name="Date" extends="univ">
+  <atom name="Date$0"/>
+</sig>
+
+<sig name="BirthdayBook" extends="univ">
+  <atom name="BirthdayBook$0"/>
+  <atom name="BirthdayBook$1"/>
+</sig>
+
+<field name="known">
+    <type> <sig name="BirthdayBook"/> <sig name="Name"/> </type>
+    <tuple> <atom name="BirthdayBook$1"/> <atom name="Name$1"/> </tuple>
+</field>
+
+<field name="date">
+    <type> <sig name="BirthdayBook"/> <sig name="Name"/> <sig name="Date"/> </type>
+    <tuple> <atom name="BirthdayBook$1"/> <atom name="Name$1"/> <atom name="Date$0"/> </tuple>
+</field>
+
+</instance>
+
+</alloy>
+
+
+ + + diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/icons/ColorIcons/black.gif b/Source/eu.modelwriter.alloyanalyzer/bin/icons/ColorIcons/black.gif new file mode 100644 index 00000000..bba84da2 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/bin/icons/ColorIcons/black.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/icons/ColorIcons/blue.gif b/Source/eu.modelwriter.alloyanalyzer/bin/icons/ColorIcons/blue.gif new file mode 100644 index 00000000..e6b2a7e9 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/bin/icons/ColorIcons/blue.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/icons/ColorIcons/cadetblue.gif b/Source/eu.modelwriter.alloyanalyzer/bin/icons/ColorIcons/cadetblue.gif new file mode 100644 index 00000000..d01b37fe Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/bin/icons/ColorIcons/cadetblue.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/icons/ColorIcons/chartreuse2.gif b/Source/eu.modelwriter.alloyanalyzer/bin/icons/ColorIcons/chartreuse2.gif new file mode 100644 index 00000000..0502739f Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/bin/icons/ColorIcons/chartreuse2.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/icons/ColorIcons/cornflowerblue.gif b/Source/eu.modelwriter.alloyanalyzer/bin/icons/ColorIcons/cornflowerblue.gif new file mode 100644 index 00000000..64a86a64 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/bin/icons/ColorIcons/cornflowerblue.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/icons/ColorIcons/cyan.gif b/Source/eu.modelwriter.alloyanalyzer/bin/icons/ColorIcons/cyan.gif new file mode 100644 index 00000000..720fb8cd Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/bin/icons/ColorIcons/cyan.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/icons/ColorIcons/darkolivegreen2.gif b/Source/eu.modelwriter.alloyanalyzer/bin/icons/ColorIcons/darkolivegreen2.gif new file mode 100644 index 00000000..827b3f9c Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/bin/icons/ColorIcons/darkolivegreen2.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/icons/ColorIcons/gold.gif b/Source/eu.modelwriter.alloyanalyzer/bin/icons/ColorIcons/gold.gif new file mode 100644 index 00000000..f6dc8ce8 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/bin/icons/ColorIcons/gold.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/icons/ColorIcons/green2.gif b/Source/eu.modelwriter.alloyanalyzer/bin/icons/ColorIcons/green2.gif new file mode 100644 index 00000000..5b689651 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/bin/icons/ColorIcons/green2.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/icons/ColorIcons/lightgoldenrod.gif b/Source/eu.modelwriter.alloyanalyzer/bin/icons/ColorIcons/lightgoldenrod.gif new file mode 100644 index 00000000..27fb805b Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/bin/icons/ColorIcons/lightgoldenrod.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/icons/ColorIcons/lightgray.gif b/Source/eu.modelwriter.alloyanalyzer/bin/icons/ColorIcons/lightgray.gif new file mode 100644 index 00000000..b3f1bfa7 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/bin/icons/ColorIcons/lightgray.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/icons/ColorIcons/limegreen.gif b/Source/eu.modelwriter.alloyanalyzer/bin/icons/ColorIcons/limegreen.gif new file mode 100644 index 00000000..29edc92b Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/bin/icons/ColorIcons/limegreen.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/icons/ColorIcons/magenta.gif b/Source/eu.modelwriter.alloyanalyzer/bin/icons/ColorIcons/magenta.gif new file mode 100644 index 00000000..8d3d29ca Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/bin/icons/ColorIcons/magenta.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/icons/ColorIcons/magic.gif b/Source/eu.modelwriter.alloyanalyzer/bin/icons/ColorIcons/magic.gif new file mode 100644 index 00000000..61598714 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/bin/icons/ColorIcons/magic.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/icons/ColorIcons/palevioletred.gif b/Source/eu.modelwriter.alloyanalyzer/bin/icons/ColorIcons/palevioletred.gif new file mode 100644 index 00000000..1de7a229 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/bin/icons/ColorIcons/palevioletred.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/icons/ColorIcons/red.gif b/Source/eu.modelwriter.alloyanalyzer/bin/icons/ColorIcons/red.gif new file mode 100644 index 00000000..c7604a5a Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/bin/icons/ColorIcons/red.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/icons/ColorIcons/salmon.gif b/Source/eu.modelwriter.alloyanalyzer/bin/icons/ColorIcons/salmon.gif new file mode 100644 index 00000000..1d3dca18 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/bin/icons/ColorIcons/salmon.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/icons/ColorIcons/white.gif b/Source/eu.modelwriter.alloyanalyzer/bin/icons/ColorIcons/white.gif new file mode 100644 index 00000000..02a767d3 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/bin/icons/ColorIcons/white.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/icons/ColorIcons/yellow.gif b/Source/eu.modelwriter.alloyanalyzer/bin/icons/ColorIcons/yellow.gif new file mode 100644 index 00000000..a09f1c23 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/bin/icons/ColorIcons/yellow.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/icons/ShapeIcons/Mcircle.gif b/Source/eu.modelwriter.alloyanalyzer/bin/icons/ShapeIcons/Mcircle.gif new file mode 100644 index 00000000..49a659f8 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/bin/icons/ShapeIcons/Mcircle.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/icons/ShapeIcons/Mdiamond.gif b/Source/eu.modelwriter.alloyanalyzer/bin/icons/ShapeIcons/Mdiamond.gif new file mode 100644 index 00000000..071f9807 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/bin/icons/ShapeIcons/Mdiamond.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/icons/ShapeIcons/Msquare.gif b/Source/eu.modelwriter.alloyanalyzer/bin/icons/ShapeIcons/Msquare.gif new file mode 100644 index 00000000..2db39335 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/bin/icons/ShapeIcons/Msquare.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/icons/ShapeIcons/box.gif b/Source/eu.modelwriter.alloyanalyzer/bin/icons/ShapeIcons/box.gif new file mode 100644 index 00000000..788dee55 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/bin/icons/ShapeIcons/box.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/icons/ShapeIcons/circle.gif b/Source/eu.modelwriter.alloyanalyzer/bin/icons/ShapeIcons/circle.gif new file mode 100644 index 00000000..87fe62e4 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/bin/icons/ShapeIcons/circle.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/icons/ShapeIcons/diamond.gif b/Source/eu.modelwriter.alloyanalyzer/bin/icons/ShapeIcons/diamond.gif new file mode 100644 index 00000000..e0a1df4b Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/bin/icons/ShapeIcons/diamond.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/icons/ShapeIcons/doublecircle.gif b/Source/eu.modelwriter.alloyanalyzer/bin/icons/ShapeIcons/doublecircle.gif new file mode 100644 index 00000000..33156e8c Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/bin/icons/ShapeIcons/doublecircle.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/icons/ShapeIcons/doubleoctagon.gif b/Source/eu.modelwriter.alloyanalyzer/bin/icons/ShapeIcons/doubleoctagon.gif new file mode 100644 index 00000000..064307f3 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/bin/icons/ShapeIcons/doubleoctagon.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/icons/ShapeIcons/egg.gif b/Source/eu.modelwriter.alloyanalyzer/bin/icons/ShapeIcons/egg.gif new file mode 100644 index 00000000..3d49e5a9 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/bin/icons/ShapeIcons/egg.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/icons/ShapeIcons/ellipse.gif b/Source/eu.modelwriter.alloyanalyzer/bin/icons/ShapeIcons/ellipse.gif new file mode 100644 index 00000000..83dc2f41 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/bin/icons/ShapeIcons/ellipse.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/icons/ShapeIcons/hexagon.gif b/Source/eu.modelwriter.alloyanalyzer/bin/icons/ShapeIcons/hexagon.gif new file mode 100644 index 00000000..e85ae033 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/bin/icons/ShapeIcons/hexagon.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/icons/ShapeIcons/house.gif b/Source/eu.modelwriter.alloyanalyzer/bin/icons/ShapeIcons/house.gif new file mode 100644 index 00000000..4812b028 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/bin/icons/ShapeIcons/house.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/icons/ShapeIcons/invhouse.gif b/Source/eu.modelwriter.alloyanalyzer/bin/icons/ShapeIcons/invhouse.gif new file mode 100644 index 00000000..71dc4b83 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/bin/icons/ShapeIcons/invhouse.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/icons/ShapeIcons/invtrapezium.gif b/Source/eu.modelwriter.alloyanalyzer/bin/icons/ShapeIcons/invtrapezium.gif new file mode 100644 index 00000000..7eaf0c04 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/bin/icons/ShapeIcons/invtrapezium.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/icons/ShapeIcons/invtriangle.gif b/Source/eu.modelwriter.alloyanalyzer/bin/icons/ShapeIcons/invtriangle.gif new file mode 100644 index 00000000..c3394467 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/bin/icons/ShapeIcons/invtriangle.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/icons/ShapeIcons/octagon.gif b/Source/eu.modelwriter.alloyanalyzer/bin/icons/ShapeIcons/octagon.gif new file mode 100644 index 00000000..7ec0b4bb Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/bin/icons/ShapeIcons/octagon.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/icons/ShapeIcons/parallelogram.gif b/Source/eu.modelwriter.alloyanalyzer/bin/icons/ShapeIcons/parallelogram.gif new file mode 100644 index 00000000..a4925d03 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/bin/icons/ShapeIcons/parallelogram.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/icons/ShapeIcons/trapezium.gif b/Source/eu.modelwriter.alloyanalyzer/bin/icons/ShapeIcons/trapezium.gif new file mode 100644 index 00000000..2381a684 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/bin/icons/ShapeIcons/trapezium.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/icons/ShapeIcons/triangle.gif b/Source/eu.modelwriter.alloyanalyzer/bin/icons/ShapeIcons/triangle.gif new file mode 100644 index 00000000..730a1b0c Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/bin/icons/ShapeIcons/triangle.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/icons/ShapeIcons/tripleoctagon.gif b/Source/eu.modelwriter.alloyanalyzer/bin/icons/ShapeIcons/tripleoctagon.gif new file mode 100644 index 00000000..1f604228 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/bin/icons/ShapeIcons/tripleoctagon.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/icons/StyleIcons/bold.gif b/Source/eu.modelwriter.alloyanalyzer/bin/icons/StyleIcons/bold.gif new file mode 100644 index 00000000..dda8863b Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/bin/icons/StyleIcons/bold.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/icons/StyleIcons/dashed.gif b/Source/eu.modelwriter.alloyanalyzer/bin/icons/StyleIcons/dashed.gif new file mode 100644 index 00000000..b12004d2 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/bin/icons/StyleIcons/dashed.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/icons/StyleIcons/dotted.gif b/Source/eu.modelwriter.alloyanalyzer/bin/icons/StyleIcons/dotted.gif new file mode 100644 index 00000000..be61201c Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/bin/icons/StyleIcons/dotted.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/icons/StyleIcons/solid.gif b/Source/eu.modelwriter.alloyanalyzer/bin/icons/StyleIcons/solid.gif new file mode 100644 index 00000000..7d837f15 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/bin/icons/StyleIcons/solid.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/images/24_execute.gif b/Source/eu.modelwriter.alloyanalyzer/bin/images/24_execute.gif new file mode 100644 index 00000000..ddd93932 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/bin/images/24_execute.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/images/24_execute_abort2.gif b/Source/eu.modelwriter.alloyanalyzer/bin/images/24_execute_abort2.gif new file mode 100644 index 00000000..332d42ab Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/bin/images/24_execute_abort2.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/images/24_graph.gif b/Source/eu.modelwriter.alloyanalyzer/bin/images/24_graph.gif new file mode 100644 index 00000000..ac830a0a Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/bin/images/24_graph.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/images/24_history.gif b/Source/eu.modelwriter.alloyanalyzer/bin/images/24_history.gif new file mode 100644 index 00000000..c18eb6b4 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/bin/images/24_history.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/images/24_new.gif b/Source/eu.modelwriter.alloyanalyzer/bin/images/24_new.gif new file mode 100644 index 00000000..9d9f0ee2 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/bin/images/24_new.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/images/24_open.gif b/Source/eu.modelwriter.alloyanalyzer/bin/images/24_open.gif new file mode 100644 index 00000000..de4696ce Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/bin/images/24_open.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/images/24_plaintext.gif b/Source/eu.modelwriter.alloyanalyzer/bin/images/24_plaintext.gif new file mode 100644 index 00000000..7eace4b3 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/bin/images/24_plaintext.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/images/24_reload.gif b/Source/eu.modelwriter.alloyanalyzer/bin/images/24_reload.gif new file mode 100644 index 00000000..2652ff48 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/bin/images/24_reload.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/images/24_save.gif b/Source/eu.modelwriter.alloyanalyzer/bin/images/24_save.gif new file mode 100644 index 00000000..e243bd53 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/bin/images/24_save.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/images/24_settings.gif b/Source/eu.modelwriter.alloyanalyzer/bin/images/24_settings.gif new file mode 100644 index 00000000..0195f5bb Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/bin/images/24_settings.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/images/24_settings_apply.gif b/Source/eu.modelwriter.alloyanalyzer/bin/images/24_settings_apply.gif new file mode 100644 index 00000000..87fe2dc1 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/bin/images/24_settings_apply.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/images/24_settings_apply2.gif b/Source/eu.modelwriter.alloyanalyzer/bin/images/24_settings_apply2.gif new file mode 100644 index 00000000..b91773cb Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/bin/images/24_settings_apply2.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/images/24_settings_apply3.gif b/Source/eu.modelwriter.alloyanalyzer/bin/images/24_settings_apply3.gif new file mode 100644 index 00000000..7472cf1e Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/bin/images/24_settings_apply3.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/images/24_settings_close.gif b/Source/eu.modelwriter.alloyanalyzer/bin/images/24_settings_close.gif new file mode 100644 index 00000000..a550fa6d Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/bin/images/24_settings_close.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/images/24_settings_close2.gif b/Source/eu.modelwriter.alloyanalyzer/bin/images/24_settings_close2.gif new file mode 100644 index 00000000..652ad79f Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/bin/images/24_settings_close2.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/images/24_settings_close3.gif b/Source/eu.modelwriter.alloyanalyzer/bin/images/24_settings_close3.gif new file mode 100644 index 00000000..20f9c5fb Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/bin/images/24_settings_close3.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/images/24_settings_close4.gif b/Source/eu.modelwriter.alloyanalyzer/bin/images/24_settings_close4.gif new file mode 100644 index 00000000..50209486 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/bin/images/24_settings_close4.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/images/24_settings_close5.gif b/Source/eu.modelwriter.alloyanalyzer/bin/images/24_settings_close5.gif new file mode 100644 index 00000000..3c417b5e Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/bin/images/24_settings_close5.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/images/24_texttree.gif b/Source/eu.modelwriter.alloyanalyzer/bin/images/24_texttree.gif new file mode 100644 index 00000000..ab735b1f Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/bin/images/24_texttree.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/images/cb0.gif b/Source/eu.modelwriter.alloyanalyzer/bin/images/cb0.gif new file mode 100644 index 00000000..4e47578d Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/bin/images/cb0.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/images/cb1.gif b/Source/eu.modelwriter.alloyanalyzer/bin/images/cb1.gif new file mode 100644 index 00000000..491dcb86 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/bin/images/cb1.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/images/logo.gif b/Source/eu.modelwriter.alloyanalyzer/bin/images/logo.gif new file mode 100644 index 00000000..c23a7f05 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/bin/images/logo.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/images/menu0.gif b/Source/eu.modelwriter.alloyanalyzer/bin/images/menu0.gif new file mode 100644 index 00000000..b1d498cd Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/bin/images/menu0.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/images/menu1.gif b/Source/eu.modelwriter.alloyanalyzer/bin/images/menu1.gif new file mode 100644 index 00000000..f204c0b5 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/bin/images/menu1.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/images/tcb01.gif b/Source/eu.modelwriter.alloyanalyzer/bin/images/tcb01.gif new file mode 100644 index 00000000..dd3972f8 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/bin/images/tcb01.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/images/tcb02.gif b/Source/eu.modelwriter.alloyanalyzer/bin/images/tcb02.gif new file mode 100644 index 00000000..7755c002 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/bin/images/tcb02.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/images/tcb03.gif b/Source/eu.modelwriter.alloyanalyzer/bin/images/tcb03.gif new file mode 100644 index 00000000..d3ad1e95 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/bin/images/tcb03.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/images/tcb04.gif b/Source/eu.modelwriter.alloyanalyzer/bin/images/tcb04.gif new file mode 100644 index 00000000..d0dff531 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/bin/images/tcb04.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/kodkod/ast/operator/package.html b/Source/eu.modelwriter.alloyanalyzer/bin/kodkod/ast/operator/package.html new file mode 100644 index 00000000..50cb245b --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/kodkod/ast/operator/package.html @@ -0,0 +1,32 @@ + + + + + + + + +Contains operators for Kodkod formulas, expressions, and integer expressions. + +

Package Specification

+ +

Contains operators for Kodkod formulas, expressions, and integer expressions.

+ + + + diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/kodkod/ast/package.html b/Source/eu.modelwriter.alloyanalyzer/bin/kodkod/ast/package.html new file mode 100644 index 00000000..6f886408 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/kodkod/ast/package.html @@ -0,0 +1,48 @@ + + + + + + + + +Contains classes for creating Kodkod formulas, expressions, +and integer expressions. + +

Package Specification

+ +

Contains the classes for creating a Kodkod abstract syntax +tree (AST). An object such as a quantified formula or a union expression is called a node. +The {@linkplain kodkod.ast.Node} class is the root of the Kodkod syntax hierarchy.

+ +

All classes in this package are immutable. Their instances +are created by calling factory methods of the classes {@linkplain kodkod.ast.Relation}, {@linkplain kodkod.ast.Variable}, +{@linkplain kodkod.ast.Expression}, {@linkplain kodkod.ast.IntExpression}, and {@linkplain kodkod.ast.Formula}. Nodes can be freely shared between +multiple parents (so a Kodkod AST is actually a directed acyclic graph).

+ +

Related Documentation

+ +@see kodkod.ast.Relation +@see kodkod.ast.Variable +@see kodkod.ast.Expression +@see kodkod.ast.IntExpression +@see kodkod.ast.Formula +@see kodkod.ast.Node + + + diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/kodkod/ast/visitor/package.html b/Source/eu.modelwriter.alloyanalyzer/bin/kodkod/ast/visitor/package.html new file mode 100644 index 00000000..5698cc13 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/kodkod/ast/visitor/package.html @@ -0,0 +1,45 @@ + + + + + + + + +Contains visitors for Kodkod formulas, expressions, and integer expressions. + +

Package Specification

+ +

Provides two interfaces for traversing the Kodkod AST using +the visitor pattern. A {@linkplain kodkod.ast.visitor.VoidVisitor} visits the nodes but returns no values. A +{@linkplain kodkod.ast.visitor.ReturnVisitor} can be parametrized to return values of specific types for +{@linkplain kodkod.ast.Decls}, {@linkplain kodkod.ast.Expression}, {@linkplain kodkod.ast.IntExpression}, +and {@linkplain kodkod.ast.Formula} nodes.

+ +

Several skeletal implementations of the VoidVisitor and ReturnVisitor interfaces +are also provided. These traverse the AST in a depth-first manner and optionally cache +the results of visiting specified nodes. The caching functionality makes it convenient +to implement visitors that visit shared nodes only once.

+ +

Related Documentation

+ +@see kodkod.ast.visitor.VoidVisitor +@see kodkod.ast.visitor.ReturnVisitor + + + diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/kodkod/engine/bool/package.html b/Source/eu.modelwriter.alloyanalyzer/bin/kodkod/engine/bool/package.html new file mode 100644 index 00000000..2bac8bb0 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/kodkod/engine/bool/package.html @@ -0,0 +1,42 @@ + + + + + + + + +Provides classes for constructing and composing boolean matrices, boolean circuits, and +boolean representations of integers. + +

Package Specification

+ +

Contains classes that represent {@linkplain kodkod.engine.bool.BooleanMatrix boolean matrices}, +{@linkplain kodkod.engine.bool.BooleanValue boolean circuits}, and {@linkplain kodkod.engine.bool.Int boolean +representation of integers}. Matrices, circuits, and integers are constructed via factory methods of the + {@linkplain kodkod.engine.bool.BooleanFactory} class.

+ +

Related Documentation

+ +@see kodkod.engine.bool.BooleanFactory +@see kodkod.engine.bool.BooleanValue +@see kodkod.engine.bool.BooleanMatrix +@see kodkod.engine.bool.Int + + + diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/kodkod/engine/config/package.html b/Source/eu.modelwriter.alloyanalyzer/bin/kodkod/engine/config/package.html new file mode 100644 index 00000000..50a3a06b --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/kodkod/engine/config/package.html @@ -0,0 +1,42 @@ + + + + + + + + +Provides a mechanism for configuring the kodkod engine and for passing messages +between the engine and the client. + +

Package Specification

+ +

Provides a mechanism for configuring the kodkod engine and for passing messages +between the engine and the client. The class {@linkplain kodkod.engine.config.Options} +stores information about various user-level translation and analysis options. It can be +used to choose the SAT solver, control symmetry breaking, etc. The interface +{@linkplain kodkod.engine.config.Reporter} enables passing of messages between the kodkod engine +and the client via callback methods.

+ +

Related Documentation

+ +@see kodkod.engine.config.Options +@see kodkod.engine.config.Reporter + + + diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/kodkod/engine/fol2sat/package.html b/Source/eu.modelwriter.alloyanalyzer/bin/kodkod/engine/fol2sat/package.html new file mode 100644 index 00000000..1163ddca --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/kodkod/engine/fol2sat/package.html @@ -0,0 +1,45 @@ + + + + + + + + +Provides a facade for translating, evaluating, and approximating Kodkod +formulas, expressions, and int expressions with respect to a given Bounds +(or Instance) and Options. + +

Package Specification

+ +

Provides a facade for translating, evaluating, and approximating Kodkod +formulas, expressions, and int expressions with respect to given Bounds +(or Instance) and Options. The {@linkplain kodkod.engine.fol2sat.Translator} +class contains methods for translating a Kodkod formula to CNF, evaluating +a Node with respect to an instance, and over-approximating the value of an +expression based on the upper bounds in a given Bounds object.

+ +

Related Documentation

+ +@see kodkod.engine.fol2sat.Translator +@see kodkod.engine.fol2sat.Translation +@see kodkod.engine.fol2sat.TranslationLog +@see kodkod.engine.fol2sat.TranslationRecord + + + diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/kodkod/engine/package.html b/Source/eu.modelwriter.alloyanalyzer/bin/kodkod/engine/package.html new file mode 100644 index 00000000..2810e5d3 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/kodkod/engine/package.html @@ -0,0 +1,47 @@ + + + + + + + + +Provides classes for analyzing and evaluating Kodkod ASTs with +respect to finite bounds or instances. + +

Package Specification

+ +

Contains classes for analyzing and evaluating Kodkod ASTs with +respect to finite bounds or instances. The class Solver provides +methods for finding finite models of Kodkod formulas with respect +to given {@linkplain kodkod.instance.Bounds Bounds} and {@linkplain kodkod.engine.config.Options Options}. +The class Evalutor enables evaluation of formulas, expressions, and integer expressions with +respect to a particular {@linkplain kodkod.instance.Instance Instance} and {@linkplain kodkod.engine.config.Options Options}.

+ +

Related Documentation

+ +@see kodkod.instance.Bounds +@see kodkod.instance.Instance +@see kodkod.ast.Expression +@see kodkod.ast.IntExpression +@see kodkod.ast.Formula +@see kodkod.engine.Solver +@see kodkod.engine.Evaluator + + + diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/kodkod/engine/satlab/package.html b/Source/eu.modelwriter.alloyanalyzer/bin/kodkod/engine/satlab/package.html new file mode 100644 index 00000000..76b48632 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/kodkod/engine/satlab/package.html @@ -0,0 +1,42 @@ + + + + + + + +Provides access to various Java and C++ SAT solvers through a +common SAT Solver interface. + +

Package Specification

+ +

Provides access to various Java and C++ SAT solvers through the +{@linkplain kodkod.engine.satlab.SATSolver}, +{@linkplain kodkod.engine.satlab.SATProver}, and {@linkplain kodkod.engine.satlab.SATMinSolver} +interfaces. The {@linkplain kodkod.engine.satlab.SATFactory} class contains a selection of +static instances that can be used to generate specific SAT solvers.

+ +

Related Documentation

+ +@see kodkod.engine.satlab.SATFactory +@see kodkod.engine.satlab.SATSolver +@see kodkod.engine.satlab.SATProver +@see kodkod.engine.satlab.SATMinSolver + + + diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/kodkod/engine/ucore/package.html b/Source/eu.modelwriter.alloyanalyzer/bin/kodkod/engine/ucore/package.html new file mode 100644 index 00000000..b8bc00ea --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/kodkod/engine/ucore/package.html @@ -0,0 +1,37 @@ + + + + + + + +Contains strategies for minimizing unsatisfiable cores generated by SAT provers. + +

Package Specification

+ +

Contains implementations of various {@linkplain kodkod.engine.satlab.ReductionStrategy strategies} +for minimizing unsatisfiable cores generated by {@linkplain kodkod.engine.satlab.SATProver SAT provers}.

+ +

Related Documentation

+ +@see kodkod.engine.satlab.ReductionStrategy +@see kodkod.engine.satlab.SATProver + + + + diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/kodkod/instance/package.html b/Source/eu.modelwriter.alloyanalyzer/bin/kodkod/instance/package.html new file mode 100644 index 00000000..ade655f5 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/kodkod/instance/package.html @@ -0,0 +1,42 @@ + + + + + + + +Contains classes for creating tuples, sets of tuples, bounds, and instances +drawn from a finite universe of uninterpreted atoms. + +

Package Specification

+ +

Contains classes for creating {@linkplain kodkod.instance.Tuple tuples}, +{@linkplain kodkod.instance.TupleSet sets of tuples}, {@linkplain kodkod.instance.Bounds bounds}, and +{@linkplain kodkod.instance.Instance instances} drawn from a finite +{@linkplain kodkod.instance.Universe universe} of uninterpreted atoms.

+ +

Related Documentation

+ +@see kodkod.instance.Universe +@see kodkod.instance.TupleFactory +@see kodkod.instance.Bounds +@see kodkod.instance.Instance + + + + diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/kodkod/util/collections/package.html b/Source/eu.modelwriter.alloyanalyzer/bin/kodkod/util/collections/package.html new file mode 100644 index 00000000..0b4d679d --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/kodkod/util/collections/package.html @@ -0,0 +1,34 @@ + + + + + + + +Contains specialized collections, such as a set that provides methods for +retrieving elements with a particular hashcode. + +

Package Specification

+ +

Contains specialized collections, such as a set that provides methods for +retrieving elements with a particular hashcode. It also provides a +utility class for working with arrays.

+ + + + diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/kodkod/util/ints/package.html b/Source/eu.modelwriter.alloyanalyzer/bin/kodkod/util/ints/package.html new file mode 100644 index 00000000..6202e784 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/kodkod/util/ints/package.html @@ -0,0 +1,36 @@ + + + + + + + +Provides implementations of ordered collections for storing integer primitives. + +

Package Specification

+ +

Provides several implementations of ordered collections for storing integer primitives.

+ +

Related Documentation

+ +@see kodkod.util.ints.IntSet +@see kodkod.util.ints.IntVector +@see kodkod.util.ints.SparseSequence + + + diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/kodkod/util/nodes/package.html b/Source/eu.modelwriter.alloyanalyzer/bin/kodkod/util/nodes/package.html new file mode 100644 index 00000000..03fd26ae --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/kodkod/util/nodes/package.html @@ -0,0 +1,36 @@ + + + + + + + +Provides utility methods for constructing, analyzing, and pretty printing Kodkod nodes. + +

Package Specification

+ +

Provides utility methods for constructing, analyzing, and pretty printing Kodkod nodes.

+ +

Related Documentation

+ +@see kodkod.util.nodes.Nodes +@see kodkod.util.nodes.AnnotatedNode +@see kodkod.util.nodes.PrettyPrinter + + + diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/book/appendixA/addressBook1.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/appendixA/addressBook1.als new file mode 100644 index 00000000..06c2abf8 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/appendixA/addressBook1.als @@ -0,0 +1,19 @@ +module appendixA/addressBook1 + +abstract sig Name { + address: set Addr+Name + } + +sig Alias, Group extends Name { } + +sig Addr { } + +fact { + // the invariants should go here + } + +pred show { + // simulation constraints should go here + } + +run show for 3 diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/book/appendixA/addressBook2.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/appendixA/addressBook2.als new file mode 100644 index 00000000..02ba6373 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/appendixA/addressBook2.als @@ -0,0 +1,27 @@ +module appendixA/addressBook2 + +sig Addr, Name { } + +sig Book { + addr: Name -> (Name + Addr) + } + +pred inv [b: Book] { + let addr = b.addr | + all n: Name { + n not in n.^addr + some addr.n => some n.^addr & Addr + } + } + +pred add [b, b': Book, n: Name, t: Name+Addr] { + b'.addr = b.addr + n->t + } + +pred del [b, b': Book, n: Name, t: Name+Addr] { + b'.addr = b.addr - n->t + } + +fun lookup [b: Book, n: Name] : set Addr { + n.^(b.addr) & Addr + } diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/book/appendixA/barbers.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/appendixA/barbers.als new file mode 100644 index 00000000..6205b5f4 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/appendixA/barbers.als @@ -0,0 +1,9 @@ +module appendixA/barbers + +sig Man { shaves: set Man } + +one sig Barber extends Man { } + +fact { + Barber.shaves = { m: Man | m not in m.shaves } + } diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/book/appendixA/closure.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/appendixA/closure.als new file mode 100644 index 00000000..6a50e404 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/appendixA/closure.als @@ -0,0 +1,16 @@ +module appendixA/closure + +pred transCover [R, r: univ->univ] { + // You have to fill in the appropriate formula here +} + +pred transClosure [R, r: univ->univ] { + transCover [R, r] + // You have to fill in the appropriate formula here +} + +assert Equivalence { + all R, r: univ->univ | transClosure [R,r] iff R = ^r +} + +check Equivalence for 3 diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/book/appendixA/distribution.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/appendixA/distribution.als new file mode 100644 index 00000000..6614b2e6 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/appendixA/distribution.als @@ -0,0 +1,7 @@ +module appendixA/distribution + +assert union { + all s: set univ, p, q: univ->univ | s.(p+q) = s.p + s.q +} + +check union for 4 diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/book/appendixA/phones.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/appendixA/phones.als new file mode 100644 index 00000000..98c13677 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/appendixA/phones.als @@ -0,0 +1,6 @@ +module appendixA/phones + +sig Phone { + requests: set Phone, + connects: lone Phone + } diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/book/appendixA/prison.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/appendixA/prison.als new file mode 100644 index 00000000..42e9ea00 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/appendixA/prison.als @@ -0,0 +1,17 @@ +module appendixA/prison + +sig Gang { members: set Inmate } + +sig Inmate { room: Cell } + +sig Cell { } + +pred safe { + // your constraints should go here + } + +pred show { + // your constraints should go here + } + +run show diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/book/appendixA/properties.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/appendixA/properties.als new file mode 100644 index 00000000..cc06cc1e --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/appendixA/properties.als @@ -0,0 +1,23 @@ +module appendixA/properties + +pred show { + some r: univ->univ { + some r -- nonempty + r.r in r -- transitive + no iden & r -- irreflexive + ~r in r -- symmetric + ~r.r in iden -- functional + r.~r in iden -- injective + univ in r.univ -- total + univ in univ.r -- onto + } + } + +run show for 4 + +assert ReformulateNonEmptinessOK { + all r: univ->univ | + some r iff (some x, y: univ | x->y in r) + } + +check ReformulateNonEmptinessOK diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/book/appendixA/ring.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/appendixA/ring.als new file mode 100644 index 00000000..ec577666 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/appendixA/ring.als @@ -0,0 +1,9 @@ +module appendixA/ring + +sig Node { next: set Node } + +pred isRing { + // You have to fill in the appropriate formula here +} + +run isRing for exactly 4 Node diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/book/appendixA/spanning.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/appendixA/spanning.als new file mode 100644 index 00000000..5218e700 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/appendixA/spanning.als @@ -0,0 +1,17 @@ +module appendixA/spanning + +pred isTree [r: univ->univ] { + // You have to fill in the appropriate formula here +} + +pred spans [r1, r2: univ->univ] { + // You have to fill in the appropriate formula here +} + +pred show [r, t1, t2: univ->univ] { + spans [t1,r] and isTree [t1] + spans [t2,r] and isTree [t2] + t1 != t2 +} + +run show for 3 diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/book/appendixA/tree.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/appendixA/tree.als new file mode 100644 index 00000000..74750d40 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/appendixA/tree.als @@ -0,0 +1,7 @@ +module appendixA/tree + +pred isTree [r:univ->univ] { + // You have to fill in the appropriate formula here +} + +run isTree for 4 diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/book/appendixA/tube.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/appendixA/tube.als new file mode 100644 index 00000000..03af8de9 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/appendixA/tube.als @@ -0,0 +1,21 @@ +module appendixA/tube + +abstract sig Station { + jubilee, central, circle: set Station + } + +sig Jubilee, Central, Circle in Station {} + +one sig + Stanmore, BakerStreet, BondStreet, Westminster, Waterloo, + WestRuislip, EalingBroadway, NorthActon, NottingHillGate, + LiverpoolStreet, Epping + extends Station {} + +fact { + // the constraints should go here + } + +pred show {} + +run show diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/book/appendixA/undirected.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/appendixA/undirected.als new file mode 100644 index 00000000..937cedb1 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/appendixA/undirected.als @@ -0,0 +1,11 @@ +module appendixA/undirected + +sig Node { adjs: set Node } + +pred acyclic { + adjs = ~adjs + // You have to fill in additional formula here +} + +run acyclic for 4 + diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/book/appendixE/hotel.thm b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/appendixE/hotel.thm new file mode 100644 index 00000000..fe20fbe1 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/appendixE/hotel.thm @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/book/appendixE/p300-hotel.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/appendixE/p300-hotel.als new file mode 100644 index 00000000..813846b2 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/appendixE/p300-hotel.als @@ -0,0 +1,66 @@ +module hotel + +open util/ordering [Time] as timeOrder + +sig Key, Time {} + +sig Card { + fst, snd: Key + } + +sig Room { + key: Key one->Time + } + +one sig Desk { + issued: Key->Time, + prev: (Room->lone Key)->Time + } + +sig Guest { + cards: Card->Time + } + +pred init [t: Time] { + Desk.prev.t = key.t + no issued.t and no cards.t ------ bug! (see page 303) + } + +pred checkin [t,t': Time, r: Room, g: Guest] { + some c: Card { + c.fst = r.(Desk.prev.t) + c.snd not in Desk.issued.t + cards.t' = cards.t + g->c ------------- bug! (see page 306) + Desk.issued.t' = Desk.issued.t + c.snd + Desk.prev.t' = Desk.prev.t ++ r->c.snd + } + key.t = key.t' + } + +pred enter [t,t': Time, r: Room, g: Guest] { + some c: g.cards.t | + let k = r.key.t { + c.snd = k and key.t' = key.t + or c.fst = k and key.t' = key.t ++ r->c.snd + } + issued.t = issued.t' and (Desk<:prev).t = prev.t' + cards.t = cards.t' + } + +fact Traces { + init [first] + all t: Time - last | some g: Guest, r: Room | + checkin [t, t.next, r, g] or enter[t, t.next, r, g] + } + +assert NoIntruder { + no t1: Time, g: Guest, g': Guest-g, r: Room | + let t2=t1.next, t3=t2.next, t4=t3.next { + enter [t1, t2, r, g] + enter [t2, t3, r, g'] + enter [t3, t4, r, g] + } + } + +-- This check reveals a bug (similar to Fig E.3) in which the initial key was issued twice. +check NoIntruder for 3 but 6 Time, 1 Room, 2 Guest diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/book/appendixE/p303-hotel.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/appendixE/p303-hotel.als new file mode 100644 index 00000000..652de3d8 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/appendixE/p303-hotel.als @@ -0,0 +1,70 @@ +module hotel + +open util/ordering [Time] as timeOrder + +sig Key, Time {} + +sig Card { + fst, snd: Key + } + +sig Room { + key: Key one->Time + } + +one sig Desk { + issued: Key->Time, + prev: (Room->lone Key)->Time + } + +sig Guest { + cards: Card->Time + } + +pred init [t: Time] { + Desk.prev.t = key.t + Desk.issued.t = Room.key.t and no cards.t + } + +pred checkin [t,t': Time, r: Room, g: Guest] { + some c: Card { + c.fst = r.(Desk.prev.t) + c.snd not in Desk.issued.t + cards.t' = cards.t + g->c ------------- bug! (see page 306) + Desk.issued.t' = Desk.issued.t + c.snd + Desk.prev.t' = Desk.prev.t ++ r->c.snd + } + key.t = key.t' + } + +pred enter [t,t': Time, r: Room, g: Guest] { + some c: g.cards.t | + let k = r.key.t { + c.snd = k and key.t' = key.t + or c.fst = k and key.t' = key.t ++ r->c.snd + } + issued.t = issued.t' and (Desk<:prev).t = prev.t' + cards.t = cards.t' + } + +fact Traces { + init [first] + all t: Time - last | some g: Guest, r: Room | + checkin [t, t.next, r, g] or enter[t, t.next, r, g] + } + +assert NoIntruder { + no t1: Time, g: Guest, g': Guest-g, r: Room | + let t2=t1.next, t3=t2.next, t4=t3.next { + enter [t1, t2, r, g] + enter [t2, t3, r, g'] + enter [t3, t4, r, g] + } + } + +-- This check now succeeds without finding any counterexample. +check NoIntruder for 3 but 6 Time, 1 Room, 2 Guest + +-- To increase our confidence, we can increase the scope. +-- This time, it finds a counterexample. +check NoIntruder for 4 but 7 Time, 1 Room, 2 Guest diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/book/appendixE/p306-hotel.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/appendixE/p306-hotel.als new file mode 100644 index 00000000..efcd7e7d --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/appendixE/p306-hotel.als @@ -0,0 +1,73 @@ +module hotel + +open util/ordering [Time] as timeOrder + +sig Key, Time {} + +sig Card { + fst, snd: Key + } + +sig Room { + key: Key one->Time + } + +one sig Desk { + issued: Key->Time, + prev: (Room->lone Key)->Time + } + +sig Guest { + cards: Card->Time + } + +pred init [t: Time] { + Desk.prev.t = key.t + Desk.issued.t = Room.key.t and no cards.t + } + +pred checkin [t,t': Time, r: Room, g: Guest] { + some c: Card { + c.fst = r.(Desk.prev.t) + c.snd not in Desk.issued.t + cards.t' = cards.t ++ g->c + Desk.issued.t' = Desk.issued.t + c.snd + Desk.prev.t' = Desk.prev.t ++ r->c.snd + } + key.t = key.t' + } + +pred enter [t,t': Time, r: Room, g: Guest] { + some c: g.cards.t | + let k = r.key.t { + c.snd = k and key.t' = key.t + or c.fst = k and key.t' = key.t ++ r->c.snd + } + issued.t = issued.t' and (Desk<:prev).t = prev.t' + cards.t = cards.t' + } + +fact Traces { + init [first] + all t: Time - last | some g: Guest, r: Room | + checkin [t, t.next, r, g] or enter[t, t.next, r, g] + } + +assert NoIntruder { + no t1: Time, g: Guest, g': Guest-g, r: Room | + let t2=t1.next, t3=t2.next, t4=t3.next { + enter [t1, t2, r, g] + enter [t2, t3, r, g'] + enter [t3, t4, r, g] + } + } + +-- This check now succeeds without finding any counterexample. +check NoIntruder for 3 but 6 Time, 1 Room, 2 Guest + +-- This check now succeeds without finding any counterexample. +check NoIntruder for 4 but 7 Time, 1 Room, 2 Guest + +-- We can try to increase the scope further. +-- This check also succeeds without finding any counterexample. +check NoIntruder for 6 but 12 Time, 3 Room, 3 Guest diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter2/addressBook1a.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter2/addressBook1a.als new file mode 100644 index 00000000..643c0f2c --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter2/addressBook1a.als @@ -0,0 +1,12 @@ +module tour/addressBook1a ----- Page 6 + +sig Name, Addr { } + +sig Book { + addr: Name -> lone Addr +} + +pred show { } + +// This command generates an instance similar to Fig 2.1 +run show for 3 but 1 Book diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter2/addressBook1b.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter2/addressBook1b.als new file mode 100644 index 00000000..1d4255e9 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter2/addressBook1b.als @@ -0,0 +1,14 @@ +module tour/addressBook1b ----- Page 8 + +sig Name, Addr { } + +sig Book { + addr: Name -> lone Addr +} + +pred show [b: Book] { + #b.addr > 1 +} + +// This command generates an instance similar to Fig 2.2 +run show for 3 but 1 Book diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter2/addressBook1c.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter2/addressBook1c.als new file mode 100644 index 00000000..85b18616 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter2/addressBook1c.als @@ -0,0 +1,15 @@ +module tour/addressBook1c ----- Page 8 + +sig Name, Addr { } + +sig Book { + addr: Name -> lone Addr +} + +pred show [b: Book] { + #b.addr > 1 + some n: Name | #n.(b.addr) > 1 +} + +// This command should not find any instance. +run show for 3 but 1 Book diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter2/addressBook1d.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter2/addressBook1d.als new file mode 100644 index 00000000..c553a4f9 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter2/addressBook1d.als @@ -0,0 +1,15 @@ +module tour/addressBook1d ----- Page 9 + +sig Name, Addr { } + +sig Book { + addr: Name -> lone Addr +} + +pred show [b: Book] { + #b.addr > 1 + #Name.(b.addr) > 1 +} + +// This command generates an instance similar to Fig 2.3 +run show for 3 but 1 Book diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter2/addressBook1e.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter2/addressBook1e.als new file mode 100644 index 00000000..18165f34 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter2/addressBook1e.als @@ -0,0 +1,14 @@ +module tour/addressBook1e ----- Page 11 + +sig Name, Addr { } + +sig Book { + addr: Name -> lone Addr +} + +pred add [b, b': Book, n: Name, a: Addr] { + b'.addr = b.addr + n->a +} + +// This command generates an instance similar to Fig 2.4 +run add for 3 but 2 Book diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter2/addressBook1f.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter2/addressBook1f.als new file mode 100644 index 00000000..8774cd7b --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter2/addressBook1f.als @@ -0,0 +1,19 @@ +module tour/addressBook1f ----- Page 12 + +sig Name, Addr { } + +sig Book { + addr: Name -> lone Addr +} + +pred add [b, b': Book, n: Name, a: Addr] { + b'.addr = b.addr + n->a +} + +pred showAdd [b, b': Book, n: Name, a: Addr] { + add [b, b', n, a] + #Name.(b'.addr) > 1 +} + +// This command generates an instance similar to Fig 2.5 +run showAdd for 3 but 2 Book diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter2/addressBook1g.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter2/addressBook1g.als new file mode 100644 index 00000000..4eeac73a --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter2/addressBook1g.als @@ -0,0 +1,29 @@ +module tour/addressBook1g ----- Page 14 + +sig Name, Addr { } + +sig Book { + addr: Name -> lone Addr +} + +pred add [b, b': Book, n: Name, a: Addr] { + b'.addr = b.addr + n->a +} + +pred del [b, b': Book, n: Name] { + b'.addr = b.addr - n->Addr +} + +fun lookup [b: Book, n: Name] : set Addr { + n.(b.addr) +} + +assert delUndoesAdd { + all b, b', b'': Book, n: Name, a: Addr | + add [b, b', n, a] and del [b', b'', n] + implies + b.addr = b''.addr +} + +// This command generates an instance similar to Fig 2.6 +check delUndoesAdd for 3 diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter2/addressBook1h.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter2/addressBook1h.als new file mode 100644 index 00000000..115eab78 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter2/addressBook1h.als @@ -0,0 +1,64 @@ +module tour/addressBook1h ------- Page 14..16 + +sig Name, Addr { } + +sig Book { + addr: Name -> lone Addr +} + +pred show [b: Book] { + #b.addr > 1 + #Name.(b.addr) > 1 +} +run show for 3 but 1 Book + +pred add [b, b': Book, n: Name, a: Addr] { + b'.addr = b.addr + n->a +} + +pred del [b, b': Book, n: Name] { + b'.addr = b.addr - n->Addr +} + +fun lookup [b: Book, n: Name] : set Addr { + n.(b.addr) +} + +pred showAdd [b, b': Book, n: Name, a: Addr] { + add [b, b', n, a] + #Name.(b'.addr) > 1 +} +run showAdd for 3 but 2 Book + +assert delUndoesAdd { + all b, b', b'': Book, n: Name, a: Addr | + no n.(b.addr) and add [b, b', n, a] and del [b', b'', n] + implies + b.addr = b''.addr +} + +assert addIdempotent { + all b, b', b'': Book, n: Name, a: Addr | + add [b, b', n, a] and add [b', b'', n, a] + implies + b'.addr = b''.addr +} + +assert addLocal { + all b, b': Book, n, n': Name, a: Addr | + add [b, b', n, a] and n != n' + implies + lookup [b, n'] = lookup [b', n'] +} + +// This command should not find any counterexample. +check delUndoesAdd for 3 + +// This command should not find any counterexample. +check delUndoesAdd for 10 but 3 Book + +// This command should not find any counterexample. +check addIdempotent for 3 + +// This command should not find any counterexample. +check addLocal for 3 but 2 Book diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter2/addressBook2a.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter2/addressBook2a.als new file mode 100644 index 00000000..ab94951b --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter2/addressBook2a.als @@ -0,0 +1,16 @@ +module tour/addressBook2a ----- Page 18 + +abstract sig Target { } +sig Addr extends Target { } +abstract sig Name extends Target { } + +sig Alias, Group extends Name { } + +sig Book { + addr: Name->Target +} + +pred show [b:Book] { some b.addr } + +// This command generates an instance similar to Fig 2.9 +run show for 3 but 1 Book diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter2/addressBook2b.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter2/addressBook2b.als new file mode 100644 index 00000000..a6d1da91 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter2/addressBook2b.als @@ -0,0 +1,18 @@ +module tour/addressBook2b ----- Page 19 + +abstract sig Target { } +sig Addr extends Target { } +abstract sig Name extends Target { } + +sig Alias, Group extends Name { } + +sig Book { + addr: Name->Target +} { + no n: Name | n in n.^addr +} + +pred show [b:Book] { some b.addr } + +// This command generates an instance similar to Fig 2.10 +run show for 3 but 1 Book diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter2/addressBook2c.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter2/addressBook2c.als new file mode 100644 index 00000000..33ded6b3 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter2/addressBook2c.als @@ -0,0 +1,18 @@ +module tour/addressBook2c ----- Page 20 + +abstract sig Target { } +sig Addr extends Target { } +abstract sig Name extends Target { } + +sig Alias, Group extends Name { } + +sig Book { + addr: Name->Target +} { + no n: Name | n in n.^addr +} + +pred show [b:Book] { some Alias.(b.addr) } + +// This command generates an instance similar to Fig 2.11 +run show for 3 but 1 Book diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter2/addressBook2d.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter2/addressBook2d.als new file mode 100644 index 00000000..adac4fbe --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter2/addressBook2d.als @@ -0,0 +1,19 @@ +module tour/addressBook2d ----- Page 21 + +abstract sig Target { } +sig Addr extends Target { } +abstract sig Name extends Target { } + +sig Alias, Group extends Name { } + +sig Book { + addr: Name->Target +} { + no n: Name | n in n.^addr + all a: Alias | lone a.addr +} + +pred show [b:Book] { some Alias.(b.addr) } + +// This command generates an instance similar to Fig 2.12 +run show for 3 but 1 Book diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter2/addressBook2e.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter2/addressBook2e.als new file mode 100644 index 00000000..3baea5ac --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter2/addressBook2e.als @@ -0,0 +1,56 @@ +module tour/addressBook2e --- this is the final model in Fig 2.14 + +abstract sig Target { } +sig Addr extends Target { } +abstract sig Name extends Target { } + +sig Alias, Group extends Name { } + +sig Book { + names: set Name, + addr: names->some Target +} { + no n: Name | n in n.^addr + all a: Alias | lone a.addr +} + +pred add [b, b': Book, n: Name, t: Target] { b'.addr = b.addr + n->t } +pred del [b, b': Book, n: Name, t: Target] { b'.addr = b.addr - n->t } +fun lookup [b: Book, n: Name] : set Addr { n.^(b.addr) & Addr } + +assert delUndoesAdd { + all b, b', b'': Book, n: Name, t: Target | + no n.(b.addr) and add [b, b', n, t] and del [b', b'', n, t] + implies + b.addr = b''.addr +} + +// This should not find any counterexample. +check delUndoesAdd for 3 + +assert addIdempotent { + all b, b', b'': Book, n: Name, t: Target | + add [b, b', n, t] and add [b', b'', n, t] + implies + b'.addr = b''.addr +} + +// This should not find any counterexample. +check addIdempotent for 3 + +assert addLocal { + all b, b': Book, n, n': Name, t: Target | + add [b, b', n, t] and n != n' + implies + lookup [b, n'] = lookup [b', n'] +} + +// This shows a counterexample similar to Fig 2.13 +check addLocal for 3 but 2 Book + +assert lookupYields { + all b: Book, n: b.names | some lookup [b,n] +} + +// This shows a counterexample similar to Fig 2.12 +check lookupYields for 4 but 1 Book diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter2/addressBook3a.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter2/addressBook3a.als new file mode 100644 index 00000000..5ff9eee0 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter2/addressBook3a.als @@ -0,0 +1,38 @@ +module tour/addressBook3a ----- Page 25 + +open util/ordering [Book] as BookOrder + +abstract sig Target { } +sig Addr extends Target { } +abstract sig Name extends Target { } + +sig Alias, Group extends Name { } + +sig Book { + names: set Name, + addr: names->some Target +} { + no n: Name | n in n.^addr + all a: Alias | lone a.addr +} + +pred add [b, b': Book, n: Name, t: Target] { b'.addr = b.addr + n->t } +pred del [b, b': Book, n: Name, t: Target] { b'.addr = b.addr - n->t } +fun lookup [b: Book, n: Name] : set Addr { n.^(b.addr) & Addr } + +pred init [b: Book] { no b.addr } + +fact traces { + init [first] + all b: Book-last | + let b' = b.next | + some n: Name, t: Target | + add [b, b', n, t] or del [b, b', n, t] +} + +------------------------------------------------------ + +pred show { } + +// This command generates an instance similar to Fig 2.15 +run show for 4 diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter2/addressBook3b.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter2/addressBook3b.als new file mode 100644 index 00000000..5c4b835d --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter2/addressBook3b.als @@ -0,0 +1,76 @@ +module tour/addressBook3b ----- Page 26 + +open util/ordering [Book] as BookOrder + +abstract sig Target { } +sig Addr extends Target { } +abstract sig Name extends Target { } + +sig Alias, Group extends Name { } + +sig Book { + names: set Name, + addr: names->some Target +} { + no n: Name | n in n.^addr + all a: Alias | lone a.addr +} + +pred add [b, b': Book, n: Name, t: Target] { b'.addr = b.addr + n->t } +pred del [b, b': Book, n: Name, t: Target] { b'.addr = b.addr - n->t } +fun lookup [b: Book, n: Name] : set Addr { n.^(b.addr) & Addr } + +pred init [b: Book] { no b.addr } + +fact traces { + init [first] + all b: Book-last | + let b' = b.next | + some n: Name, t: Target | + add [b, b', n, t] or del [b, b', n, t] +} + +------------------------------------------------------ + +assert delUndoesAdd { + all b, b', b'': Book, n: Name, t: Target | + no n.(b.addr) and add [b, b', n, t] and del [b', b'', n, t] + implies + b.addr = b''.addr +} + +// This should not find any counterexample. +check delUndoesAdd for 3 + +------------------------------------------------------ + +assert addIdempotent { + all b, b', b'': Book, n: Name, t: Target | + add [b, b', n, t] and add [b', b'', n, t] + implies + b'.addr = b''.addr +} + +// This should not find any counterexample. +check addIdempotent for 3 + +------------------------------------------------------ + +assert addLocal { + all b, b': Book, n, n': Name, t: Target | + add [b, b', n, t] and n != n' + implies + lookup [b, n'] = lookup [b', n'] +} + +// This should not find any counterexample. +check addLocal for 3 but 2 Book + +------------------------------------------------------ + +assert lookupYields { + all b: Book, n: b.names | some lookup [b,n] +} + +// This shows a counterexample similar to Fig 2.16 +check lookupYields for 3 but 4 Book diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter2/addressBook3c.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter2/addressBook3c.als new file mode 100644 index 00000000..284f8c36 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter2/addressBook3c.als @@ -0,0 +1,81 @@ +module tour/addressBook3c ----- Page 27 + +open util/ordering [Book] as BookOrder + +abstract sig Target { } +sig Addr extends Target { } +abstract sig Name extends Target { } + +sig Alias, Group extends Name { } + +sig Book { + names: set Name, + addr: names->some Target +} { + no n: Name | n in n.^addr + all a: Alias | lone a.addr +} + +pred add [b, b': Book, n: Name, t: Target] { + t in Addr or some lookup [b, Name&t] + b'.addr = b.addr + n->t +} + +pred del [b, b': Book, n: Name, t: Target] { b'.addr = b.addr - n->t } + +fun lookup [b: Book, n: Name] : set Addr { n.^(b.addr) & Addr } + +pred init [b: Book] { no b.addr } + +fact traces { + init [first] + all b: Book-last | + let b' = b.next | + some n: Name, t: Target | + add [b, b', n, t] or del [b, b', n, t] +} + +------------------------------------------------------ + +assert delUndoesAdd { + all b, b', b'': Book, n: Name, t: Target | + no n.(b.addr) and add [b, b', n, t] and del [b', b'', n, t] + implies + b.addr = b''.addr +} + +// This should not find any counterexample. +check delUndoesAdd for 3 + +------------------------------------------------------ + +assert addIdempotent { + all b, b', b'': Book, n: Name, t: Target | + add [b, b', n, t] and add [b', b'', n, t] + implies + b'.addr = b''.addr +} + +// This should not find any counterexample. +check addIdempotent for 3 + +------------------------------------------------------ + +assert addLocal { + all b, b': Book, n, n': Name, t: Target | + add [b, b', n, t] and n != n' + implies + lookup [b, n'] = lookup [b', n'] +} + +// This should not find any counterexample. +check addLocal for 3 but 2 Book + +------------------------------------------------------ + +assert lookupYields { + all b: Book, n: b.names | some lookup [b,n] +} + +// This shows a counterexample similar to Fig 2.17 +check lookupYields for 3 but 4 Book diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter2/addressBook3d.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter2/addressBook3d.als new file mode 100644 index 00000000..aceac514 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter2/addressBook3d.als @@ -0,0 +1,87 @@ +module tour/addressBook3d ----- this is the final model in fig 2.18 + +open util/ordering [Book] as BookOrder + +abstract sig Target { } +sig Addr extends Target { } +abstract sig Name extends Target { } + +sig Alias, Group extends Name { } + +sig Book { + names: set Name, + addr: names->some Target +} { + no n: Name | n in n.^addr + all a: Alias | lone a.addr +} + +pred add [b, b': Book, n: Name, t: Target] { + t in Addr or some lookup [b, Name&t] + b'.addr = b.addr + n->t +} + +pred del [b, b': Book, n: Name, t: Target] { + no b.addr.n or some n.(b.addr) - t + b'.addr = b.addr - n->t +} + +fun lookup [b: Book, n: Name] : set Addr { n.^(b.addr) & Addr } + +pred init [b: Book] { no b.addr } + +fact traces { + init [first] + all b: Book-last | + let b' = b.next | + some n: Name, t: Target | + add [b, b', n, t] or del [b, b', n, t] +} + +------------------------------------------------------ + +assert delUndoesAdd { + all b, b', b'': Book, n: Name, t: Target | + no n.(b.addr) and add [b, b', n, t] and del [b', b'', n, t] + implies + b.addr = b''.addr +} + +// This should not find any counterexample. +check delUndoesAdd for 3 + +------------------------------------------------------ + +assert addIdempotent { + all b, b', b'': Book, n: Name, t: Target | + add [b, b', n, t] and add [b', b'', n, t] + implies + b'.addr = b''.addr +} + +// This should not find any counterexample. +check addIdempotent for 3 + +------------------------------------------------------ + +assert addLocal { + all b, b': Book, n, n': Name, t: Target | + add [b, b', n, t] and n != n' + implies + lookup [b, n'] = lookup [b', n'] +} + +// This should not find any counterexample. +check addLocal for 3 but 2 Book + +------------------------------------------------------ + +assert lookupYields { + all b: Book, n: b.names | some lookup [b,n] +} + +// This should not find any counterexample. +check lookupYields for 3 but 4 Book + +// This should not find any counterexample. +check lookupYields for 6 diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter2/theme.thm b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter2/theme.thm new file mode 100644 index 00000000..2b839993 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter2/theme.thm @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter4/filesystem.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter4/filesystem.als new file mode 100644 index 00000000..c45e776b --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter4/filesystem.als @@ -0,0 +1,28 @@ +module chapter4/filesystem ----- The model from page 125 + +abstract sig Object {} + +sig Dir extends Object {contents: set Object} + +one sig Root extends Dir { } + +sig File extends Object {} + +fact { + Object in Root.*contents + } + +assert SomeDir { + all o: Object - Root | some contents.o + } +check SomeDir // This assertion is valid + +assert RootTop { + no o: Object | Root in o.contents + } +check RootTop // This assertion should produce a counterexample + +assert FileInDir { + all f: File | some contents.f + } +check FileInDir // This assertion is valid diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter4/grandpa1.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter4/grandpa1.als new file mode 100644 index 00000000..c05c85c9 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter4/grandpa1.als @@ -0,0 +1,44 @@ +module language/grandpa1 ---- Page 84, 85 + +abstract sig Person { + father: lone Man, + mother: lone Woman + } + +sig Man extends Person { + wife: lone Woman + } + +sig Woman extends Person { + husband: lone Man + } + +fact { + no p: Person | p in p.^(mother+father) + wife = ~husband + } + +assert NoSelfFather { + no m: Man | m = m.father + } + +// This should not find any counterexample. +check NoSelfFather + +fun grandpas [p: Person] : set Person { + p.(mother+father).father + } + +pred ownGrandpa [p: Person] { + p in p.grandpas + } + +// This should not find any instance. +run ownGrandpa for 4 Person + +assert NoSelfGrandpa { + no p: Person | p in p.grandpas + } + +// This should not find any counterexample +check NoSelfGrandpa for 4 Person diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter4/grandpa2.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter4/grandpa2.als new file mode 100644 index 00000000..08dda731 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter4/grandpa2.als @@ -0,0 +1,38 @@ +module language/grandpa2 ---- Page 86 + +abstract sig Person { + father: lone Man, + mother: lone Woman + } + +sig Man extends Person { + wife: lone Woman + } + +sig Woman extends Person { + husband: lone Man + } + +fact { + no p: Person | p in p.^(mother+father) + wife = ~husband + } + +assert NoSelfFather { + no m: Man | m = m.father + } + +// This should not find any counterexample. +check NoSelfFather + +fun grandpas [p: Person] : set Person { + let parent = mother + father + father.wife + mother.husband | + p.parent.parent & Man + } + +pred ownGrandpa [p: Person] { + p in p.grandpas + } + +// This generates an instance similar to Fig 4.2 +run ownGrandpa for 4 Person diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter4/grandpa3.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter4/grandpa3.als new file mode 100644 index 00000000..d9147cbf --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter4/grandpa3.als @@ -0,0 +1,70 @@ +module language/grandpa3 ---- the final model in fig 4.4 + +abstract sig Person { + father: lone Man, + mother: lone Woman + } + +sig Man extends Person { + wife: lone Woman + } + +sig Woman extends Person { + husband: lone Man + } + +fact Biology { + no p: Person | p in p.^(mother+father) + } + +fact Terminology { + wife = ~husband + } + +fact SocialConvention { + no (wife+husband) & ^(mother+father) + } + +------------------------------------------ + +assert NoSelfFather { + no m: Man | m = m.father + } + +// This should not find any counterexample. +check NoSelfFather + +------------------------------------------ + +fun grandpas [p: Person] : set Person { + let parent = mother + father + father.wife + mother.husband | + p.parent.parent & Man + } + +pred ownGrandpa [p: Person] { + p in p.grandpas + } + +// This generates an instance similar to Fig 4.3 +run ownGrandpa for 4 Person + +------------------------------------------ + +pred SocialConvention1 { + no (wife + husband) & ^(mother + father) + } + +pred SocialConvention2 { + let parent = mother + father { + no m: Man | some m.wife and m.wife in m.*parent.mother + no w: Woman | some w.husband and w.husband in w.*parent.father + } + } + +// This assertion was described on page 90. +assert Same { + SocialConvention1 iff SocialConvention2 + } + +// This should not find any counterexample +check Same diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter4/lights.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter4/lights.als new file mode 100644 index 00000000..24997705 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter4/lights.als @@ -0,0 +1,41 @@ +module chapter4/lights ----- The model from page 127 + +abstract sig Color {} + +one sig Red, Yellow, Green extends Color {} + +fun colorSequence: Color -> Color { + Color <: iden + Red->Green + Green->Yellow + Yellow->Red + } + +sig Light {} +sig LightState {color: Light -> one Color} +sig Junction {lights: set Light} + +fun redLights [s: LightState]: set Light { s.color.Red } + +pred mostlyRed [s: LightState, j: Junction] { + lone j.lights - redLights[s] + } + +pred trans [s, s': LightState, j: Junction] { + lone x: j.lights | s.color[x] != s'.color[x] + all x: j.lights | + let step = s.color[x] -> s'.color[x] { + step in colorSequence + step in Red->(Color-Red) => j.lights in redLights[s] + } + } + +assert Safe { + all s, s': LightState, j: Junction | + mostlyRed [s, j] and trans [s, s', j] => mostlyRed [s', j] + } + +check Safe for 3 but 1 Junction + +//assert ColorSequenceDeterministic { +// all c: Color | lone c.colorSequence +// } +// +//check ColorSequenceDeterministic diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter5/addressBook.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter5/addressBook.als new file mode 100644 index 00000000..5d7dee56 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter5/addressBook.als @@ -0,0 +1,26 @@ +module chapter5/addressBook --- the model in fig 5.1 + +abstract sig Target {} + +sig Addr extends Target {} +sig Name extends Target {} +sig Book {addr: Name -> Target} + +fact Acyclic {all b: Book | no n: Name | n in n.^(b.addr)} + +pred add [b, b': Book, n: Name, t: Target] { + b'.addr = b.addr + n -> t + } + +// This command should produce an instance similar to Fig 5.2 +run add for 3 but 2 Book + +fun lookup [b: Book, n: Name]: set Addr {n.^(b.addr) & Addr} + +assert addLocal { + all b,b': Book, n,n': Name, t: Target | + add [b,b',n,t] and n != n' => lookup [b,n'] = lookup [b',n'] + } + +// This command should produce a counterexample similar to Fig 5.3 +check addLocal for 3 but 2 Book diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter5/lists.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter5/lists.als new file mode 100644 index 00000000..79cbd0da --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter5/lists.als @@ -0,0 +1,23 @@ +module chapter5/lists ---- page 157 + +some sig Element {} + +abstract sig List {} +one sig EmptyList extends List {} +sig NonEmptyList extends List { + element: Element, + rest: List + } + +fact ListGenerator { + all list: List, e: Element | + some list': List | list'.rest = list and list'.element = e + } + +assert FalseAssertion { + all list: List | list != list + } + +// This check finds no counterexample since +// the only possible counterexamples are infinite. +check FalseAssertion diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter5/sets1.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter5/sets1.als new file mode 100644 index 00000000..dc998820 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter5/sets1.als @@ -0,0 +1,16 @@ +module chapter5/sets1 ----- page 156 + +sig Set { + elements: set Element +} + +sig Element {} + +assert Closed { + all s0, s1: Set | + some s2: Set | + s2.elements = s0.elements + s1.elements + } + +// This check should produce a counterexample +check Closed diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter5/sets2.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter5/sets2.als new file mode 100644 index 00000000..265a1e64 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter5/sets2.als @@ -0,0 +1,21 @@ +module chapter5/sets2 ----- page 157 + +sig Set { + elements: set Element +} + +sig Element {} + +assert Closed { + all s0, s1: Set | + some s2: Set | + s2.elements = s0.elements + s1.elements + } + +fact SetGenerator { + some s: Set | no s.elements + all s: Set, e: Element | some s': Set | s'.elements = s.elements + e + } + +// This check should not produce a counterexample +check Closed for 4 Element, 16 Set diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter6/hotel.thm b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter6/hotel.thm new file mode 100644 index 00000000..acdb8d09 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter6/hotel.thm @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter6/hotel1.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter6/hotel1.als new file mode 100644 index 00000000..22610689 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter6/hotel1.als @@ -0,0 +1,101 @@ +module chapter6/hotel1 --- the model up to the top of page 191 + +open util/ordering[Time] as to +open util/ordering[Key] as ko + +sig Key {} +sig Time {} + +sig Room { + keys: set Key, + currentKey: keys one -> Time + } + +fact DisjointKeySets { + -- each key belongs to at most one room + Room<:keys in Room lone-> Key + } + +one sig FrontDesk { + lastKey: (Room -> lone Key) -> Time, + occupant: (Room -> Guest) -> Time + } + +sig Guest { + keys: Key -> Time + } + +fun nextKey [k: Key, ks: set Key]: set Key { + min [k.nexts & ks] + } + +pred init [t: Time] { + no Guest.keys.t + no FrontDesk.occupant.t + all r: Room | FrontDesk.lastKey.t [r] = r.currentKey.t + } + +pred entry [t, t': Time, g: Guest, r: Room, k: Key] { + k in g.keys.t + let ck = r.currentKey | + (k = ck.t and ck.t' = ck.t) or + (k = nextKey[ck.t, r.keys] and ck.t' = k) + noRoomChangeExcept [t, t', r] + noGuestChangeExcept [t, t', none] + noFrontDeskChange [t, t'] + } + +pred noFrontDeskChange [t, t': Time] { + FrontDesk.lastKey.t = FrontDesk.lastKey.t' + FrontDesk.occupant.t = FrontDesk.occupant.t' + } + +pred noRoomChangeExcept [t, t': Time, rs: set Room] { + all r: Room - rs | r.currentKey.t = r.currentKey.t' + } + +pred noGuestChangeExcept [t, t': Time, gs: set Guest] { + all g: Guest - gs | g.keys.t = g.keys.t' + } + +pred checkout [t, t': Time, g: Guest] { + let occ = FrontDesk.occupant { + some occ.t.g + occ.t' = occ.t - Room ->g + } + FrontDesk.lastKey.t = FrontDesk.lastKey.t' + noRoomChangeExcept [t, t', none] + noGuestChangeExcept [t, t', none] + } + +pred checkin [t, t': Time, g: Guest, r: Room, k: Key] { + g.keys.t' = g.keys.t + k + let occ = FrontDesk.occupant { + no occ.t [r] + occ.t' = occ.t + r -> g + } + let lk = FrontDesk.lastKey { + lk.t' = lk.t ++ r -> k + k = nextKey [lk.t [r], r.keys] + } + noRoomChangeExcept [t, t', none] + noGuestChangeExcept [t, t', g] + } + +fact traces { + init [first] + all t: Time-last | let t' = t.next | + some g: Guest, r: Room, k: Key | + entry [t, t', g, r, k] + or checkin [t, t', g, r, k] + or checkout [t, t', g] + } + +assert NoBadEntry { + all t: Time, r: Room, g: Guest, k: Key | + let t' = t.next, o = FrontDesk.occupant.t[r] | + entry [t, t', g, r, k] and some o => g in o + } + +// This generates a counterexample similar to Fig 6.6 +check NoBadEntry for 3 but 2 Room, 2 Guest, 5 Time diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter6/hotel2.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter6/hotel2.als new file mode 100644 index 00000000..09378f2e --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter6/hotel2.als @@ -0,0 +1,110 @@ +module chapter6/hotel2 --- the final model in Fig 6.7 + +open util/ordering[Time] as to +open util/ordering[Key] as ko + +sig Key {} +sig Time {} + +sig Room { + keys: set Key, + currentKey: keys one -> Time + } + +fact DisjointKeySets { + -- each key belongs to at most one room + Room<:keys in Room lone-> Key + } + +one sig FrontDesk { + lastKey: (Room -> lone Key) -> Time, + occupant: (Room -> Guest) -> Time + } + +sig Guest { + keys: Key -> Time + } + +fun nextKey [k: Key, ks: set Key]: set Key { + min [k.nexts & ks] + } + +pred init [t: Time] { + no Guest.keys.t + no FrontDesk.occupant.t + all r: Room | FrontDesk.lastKey.t [r] = r.currentKey.t + } + +pred entry [t, t': Time, g: Guest, r: Room, k: Key] { + k in g.keys.t + let ck = r.currentKey | + (k = ck.t and ck.t' = ck.t) or + (k = nextKey[ck.t, r.keys] and ck.t' = k) + noRoomChangeExcept [t, t', r] + noGuestChangeExcept [t, t', none] + noFrontDeskChange [t, t'] + } + +pred noFrontDeskChange [t, t': Time] { + FrontDesk.lastKey.t = FrontDesk.lastKey.t' + FrontDesk.occupant.t = FrontDesk.occupant.t' + } + +pred noRoomChangeExcept [t, t': Time, rs: set Room] { + all r: Room - rs | r.currentKey.t = r.currentKey.t' + } + +pred noGuestChangeExcept [t, t': Time, gs: set Guest] { + all g: Guest - gs | g.keys.t = g.keys.t' + } + +pred checkout [t, t': Time, g: Guest] { + let occ = FrontDesk.occupant { + some occ.t.g + occ.t' = occ.t - Room ->g + } + FrontDesk.lastKey.t = FrontDesk.lastKey.t' + noRoomChangeExcept [t, t', none] + noGuestChangeExcept [t, t', none] + } + +pred checkin [t, t': Time, g: Guest, r: Room, k: Key] { + g.keys.t' = g.keys.t + k + let occ = FrontDesk.occupant { + no occ.t [r] + occ.t' = occ.t + r -> g + } + let lk = FrontDesk.lastKey { + lk.t' = lk.t ++ r -> k + k = nextKey [lk.t [r], r.keys] + } + noRoomChangeExcept [t, t', none] + noGuestChangeExcept [t, t', g] + } + +fact traces { + init [first] + all t: Time-last | let t' = t.next | + some g: Guest, r: Room, k: Key | + entry [t, t', g, r, k] + or checkin [t, t', g, r, k] + or checkout [t, t', g] + } + +fact NoIntervening { + all t: Time-last | let t' = t.next, t" = t'.next | + all g: Guest, r: Room, k: Key | + checkin [t, t', g, r, k] => (entry [t', t", g, r, k] or no t") + } + +assert NoBadEntry { + all t: Time, r: Room, g: Guest, k: Key | + let t' = t.next, o = FrontDesk.occupant.t[r] | + entry [t, t', g, r, k] and some o => g in o + } + +// After adding the NoIntervening fact, +// these commands no longer generate counterexamples +check NoBadEntry for 3 but 2 Room, 2 Guest, 5 Time +check NoBadEntry for 3 but 3 Room, 3 Guest, 7 Time +check NoBadEntry for 5 but 3 Room, 3 Guest, 9 Time diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter6/hotel3.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter6/hotel3.als new file mode 100644 index 00000000..b3ad4b49 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter6/hotel3.als @@ -0,0 +1,92 @@ +module chapter6/hotel3 --- model in Fig 6.10 without the NonIntervening fact + +open util/ordering[Time] as to +open util/ordering[Key] as ko + +sig Key, Time {} + +sig Room { + keys: set Key, + currentKey: keys one -> Time + } + +fact { + Room <: keys in Room lone -> Key + } + +one sig FrontDesk { + lastKey: (Room -> lone Key) -> Time, + occupant: (Room -> Guest) -> Time + } + +sig Guest { + keys: Key -> Time + } + +fun nextKey [k: Key, ks: set Key]: set Key { + min [k.nexts & ks] + } + +pred init [t: Time] { + no Guest.keys.t + no FrontDesk.occupant.t + all r: Room | FrontDesk.lastKey.t [r] = r.currentKey.t + } + +abstract sig Event { + pre, post: Time, + guest: Guest + } + +abstract sig RoomKeyEvent extends Event { + room: Room, + key: Key + } + +sig Entry extends RoomKeyEvent { } { + key in guest.keys.pre + let ck = room.currentKey | + (key = ck.pre and ck.post = ck.pre) or + (key = nextKey[ck.pre, room.keys] and ck.post = key) + currentKey.post = currentKey.pre ++ room->key + } + +sig Checkin extends RoomKeyEvent { } { + keys.post = keys.pre + guest -> key + let occ = FrontDesk.occupant { + no occ.pre [room] + occ.post = occ.pre + room -> guest + } + let lk = FrontDesk.lastKey { + lk.post = lk.pre ++ room -> key + key = nextKey [lk.pre [room], room.keys] + } + } + +sig Checkout extends Event { } { + let occ = FrontDesk.occupant { + some occ.pre.guest + occ.post = occ.pre - Room -> guest + } + } + +fact Traces { + init [first] + all t: Time-last | + let t' = t.next | + some e: Event { + e.pre = t and e.post = t' + currentKey.t != currentKey.t' => e in Entry + occupant.t != occupant.t' => e in Checkin + Checkout + (lastKey.t != lastKey.t' or keys.t != keys.t') => e in Checkin + } + } + +assert NoBadEntry { + all e: Entry | + let o=FrontDesk.occupant.(e.pre) [e.room] | + some o => e.guest in o + } + +// This generates a counterexample similar to Fig 6.13 +check NoBadEntry for 5 but 3 Room, 3 Guest, 5 Time, 4 Event diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter6/hotel4.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter6/hotel4.als new file mode 100644 index 00000000..35f3566b --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter6/hotel4.als @@ -0,0 +1,103 @@ +module chapter6/hotel4 --- model in Fig 6.10 with the NonIntervening fact + +open util/ordering[Time] as to +open util/ordering[Key] as ko + +sig Key, Time {} + +sig Room { + keys: set Key, + currentKey: keys one -> Time + } + +fact { + Room <: keys in Room lone -> Key + } + +one sig FrontDesk { + lastKey: (Room -> lone Key) -> Time, + occupant: (Room -> Guest) -> Time + } + +sig Guest { + keys: Key -> Time + } + +fun nextKey [k: Key, ks: set Key]: set Key { + min [k.nexts & ks] + } + +pred init [t: Time] { + no Guest.keys.t + no FrontDesk.occupant.t + all r: Room | FrontDesk.lastKey.t [r] = r.currentKey.t + } + +abstract sig Event { + pre, post: Time, + guest: Guest + } + +abstract sig RoomKeyEvent extends Event { + room: Room, + key: Key + } + +sig Entry extends RoomKeyEvent { } { + key in guest.keys.pre + let ck = room.currentKey | + (key = ck.pre and ck.post = ck.pre) or + (key = nextKey[ck.pre, room.keys] and ck.post = key) + currentKey.post = currentKey.pre ++ room->key + } + +sig Checkin extends RoomKeyEvent { } { + keys.post = keys.pre + guest -> key + let occ = FrontDesk.occupant { + no occ.pre [room] + occ.post = occ.pre + room -> guest + } + let lk = FrontDesk.lastKey { + lk.post = lk.pre ++ room -> key + key = nextKey [lk.pre [room], room.keys] + } + } + +sig Checkout extends Event { } { + let occ = FrontDesk.occupant { + some occ.pre.guest + occ.post = occ.pre - Room -> guest + } + } + +fact Traces { + init [first] + all t: Time-last | + let t' = t.next | + some e: Event { + e.pre = t and e.post = t' + currentKey.t != currentKey.t' => e in Entry + occupant.t != occupant.t' => e in Checkin + Checkout + (lastKey.t != lastKey.t' or keys.t != keys.t') => e in Checkin + } + } + +assert NoBadEntry { + all e: Entry | + let o=FrontDesk.occupant.(e.pre) [e.room] | + some o => e.guest in o + } + +fact NoIntervening { + all c: Checkin | + c.post = last + or some e: Entry { + e.pre = c.post + e.room = c.room + e.guest = c.guest + } + } + +// After adding the NoIntervening fact, +// this command no longer generates a counterexample +check NoBadEntry for 5 but 3 Room, 3 Guest, 9 Time, 8 Event diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter6/mediaAssets.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter6/mediaAssets.als new file mode 100644 index 00000000..668b3130 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter6/mediaAssets.als @@ -0,0 +1,117 @@ +module chapter6/mediaAssets + +sig ApplicationState { + catalogs: set Catalog, + catalogState: catalogs -> one CatalogState, + currentCatalog: catalogs, + buffer: set Asset + } + +sig Catalog, Asset {} + +sig CatalogState { + assets: set Asset, + disj hidden, showing: set assets, + selection: set assets + Undefined + } { + hidden+showing = assets + } + +one sig Undefined {} + +pred catalogInv [cs: CatalogState] { + cs.selection = Undefined or (some cs.selection and cs.selection in cs.showing) + } + +pred appInv [xs: ApplicationState] { + all cs: xs.catalogs | catalogInv [xs.catalogState[cs]] + } + +pred showSelected [cs, cs': CatalogState] { + cs.selection != Undefined + cs'.showing = cs.selection + cs'.selection = cs.selection + cs'.assets = cs.assets + } + +pred hideSelected [cs, cs': CatalogState] { + cs.selection != Undefined + cs'.hidden = cs.hidden + cs.selection + cs'.selection = Undefined + cs'.assets = cs.assets + } + +pred cut [xs, xs': ApplicationState] { + let cs = xs.currentCatalog.(xs.catalogState), sel = cs.selection { + sel != Undefined + xs'.buffer = sel + some cs': CatalogState { + cs'.assets = cs.assets - sel + cs'.showing = cs.showing - sel + cs'.selection = Undefined + xs'.catalogState = xs.catalogState ++ xs.currentCatalog -> cs' + } + } + xs'.catalogs = xs.catalogs + xs'.currentCatalog = xs.currentCatalog + } + +pred paste [xs, xs': ApplicationState] { + let cs = xs.currentCatalog.(xs.catalogState), buf = xs.buffer { + xs'.buffer = buf + some cs': CatalogState { + cs'.assets = cs.assets + buf + cs'.showing = cs.showing + (buf - cs.assets) + cs'.selection = buf - cs.assets + xs'.catalogState = xs.catalogState ++ xs.currentCatalog -> cs' + } + } + xs'.catalogs = xs.catalogs + xs'.currentCatalog = xs.currentCatalog + } + +assert HidePreservesInv { + all cs, cs': CatalogState | + catalogInv [cs] and hideSelected [cs, cs'] => catalogInv [cs'] + } + +// This check should not find any counterexample +check HidePreservesInv + +pred sameApplicationState [xs, xs': ApplicationState] { + xs'.catalogs = xs.catalogs + all c: xs.catalogs | sameCatalogState [c.(xs.catalogState), c.(xs'.catalogState)] + xs'.currentCatalog = xs.currentCatalog + xs'.buffer = xs.buffer + } + +pred sameCatalogState [cs, cs': CatalogState] { + cs'.assets = cs.assets + cs'.showing = cs.showing + cs'.selection = cs.selection + } + +assert CutPaste { + all xs, xs', xs": ApplicationState | + (appInv [xs] and cut [xs, xs'] and paste [xs', xs"]) => sameApplicationState [xs, xs"] + } + +// This check should find a counterexample +check CutPaste + +assert PasteCut { + all xs, xs', xs": ApplicationState | + (appInv [xs] and paste [xs, xs'] and cut [xs', xs"]) => sameApplicationState [xs, xs"] + } + +// This check should find a counterexample +check PasteCut + +assert PasteNotAffectHidden { + all xs, xs': ApplicationState | + (appInv [xs] and paste [xs, xs']) => + let c = xs.currentCatalog | xs'.catalogState[c].hidden = xs.catalogState[c].hidden + } + +// This check should not find any counterexample +check PasteNotAffectHidden diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter6/memory/abstractMemory.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter6/memory/abstractMemory.als new file mode 100644 index 00000000..3d676d50 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter6/memory/abstractMemory.als @@ -0,0 +1,33 @@ +module chapter6/memory/abstractMemory [Addr, Data] ----- the model from page 217 + +sig Memory { + data: Addr -> lone Data + } + +pred init [m: Memory] { + no m.data + } + +pred write [m, m': Memory, a: Addr, d: Data] { + m'.data = m.data ++ a -> d + } + +pred read [m: Memory, a: Addr, d: Data] { + let d' = m.data [a] | some d' implies d = d' + } + +fact Canonicalize { + no disj m, m': Memory | m.data = m'.data + } + +// This command should not find any counterexample +WriteRead: check { + all m, m': Memory, a: Addr, d1, d2: Data | + write [m, m', a, d1] and read [m', a, d2] => d1 = d2 + } + +// This command should not find any counterexample +WriteIdempotent: check { + all m, m', m": Memory, a: Addr, d: Data | + write [m, m', a, d] and write [m', m", a, d] => m' = m" + } diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter6/memory/cacheMemory.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter6/memory/cacheMemory.als new file mode 100644 index 00000000..c95fe22b --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter6/memory/cacheMemory.als @@ -0,0 +1,43 @@ +module chapter6/memory/cacheMemory [Addr, Data] ----- the model from page 219 + +sig CacheSystem { + main, cache: Addr -> lone Data + } + +pred init [c: CacheSystem] { + no c.main + c.cache + } + +pred write [c, c': CacheSystem, a: Addr, d: Data] { + c'.main = c.main + c'.cache = c.cache ++ a -> d + } + +pred read [c: CacheSystem, a: Addr, d: Data] { + some d + d = c.cache [a] + } + +pred load [c, c': CacheSystem] { + some addrs: set c.main.Data - c.cache.Data | + c'.cache = c.cache ++ addrs <: c.main + c'.main = c.main + } + +pred flush [c, c': CacheSystem] { + some addrs: some c.cache.Data { + c'.main = c.main ++ addrs <: c.cache + c'.cache = c.cache - addrs -> Data + } + } + +// This command should not find any counterexample +LoadNotObservable: check { + all c, c', c": CacheSystem, a1, a2: Addr, d1, d2, d3: Data | + { + read [c, a2, d2] + write [c, c', a1, d1] + load [c', c"] + read [c", a2, d3] + } implies d3 = (a1=a2 => d1 else d2) + } diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter6/memory/checkCache.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter6/memory/checkCache.als new file mode 100644 index 00000000..8214389b --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter6/memory/checkCache.als @@ -0,0 +1,34 @@ +module chapter6/memory/checkCache [Addr, Data] + +open chapter6/memory/cacheMemory [Addr, Data] as cache +open chapter6/memory/abstractMemory [Addr, Data] as amemory + +fun alpha [c: CacheSystem]: Memory { + {m: Memory | m.data = c.main ++ c.cache} + } + +// This check should not produce a counterexample +ReadOK: check { + // introduction of m, m' ensures that they exist, and gives witnesses if counterexample + all c: CacheSystem, a: Addr, d: Data, m: Memory | + cache/read [c, a, d] and m = alpha [c] => amemory/read [m, a, d] + } + +// This check should not produce a counterexample +WriteOK: check { + all c, c': CacheSystem, a: Addr, d: Data, m, m': Memory | + cache/write [c, c', a, d] and m = alpha [c] and m' = alpha [c'] + => amemory/write [m, m', a, d] + } + +// This check should not produce a counterexample +LoadOK: check { + all c, c': CacheSystem, m, m': Memory | + cache/load [c, c'] and m = alpha [c] and m' = alpha [c'] => m = m' + } + +// This check should not produce a counterexample +FlushOK: check { + all c, c': CacheSystem, m, m': Memory | + cache/flush [c, c'] and m = alpha [c] and m' = alpha [c'] => m = m' + } diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter6/memory/checkFixedSize.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter6/memory/checkFixedSize.als new file mode 100644 index 00000000..20d291d1 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter6/memory/checkFixedSize.als @@ -0,0 +1,28 @@ +module chapter6/memory/checkFixedSize [Addr, Data] + +open chapter6/memory/fixedSizeMemory_H [Addr, Data] as fmemory +open chapter6/memory/abstractMemory [Addr, Data] as amemory + +// define abstraction function from history-extended concrete state to abstract state +pred alpha [fm: fmemory/Memory_H, am: amemory/Memory] { + am.data = fm.data - (fm.unwritten -> Data) + } + +// This check should not find a counterexample +initOk: check { + all fm: fmemory/Memory_H, am: amemory/Memory | + fmemory/init [fm] and alpha [fm, am] => amemory/init [am] + } + +// This check should not find a counterexample +readOk: check { + all fm: fmemory/Memory_H, a: Addr, d: Data, am: amemory/Memory | + fmemory/read [fm, a, d] and alpha [fm, am] => amemory/read [am, a, d] + } + +// This check should not find a counterexample +writeOk: check { + all fm, fm': fmemory/Memory_H, a: Addr, d: Data, am, am': amemory/Memory | + fmemory/write [fm, fm', a, d] and alpha [fm, am] and alpha [fm', am'] + implies amemory/write [am, am', a, d] + } diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter6/memory/fixedSizeMemory.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter6/memory/fixedSizeMemory.als new file mode 100644 index 00000000..df6ab2a9 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter6/memory/fixedSizeMemory.als @@ -0,0 +1,21 @@ +module chapter6/memory/fixedSizeMemory [Addr, Data] + +sig Memory { + data: Addr -> one Data + } + +pred init [m: Memory] { + // This predicate is empty in order to allow non-deterministic initialization + } + +pred write [m, m': Memory, a: Addr, d: Data] { + m'.data = m.data ++ a -> d + } + +pred read [m: Memory, a: Addr, d: Data] { + d = m.data [a] + } + +fact Canonicalize { + no disj m, m': Memory | m.data = m'.data + } diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter6/memory/fixedSizeMemory_H.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter6/memory/fixedSizeMemory_H.als new file mode 100644 index 00000000..15785991 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter6/memory/fixedSizeMemory_H.als @@ -0,0 +1,21 @@ +module chapter6/memory/fixedSizeMemory_H [Addr, Data] + +open chapter6/memory/fixedSizeMemory [Addr, Data] as memory + +sig Memory_H extends memory/Memory { + unwritten: set Addr + } + +pred init [m: Memory_H] { + memory/init [m] + m.unwritten = Addr + } + +pred read [m: Memory_H, a: Addr, d: Data] { + memory/read [m, a, d] + } + +pred write [m, m': Memory_H, a: Addr, d: Data] { + memory/write [m, m', a, d] + m'.unwritten = m.unwritten - a + } diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter6/ringElection.thm b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter6/ringElection.thm new file mode 100644 index 00000000..0d172180 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter6/ringElection.thm @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter6/ringElection1.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter6/ringElection1.als new file mode 100644 index 00000000..043b6a07 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter6/ringElection1.als @@ -0,0 +1,57 @@ +module chapter6/ringElection1 --- the version up to the top of page 181 + +open util/ordering[Time] as TO +open util/ordering[Process] as PO + +sig Time {} + +sig Process { + succ: Process, + toSend: Process -> Time, + elected: set Time + } + +fact ring { + all p: Process | Process in p.^succ + } + +pred init [t: Time] { + all p: Process | p.toSend.t = p + } + +pred step [t, t': Time, p: Process] { + let from = p.toSend, to = p.succ.toSend | + some id: from.t { + from.t' = from.t - id + to.t' = to.t + (id - p.succ.prevs) + } + } + +fact defineElected { + no elected.first + all t: Time-first | elected.t = {p: Process | p in p.toSend.t - p.toSend.(t.prev)} + } + +fact traces { + init [first] + all t: Time-last | + let t' = t.next | + all p: Process | + step [t, t', p] or step [t, t', succ.p] or skip [t, t', p] + } + +pred skip [t, t': Time, p: Process] { + p.toSend.t = p.toSend.t' + } + +pred show { some elected } +run show for 3 Process, 4 Time +// This generates an instance similar to Fig 6.4 + +assert AtMostOneElected { lone elected.Time } +check AtMostOneElected for 3 Process, 7 Time +// This should not find any counterexample + +assert AtLeastOneElected { some t: Time | some elected.t } +check AtLeastOneElected for 3 Process, 7 Time +// This generates a counterexample in which nothing happens diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter6/ringElection2.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter6/ringElection2.als new file mode 100644 index 00000000..16bee928 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/book/chapter6/ringElection2.als @@ -0,0 +1,74 @@ +module chapter6/ringElection2 --- the final version (as depicted in Fig 6.1) + +open util/ordering[Time] as TO +open util/ordering[Process] as PO + +sig Time {} + +sig Process { + succ: Process, + toSend: Process -> Time, + elected: set Time + } + +fact ring { + all p: Process | Process in p.^succ + } + +pred init [t: Time] { + all p: Process | p.toSend.t = p + } + +pred step [t, t': Time, p: Process] { + let from = p.toSend, to = p.succ.toSend | + some id: from.t { + from.t' = from.t - id + to.t' = to.t + (id - p.succ.prevs) + } + } + +fact defineElected { + no elected.first + all t: Time-first | elected.t = {p: Process | p in p.toSend.t - p.toSend.(t.prev)} + } + +fact traces { + init [first] + all t: Time-last | + let t' = t.next | + all p: Process | + step [t, t', p] or step [t, t', succ.p] or skip [t, t', p] + } + +pred skip [t, t': Time, p: Process] { + p.toSend.t = p.toSend.t' + } + +pred show { some elected } +run show for 3 Process, 4 Time +// This generates an instance similar to Fig 6.4 + +assert AtMostOneElected { lone elected.Time } +check AtMostOneElected for 3 Process, 7 Time +// This should not find any counterexample + +pred progress { + all t: Time - TO/last | + let t' = TO/next [t] | + some Process.toSend.t => some p: Process | not skip [t, t', p] + } + +assert AtLeastOneElected { progress => some elected.Time } +check AtLeastOneElected for 3 Process, 7 Time +// This should not find any counterexample + +pred looplessPath { no disj t, t': Time | toSend.t = toSend.t' } + +// This produces an instance +run looplessPath for 3 Process, 12 Time + +// This does not produce an instance +run looplessPath for 3 Process, 13 Time + +// Therefore, we can conclude that a scope of 12 for Time is +// sufficient to reach all states of the protocol for a three-node ring. diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/algorithms/dijkstra.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/algorithms/dijkstra.als new file mode 100644 index 00000000..ab342811 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/algorithms/dijkstra.als @@ -0,0 +1,120 @@ +module examples/algorithms/dijkstra + +/* + * Models how mutexes are grabbed and released by processes, and + * how Dijkstra's mutex ordering criterion can prevent deadlocks. + * + * For a detailed description, see: + * E. W. Dijkstra, Cooperating sequential processes. Technological + * University, Eindhoven, The Netherlands, September 1965. + * Reprinted in Programming Languages, F. Genuys, Ed., Academic + * Press, New York, 1968, 43-112. + * + * Acknowledgements to Ulrich Geilmann for finding and fixing a bug + * in the GrabMutex predicate. + * + */ + +open util/ordering [State] as so +open util/ordering [Mutex] as mo + +sig Process {} +sig Mutex {} + +sig State { holds, waits: Process -> Mutex } + + +pred Initial [s: State] { no s.holds + s.waits } + +pred IsFree [s: State, m: Mutex] { + // no process holds this mutex + no m.~(s.holds) + // all p: Process | m !in p.(this.holds) +} + +pred IsStalled [s: State, p: Process] { some p.(s.waits) } + +pred GrabMutex [s: State, p: Process, m: Mutex, s': State] { + // a process can only act if it is not + // waiting for a mutex + !s.IsStalled[p] + // can only grab a mutex we do not yet hold + m !in p.(s.holds) + // mutexes are grabbed in order + all m': p.(s.holds) | mo/lt[m',m] + s.IsFree[m] => { + // if the mutex is free, we now hold it, + // and do not become stalled + p.(s'.holds) = p.(s.holds) + m + no p.(s'.waits) + } else { + // if the mutex was not free, + // we still hold the same mutexes we held, + // and are now waiting on the mutex + // that we tried to grab. + p.(s'.holds) = p.(s.holds) + p.(s'.waits) = m + } + all otherProc: Process - p | { + otherProc.(s'.holds) = otherProc.(s.holds) + otherProc.(s'.waits) = otherProc.(s.waits) + } +} + +pred ReleaseMutex [s: State, p: Process, m: Mutex, s': State] { + !s.IsStalled[p] + m in p.(s.holds) + p.(s'.holds) = p.(s.holds) - m + no p.(s'.waits) + no m.~(s.waits) => { + no m.~(s'.holds) + no m.~(s'.waits) + } else { + some lucky: m.~(s.waits) | { + m.~(s'.waits) = m.~(s.waits) - lucky + m.~(s'.holds) = lucky + } + } + all mu: Mutex - m { + mu.~(s'.waits) = mu.~(s.waits) + mu.~(s'.holds)= mu.~(s.holds) + } +} + +/** + * for every adjacent (pre,post) pair of States, + * one action happens: either some process grabs a mutex, + * or some process releases a mutex, + * or nothing happens (have to allow this to show deadlocks) + */ +pred GrabOrRelease { + Initial[so/first] && + ( + all pre: State - so/last | let post = so/next [pre] | + (post.holds = pre.holds && post.waits = pre.waits) + || + (some p: Process, m: Mutex | pre.GrabMutex [p, m, post]) + || + (some p: Process, m: Mutex | pre.ReleaseMutex [p, m, post]) + + ) +} + +pred Deadlock { + some Process + some s: State | all p: Process | some p.(s.waits) +} + +assert DijkstraPreventsDeadlocks { + GrabOrRelease => ! Deadlock +} + + +pred ShowDijkstra { + GrabOrRelease && Deadlock + some waits +} + +run Deadlock for 3 expect 1 +run ShowDijkstra for 5 State, 2 Process, 2 Mutex expect 1 +check DijkstraPreventsDeadlocks for 5 State, 5 Process, 4 Mutex expect 0 diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/algorithms/dijkstra.thm b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/algorithms/dijkstra.thm new file mode 100644 index 00000000..3be2d6c2 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/algorithms/dijkstra.thm @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/algorithms/messaging.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/algorithms/messaging.als new file mode 100644 index 00000000..0815fb44 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/algorithms/messaging.als @@ -0,0 +1,228 @@ +module examples/algorithms/messaging + +/* + * Generic messaging among several nodes + * + * By default, messages can be lost (i.e. never become visible to the + * recipient node) or may be arbitrarily delayed. Also, by default + * out-of-order delivery is allowed. + */ + +open util/ordering[Tick] as ord +open util/relation as rel + +sig Node {} + +sig MsgState { + /** Node that sent the message */ + from: Node, + + /** Intended recipient(s) of a message; note that broadcasts are allowed */ + to: set Node +} + +sig Msg { + state: MsgState, + + /** Timestamp: the tick on which the message was sent */ + sentOn: Tick, + + /** tick at which node reads message, if read */ + readOn: Node -> lone Tick +}{ + readOn.Tick in state.to +} + +sig Tick { + /** The state of each node */ + state: Node -> one NodeState, + + /** + * Definition of what each node does on this tick: + * + * Typically, a node would determine + * the messages it sends and its next state, based on its current + * state and the messages it reads. + * + * Messages that the node _can_ read in this tick, i.e. messages available + * for reading at the beginning of this tick. The messages that + * the node actually reads are a subset of this set. Determined by + * constraints in this module. + */ + visible: Node -> Msg, + + /** + * Messages that the node _actually reads_ in this tick. Must be a subset + * of visible. Determined by constraints of the particular system + * that uses this module. + */ + read: Node -> Msg, + + /** + * Messages sent by the node in this tick. They become visible to + * (and can be read by) their recipients on the next tick. + */ + sent: Node -> Msg, + + /** + * Messages available for sending at this tick. A given message + * atom is only used once, and then it gets removed from the available + * set and cannot be used to represent messages sent on subsequent ticks. + * Also, two different nodes cannot send the same message atom. + * So, a message atom represents a particular single physical message + * sent by a given node on a given tick. + */ + available: set Msg, + + /** + * For each node, at each tick, the number of messages it _needs_ to send. + * Used to rule out "proofs" of liveness violations that are caused + * solely by not having enough messages available for sending. + */ + needsToSend: Node -> Msg +} + +fun MsgsSentOnTick[t: Tick]: set Msg { t.sent[Node] } +fun MsgsVisibleOnTick[t: Tick]: set Msg { t.visible[Node] } +fun MsgsReadOnTick[t: Tick]: set Msg { t.read[Node] } + +fact MsgMovementConstraints { + // At the beginning, no messages have been sent yet + no ord/first.visible[Node] + + // Messages sent on a given tick become visible to recipient(s) + // on the subsequent tick. + all pre: Tick - ord/last | + let post = ord/next[pre] | { + // messages sent on this tick are no longer available on subsequent tick + post.available = pre.available - MsgsSentOnTick[pre] + } + + all t: Tick | { + // Messages sent on a tick are taken from the pool of available + // (not-yet-sent) message atoms + MsgsSentOnTick[t] in t.available + + // Timestamps are correct + MsgsSentOnTick[t].sentOn in t + MsgsReadOnTick[t].readOn[Node] in t + + // The only new message atoms are those sent by nodes + MsgsSentOnTick[t] = t.sent[Node] + + all n: Node, m: Msg | + m.readOn[n] = t => m in t.read[n] + // Return addresses are correct + all n: Node | t.sent[n].state.from in n + + // messages sent to a node on a tick become visible to that node on some subseqent tick, + // and permanently stop being visible to that node on the tick after that node reads the message + all n: Node, m: Msg | { + // message starts being visible to node n no earlier than it is sent; + // only messages sent to this node are visible to this node. + (m in t.visible[n] => (n in m.state.to && m.sentOn in ord/prevs[t])) + // message permanently stops being visible immediately after being read + (m in t.read[n] => m !in ord/nexts[t].visible[n]) + } + } +} + +sig NodeState { +} + +fun MsgsLiveOnTick[t: Tick]: set Msg { + Msg - { future: Msg | future.sentOn in ord/nexts[t] } + - { past: Msg | all n: past.state.to | past.readOn[n] in ord/prevs[t] } +} + +pred TicksEquivalent[t1, t2: Tick] { + t1.state = t2.state + some r: (MsgsLiveOnTick[t1] - MsgsVisibleOnTick[t1]) one -> one (MsgsLiveOnTick[t2] - MsgsVisibleOnTick[t2]) | + all m1: dom[r] | let m2 = m1.r | { + m1.(Msg<:state) = m2.state + } + some r: MsgsVisibleOnTick[t1] one -> one MsgsVisibleOnTick[t2] | + all m1: dom[r] | let m2 = m1.r | { + m1.(Msg<:state) = m2.state + } +} + +pred Loop { + some t: Tick - ord/last | TicksEquivalent[t, ord/last] +} + +fact CleanupViz { + // cleans up visualization without precluding any interesting traces + + // all messages must be sent + Msg in Tick.sent[Node] +} + +pred ReadInOrder { + // + // This function ensures that messages are read in order. + // + + // for all pairs of nodes + all n1, n2: Node | + // for all pairs of messages sent from n1 to n2 + all m1, m2: Msg | + { + m1.state.from = n1 + m2.state.from = n1 + m1.state.to = n2 + m2.state.to = n2 + } => { + // if both m1 and m2 are read by n2, *and* + // n2 reads m1 before m2, then m1 must have + // been sent before m2 + (some m1.readOn[n2] && some m2.readOn[n2] && + m1.readOn[n2] in ord/prevs[m2.readOn[n2]]) => + ord/lte[m1.sentOn, m2.sentOn] + } +} + +fact ReadOnlyVisible { read in visible } + +/** + * this function ensures that messages will not + * be lost, i.e. a message send to a node will + * eventually be visible to that node + */ +pred NoLostMessages { + all m: Msg | + (m.sentOn != ord/last) => (all n: m.state.to | + some t: ord/nexts[m.sentOn] | + m in t.visible[n]) +} + +/** + * this function ensures that there will be + * no shortage of messages in the available + * message pool during the trace + */ +pred NoMessageShortage { + all t: Tick - ord/last | + (sum n: Node | # t.needsToSend[n]) =< # t.available +} + +pred SomeState { + # Node > 1 + //# Tick$read > 1 +} + +pred OutOfOrder { + ! ReadInOrder + # Msg = 2 +} + +run SomeState for 2 expect 1 +run OutOfOrder for 4 expect 1 + + + +// DEFINED VARIABLES +// Defined variables are uncalled, no-argument functions. +// They are helpful for getting good visualization. +fun FROM: Msg -> Node {{m: Msg, n: Node | n in m.state.from}} +fun TO: Msg -> Node {{m: Msg, n: Node | n in m.state.to}} diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/algorithms/messaging.thm b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/algorithms/messaging.thm new file mode 100644 index 00000000..53f28d8d --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/algorithms/messaging.thm @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/algorithms/opt_spantree.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/algorithms/opt_spantree.als new file mode 100644 index 00000000..7c129325 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/algorithms/opt_spantree.als @@ -0,0 +1,220 @@ +module examples/algorithms/opt_spantree + +/* + * Direct specification of a distributed spanning tree algorithm + * over arbitrary network topologies + * + * Each process has a parent and a level, both of which are + * initally null. A distinct root node exists at which the + * algorithm starts. In the first step, the root assigns itself + * the level of zero and sends its level to its neighbors. + * Subsequently, if a node reads a message with level k, it sets + * its level to k+1, records the sender of the message as its + * parent, and sends the level k+1 to its neighbors. Once a node + * has set its level and parent, it ignores subsequent messages. + * Eventually, the parent pointers will form a spanning tree, + * rooted at Root. + * + * We model communication through a state-reading model, in which + * nodes can directly read the state of their neighbors. Messages + * are not explicity modelled. This makes no difference for this + * algorithm since once a node sends a message, the state of the + * node stays the same as the contents of the message. + */ + +open util/ordering[Lvl] as lo +open util/ordering[State] as so +open util/graph[Process] as graph + +sig Process { + adj : set Process +} + +one sig Root extends Process {} + +/** + * intuitively, the depth level at which + * a process resides in the spanning tree, + * with the root at level zero + */ +sig Lvl {} + +fact processGraph { + graph/noSelfLoops[adj] // for viz + graph/undirected[adj] // adjacency is symmetric + Process in Root.*adj // everything reachable from root +} + +sig State { + /** + * the set of processes which execute in this state. + * used to allow flexibility in how many processes + * run simultaneously + */ + runs : set Process, + + /** + * the level of a process in this state + */ + lvl: Process -> lone Lvl, + + /** + * who the process thinks is its parent in this state. + * the parent pointers should eventually become + * the spanning tree + */ + parent: Process -> lone Process +} + +/** + * initially, the lvl and parent fields are blank + */ +pred Init { + let fs = so/first | { + no fs.lvl + no fs.parent + } +} + +/** + * simple NOP transition + */ +pred TRNop[p : Process, pre, post: State] { + pre.lvl[p] = post.lvl[p] + pre.parent[p] = post.parent[p] +} + +/** + * preconditions for a process to actually act + * in a certain pre-state + * used to preclude stalling of entire system + * for no reason (see TransIfPossible) + */ +pred TRActPreConds[p : Process, pre : State] { + // can't already have a level + no pre.lvl[p] + // must have a neighbor with a set level so + // p can read it + // Root doesn't need to read value from a + // neighbor + (p = Root || some pre.lvl[p.adj]) +} + +/** + * transition which changes state of a process + */ +pred TRAct[p : Process, pre, post : State] { + // can't already have a level + no pre.lvl[p] + (p = Root) => { + // the root sets its level to + // 0, and has no parent pointer + post.lvl[p] = lo/first + no post.parent[p] + } else { + // choose some adjacent process + // whose level is already set + some adjProc: p.adj | + let nLvl = pre.lvl[adjProc] | { + some nLvl + // p's parent is the adjacent + // process, and p's level is one greater than + // the level of the adjacent process (since + // its one level deeper) + post.lvl[p] = lo/next[nLvl] + post.parent[p] = adjProc + } + } +} + +pred Trans[p : Process, pre, post : State] { + TRAct[p, pre, post] || + TRNop[p, pre, post] +} + +/** + * all processes do a nop transition in some + * state only when no process can act because + * preconditions are not met + */ +fact TransIfPossible { + all s : State - so/last | + (all p : Process | TRNop[p, s, so/next[s]]) => + (all p : Process | !TRActPreConds[p,s]) +} + +fact LegalTrans { + Init + all s : State - so/last | + let s' = so/next[s] | { + all p : Process | + p in s.runs => Trans[p, s, s'] else TRNop[p,s,s'] + } +} + +pred PossTrans[s, s' : State] { + all p : Process | Trans[p,s,s'] +} + +pred SpanTreeAtState[s : State] { + // all processes reachable through inverted parent pointers + // from root (spanning) + Process in Root.*~(s.parent) + // parent relation is a tree (DAG) + // we only need to check the DAG condition since there can + // be at most one parent for a process (constrained by + // multiplicity) + graph/dag[~(s.parent)] +} + +/** + * show a run that produces a spanning tree + */ +pred SuccessfulRun { + SpanTreeAtState[so/last] + all s : State - so/last | !SpanTreeAtState[s] +} + +/** + * show a trace without a loop + */ +pred TraceWithoutLoop { + all s, s' : State | s!=s' => { + !EquivStates[s, s'] + (s' in so/nexts[s] && (s' != so/next[s])) => !PossTrans[s,s'] + } + all s: State | !SpanTreeAtState[s] +} + +/** + * defines equivalent states + */ +pred EquivStates[s, s' : State] { + s.lvl = s'.lvl + s.parent = s'.parent +} + +/** + * show a trace that violates liveness + */ +pred BadLivenessTrace { + // two different states equivalent (loop) + some s, s' : State | s!=s' && EquivStates[s, s'] + all s : State | !SpanTreeAtState[s] +} + +/** + * check that once spanning tree is constructed, + * it remains + */ +assert Closure { + all s : State - so/last | + SpanTreeAtState[s] => (s.parent = so/next[s].parent) +} + +// note that for the worst case topology and choice of root, +// the scope of Lvl must equal the scope of Process +run SuccessfulRun for 4 State, exactly 5 Process, 3 Lvl expect 1 +// run TraceWithoutLoop for 8 but 9 State expect 1 +run BadLivenessTrace for 5 but 7 State expect 0 +check Closure for 5 but 7 State expect 0 diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/algorithms/opt_spantree.thm b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/algorithms/opt_spantree.thm new file mode 100644 index 00000000..1254faed --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/algorithms/opt_spantree.thm @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/algorithms/peterson.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/algorithms/peterson.als new file mode 100644 index 00000000..6e6d59e5 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/algorithms/peterson.als @@ -0,0 +1,206 @@ +module examples/algorithms/peterson + +/* + * Model of Peterson's algorithm for mutual exclusion of n + * processes. The names kept similar to Murphi specification + * to make correspondence clear. + */ + +open util/ordering[priority] as po +open util/ordering[State] as so + +sig pid { +} + +sig priority { +} + +fact { + # priority = # pid + 1 +} + +abstract sig label_t {} + +// here subtyping would help +one sig L0, L1, L2, L3, L4 extends label_t {} + +sig State { + P: pid -> label_t, + Q: pid -> priority, + turn: priority -> pid, + localj: pid -> priority +} + +pred NOPTrans[i: pid, pre, post : State] { + post.P[i] = pre.P[i] + post.Q[i] = pre.Q[i] + post.localj[i] = pre.localj[i] +} + +pred L0TransPre[i : pid, pre : State] { + // precondition + pre.P[i] = L0 +} + +pred L0Trans[i: pid, pre, post : State] { + L0TransPre[i, pre] + // localj[i] := 1 + post.localj[i] = po/next[po/first] + post.P[i] = L1 + post.Q[i] = pre.Q[i] + // something for turn? + post.turn = pre.turn +} + +pred L1TransPre[i : pid, pre : State] { + // precondition + pre.P[i] = L1 +} + +pred L1Trans[i : pid, pre, post : State] { + L1TransPre[i, pre] + post.localj[i] = pre.localj[i] + post.Q[i] = pre.localj[i] + post.P[i] = L2 + // something for turn? + post.turn = pre.turn +} + +pred L2TransPre[i : pid, pre : State] { + // precondition + pre.P[i] = L2 +} + +pred L2Trans[i : pid, pre, post : State] { + L2TransPre[i, pre] + post.localj[i] = pre.localj[i] + post.Q[i] = pre.Q[i] + post.P[i] = L3 + post.turn[post.localj[i]] = i + all j : priority - post.localj[i] | + post.turn[j] = pre.turn[j] +} + +pred L3TransPre[i : pid, pre : State] { + // precondition + pre.P[i] = L3 + + all k : pid - i | + po/lt[pre.Q[k], pre.localj[i]] || + pre.turn[pre.localj[i]] != i +} + +pred L3Trans[i : pid, pre, post : State] { + L3TransPre[i, pre] + post.localj[i] = po/next[pre.localj[i]] + po/lt[post.localj[i], po/last] => + post.P[i] = L1 + else + post.P[i] = L4 + post.Q[i] = pre.Q[i] + post.turn = pre.turn +} + +pred L4TransPre[i : pid, pre : State] { + // precondition + pre.P[i] = L4 +} + +pred L4Trans[i : pid, pre, post : State] { + L4TransPre[i, pre] + + post.P[i] = L0 + post.Q[i] = po/first + post.localj[i] = pre.localj[i] + post.turn = pre.turn +} + +pred RealTrans[i : pid, pre, post : State] { + L0Trans[i,pre,post] || + L1Trans[i,pre,post] || + L2Trans[i,pre,post] || + L3Trans[i,pre,post] || + L4Trans[i,pre,post] +} + +pred SomePre[i : pid, pre : State] { + L0TransPre[i, pre] || + L1TransPre[i, pre] || + L2TransPre[i, pre] || + L3TransPre[i, pre] || + L4TransPre[i, pre] +} + +fact Init { + let firstState = so/first | { + all i : pid | { + firstState.P[i] = L0 + firstState.Q[i] = po/first + } + no firstState.turn + no firstState.localj + } +} + +fact LegalTrans { + all pre : State - so/last | + let post = so/next[pre] | { + /*some i : pid | { + // HACK: + // need to specify that if some node + // can make a non-NOP transition, it + // does, but i can't figure out how + // right now + Trans(i,pre,post) && !NOPTrans(i,pre,post) + all j : pid - i | + NOPTrans(j,pre,post) + }*/ + all i : pid | + RealTrans[i,pre,post] || NOPTrans[i,pre,post] + (all i : pid | NOPTrans[i,pre,post]) => { + all i : pid | !SomePre[i,pre] + post.turn = pre.turn + } + } +} + +assert Safety { + all i1, i2 : pid, s : State | i1!=i2 => not (s.P[i1] = L4 && s.P[i2] = L4) +} + +assert NotStuck { + all pre : State - so/last | + let post = so/next[pre] | + some i : pid | + RealTrans[i, pre, post] && !NOPTrans[i,pre,post] +} + +pred TwoRun { + some s1, s2: State, i1, i2: pid | { + s1!=s2 + i1!=i2 + s1.P[i1] = L4 + s2.P[i2] = L4 + } +} + +pred ThreeRun { + some disj s1, s2, s3: State, disj i1, i2, i3: pid | { + s1.P[i1] = L4 + s2.P[i2] = L4 + s3.P[i3] = L4 + } +} + +// 2 pids requires 8 states +// 3 pids requires 16 states +run TwoRun for 13 but 3 pid, 4 priority, 5 label_t expect 1 + +// haven't run this one successfully yet +run ThreeRun for 19 but 3 pid,4 priority,5 label_t expect 1 + +// how many states do we need for this to match murphi? +check Safety for 10 but 2 pid, 3 priority, 5 label_t expect 0 + +// this assertion is trivial because of the hack described above +check NotStuck for 10 but 2 pid, 3 priority, 5 label_t expect 0 diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/algorithms/ringlead.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/algorithms/ringlead.als new file mode 100644 index 00000000..b5a786a8 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/algorithms/ringlead.als @@ -0,0 +1,150 @@ +module examples/algorithms/ringlead + +/* + * Model of leader election on a ring + * + * Each process has a unique ID, IDs are ordered. + * The algorithm elects the process with the highest + * ID the leader, as follows. First, each process + * sends its own ID to its right neighbor. + * Then, whenever a process receives an ID, if the + * ID is greater than the process' own ID it forwards + * the ID to its right neighbor, otherwise does nothing. + * When a process receives its own ID that process + * is the leader. + */ + +open util/boolean as bool +open examples/algorithms/messaging as msg +open util/ordering[msg/Node] as nodeOrd +open util/ordering[msg/Tick] as tickOrd + +sig RingLeadNode extends msg/Node { + rightNeighbor: msg/Node +} + +fact DefineRing { + (one msg/Node || (no n: msg/Node | n = n.rightNeighbor)) + all n: msg/Node | msg/Node in n.^rightNeighbor +} + +sig RingLeadMsgState extends msg/MsgState { + id: msg/Node +} + +sig MsgViz extends msg/Msg { + vFrom: msg/Node, + vTo: set msg/Node, + vId: msg/Node +} + +fact { + MsgViz = msg/Msg + vFrom = state.from + vTo = state.to + vId = state.id +} + + +sig RingLeadNodeState extends msg/NodeState { + leader: Bool +} + + +pred RingLeadFirstTrans [self: msg/Node, pre, post: msg/NodeState, + sees, reads, sends, needsToSend: set msg/Msg] { + one sends + # needsToSend = 1 + sends.state.to = self.rightNeighbor + sends.state.id = self + post.leader = False +} + +fact InitRingLeadState { + all n: msg/Node | + tickOrd/first.state[n].leader = False +} + +pred RingLeadRestTrans [self: msg/Node, pre, post: msg/NodeState, + sees, reads, sends, needsToSend: set msg/Msg] { + RingLeadTransHelper[self, sees, reads, sends, needsToSend] + post.leader = True iff (pre.leader = True || + self in reads.state.id) +} + +/** + * we take any messages whose node ids are higher than ours, + * and we forward them to the right neighbor. we drop + * all other messages. if we get a message with our own + * id, we're the leader. + */ +pred RingLeadTransHelper[self: msg/Node, sees, reads, sends, needsToSend: set msg/Msg] { + reads = sees + + all received: reads | + (received.state.id in nodeOrd/nexts[self]) => + (one weSend: sends | (weSend.state.id = received.state.id && weSend.state.to = self.rightNeighbor)) + + all weSend: sends | { + let mID = weSend.state.id | { + mID in nodeOrd/nexts[self] + mID in reads.state.id + weSend.state.to = self.rightNeighbor + } + //weSend.sentBecauseOf = { received : reads | received.id = weSend.id } + //all otherWeSend: sends - weSend | otherWeSend.id != weSend.id + } + + # needsToSend = # { m: reads | m.state.id in nodeOrd/nexts[self] } +} +fact RingLeadTransitions { + all n: msg/Node { + all t: msg/Tick - tickOrd/last | { + t = tickOrd/first => + RingLeadFirstTrans[n, t.state[n], tickOrd/next[t].state[n], t.visible[n], t.read[n], t.sent[n], t.needsToSend[n]] + else + RingLeadRestTrans[n, t.state[n], tickOrd/next[t].state[n], t.visible[n], t.read[n], t.sent[n], t.needsToSend[n]] + } + // also constrain last tick + RingLeadTransHelper[n, tickOrd/last.visible[n], tickOrd/last.read[n], tickOrd/last.sent[n], tickOrd/last.needsToSend[n]] + } +} + +assert OneLeader { + all t: msg/Tick | + lone n: msg/Node | + t.state[n].leader = True +} + +fact CleanupViz { + RingLeadNode = msg/Node + RingLeadMsgState = msg/MsgState + RingLeadNodeState = msg/NodeState +} + +pred SomeLeaderAtTick[t: msg/Tick] { + some n: msg/Node | t.state[n].leader = True +} + +pred NeverFindLeader { + msg/Loop + all t: msg/Tick | ! SomeLeaderAtTick[t] +} + +assert Liveness { + (msg/NoLostMessages && msg/NoMessageShortage) => ! NeverFindLeader +} + +pred SomeLeader { some t: msg/Tick | SomeLeaderAtTick[t] } + +assert LeaderHighest { + all t: msg/Tick, n: msg/Node | + t.state[n].leader = True => n = nodeOrd/last +} + +run NeverFindLeader for 1 but 3 msg/Tick, 2 Bool, 2 msg/NodeState expect 1 +check Liveness for 3 but 6 msg/Msg, 2 Bool, 2 msg/NodeState expect 0 +check OneLeader for 5 but 2 Bool, 2 msg/NodeState expect 0 +run SomeLeader for 2 but 3 msg/Node, 5 msg/Msg, 5 msg/Tick, 5 msg/MsgState expect 1 +check LeaderHighest for 3 but 2 msg/NodeState, 5 msg/Msg, 5 msg/MsgState, 5 msg/Tick expect 0 + diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/algorithms/ringlead.thm b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/algorithms/ringlead.thm new file mode 100644 index 00000000..95977a51 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/algorithms/ringlead.thm @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/algorithms/s_ringlead.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/algorithms/s_ringlead.als new file mode 100644 index 00000000..8f96b477 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/algorithms/s_ringlead.als @@ -0,0 +1,153 @@ +module examples/algorithms/s_ringlead + +/* + * Model of leader election on a ring. + * + * Each process has a unique ID, IDs are ordered. The algorithm + * elects the process with the highest ID the leader, as follows. + * First, each process sends its own ID to its right neighbor. + * Then, whenever a process receives an ID, if the ID is greater + * than the process' own ID it forwards the ID to its right + * neighbor, otherwise does nothing. When a process receives its + * own ID that process is the leader. + * + * Note: This file needs higher order quantifiers turned on. + */ + +open util/ordering[State] as so +open util/ordering[Process] as po +open util/graph[Process] as graph + +sig Process { + rightNeighbor: Process +} + +sig State { + // buffer which the right neighbor can read from + buffer: Process -> Process, + //sends, reads: Process -> Process, + runs: set Process, + leader: set Process +} + +fact DefineRing { + graph/ring[rightNeighbor] +} + +fact InitialState { + no so/first.buffer + no so/first.leader + Process in so/first.runs +} + + +fact CleanupLast { + let ls = so/last | + no ls.runs +} + +pred ValidTrans2[s, s': State] { + all p : s.runs | VT2Helper[p,s,s'] + all p : Process - s.runs | NOP2[p,s,s'] + NoMagicMsg[s,s'] + +} + +pred NoMagicMsg[s, s' : State] { + // no magically appearing messages + all p : Process, m : s'.buffer[p] | + m in s.buffer[p] || (!NOP2[p,s,s'] && + ((s = so/first && m = p) || + (s != so/first && m in s.buffer[p.~rightNeighbor] + && m !in s'.buffer[p.~rightNeighbor] && po/gt[m,p]))) +} + +pred PossTrans[s, s' : State] { + all p : Process | VT2Helper[p,s,s'] || NOP2[p,s,s'] + NoMagicMsg[s,s'] +} + +pred VT2Helper[p : Process, s, s' : State] { + ( + let readable=s.buffer[p.~rightNeighbor] | + (s = so/first) => { + p = s'.buffer[p] + readable in s'.buffer[p.~rightNeighbor] + p !in s'.leader + } else { + (some readable) => { + some m : set readable | { + m !in s'.buffer[p.~rightNeighbor] + // nothing else gets deleted + readable - m in s'.buffer[p.~rightNeighbor] + { m': m | po/gt[m',p] } /*m & nexts(p)*/ in s'.buffer[p] + p in s'.leader iff (p in s.leader || p in m) + } + } else NOP2[p,s,s'] + } + ) +} + +pred NOP2[p : Process, s,s': State] { + p in s'.leader iff p in s.leader + // no reads + s.buffer[p.~rightNeighbor] in s'.buffer[p.~rightNeighbor] + // no sends + s'.buffer[p] in s.buffer[p] +} + +pred Preconds[p : Process, s : State] { + s = so/first || some s.buffer[p.~rightNeighbor] +} + +fact TransIfPossible { + all s : State - so/last | + (all p : Process | NOP2[p, s, so/next[s]]) => + (all p : Process | !Preconds[p,s]) +} + +fact LegalTrans { + all s : State - so/last | + let s' = so/next[s] | + ValidTrans2[s,s'] +} + +pred EquivStates[s, s': State] { + s.buffer = s'.buffer + s.leader = s'.leader +} + +assert Safety { + all s : State | lone s.leader +} + +pred Legit[s : State] { + one s.leader +} + +pred BadLivenessTrace { + all s : State | !Legit[s] + let ls = so/last | + some s : State - ls | { + EquivStates[s, ls] + Process in (so/nexts[s] + s).runs + } +} + +pred TraceWithoutLoop { + all t1, t2 : State | t1!=t2 => !EquivStates[t1,t2] + all s, s' : State | (s' in (so/nexts[s] - so/next[s])) => !PossTrans[s,s'] + all s : State | !Legit[s] +} + +pred AltTrans { + SomeLeader +} + +pred SomeLeader { some State.leader } + +run BadLivenessTrace for 3 but 8 State expect 0 +run SomeLeader for 4 but 6 State expect 1 +check Safety for 7 expect 0 +// run TraceWithoutLoop for 5 but 13 State expect 1 +run AltTrans for 5 but 8 State expect 1 diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/algorithms/stable_mutex_ring.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/algorithms/stable_mutex_ring.als new file mode 100644 index 00000000..d4a21f12 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/algorithms/stable_mutex_ring.als @@ -0,0 +1,202 @@ +module examples/algorithms/stable_mutex_ring + +/* + * Dijkstra's K-state mutual exclusion algorithm for a ring + * + * Original paper describing the algorithm: + * [1] E.W.Dijkstra, "Self-Stabilizing Systems in Spite of + * Distributed Control", Comm. ACM, vol. 17, no. 11, pp. + * 643-644, Nov. 1974 + * + * Proof of algorithm's correctness: + * [2] E.W.Dijkstra, "A Belated Proof of Self-Stabilization", + * in Distributed Computing, vol. 1, no. 1, pp. 5-6, 1986 + * + * SMV analysis of this algorithm is described in: + * [3] "Symbolic Model Checking for Self-Stabilizing Algorithms", + * by Tatsuhiro Tsuchiya, Shini'ichi Nagano, Rohayu Bt Paidi, and + * Tohru Kikuno, in IEEE Transactions on Parallel and Distributed + * Systems, vol. 12, no. 1, January 2001 + * + * Description of algorithm (adapted from [3]): + * + * Consider a distributed system that consists of n processes + * connected in the form of a ring. We assume the state-reading + * model in which processes can directly read the state of their + * neighbors. We define _privilege_ of a process as its ability to + * change its current state. This ability is based on a Boolean + * predicate that consists of its current state and the state of + * one of its neighboring processes. + * + * We then define the legitimate states as those in which the + * following two properties hold: 1) exactly one process has a + * privilege, and 2) every process will eventually have a privilege. + * These properties correspond to a form of mutual exclusion, because + * the privileged process can be regarded as the only process that is + * allowed in its critical section. + * + * In the K-state algorithm, the state of each process is in + * {0,1,2,...,K-1}, where K is an integer larger than or equal to n. + * For any process p_i, we use the symbols S and L to denote its + * state and the state of its neighbor p_{i-1}, respectively, and + * process p_0 is treated differently from all other processes. The + * K-state algorithm is described below. + * + * process p_0: if (L=S) { S := (S+1) mod K; } + * process P_i(i=1,...,n-1): if (L!=S) { S:=L; } + */ + +open util/ordering[Tick] as to +open util/graph[Process] as pg +open util/graph[Val] as vg + +sig Process { + rightNeighbor: Process +} + +sig Val { + nextVal : Val +} + +fact MoreValThanProcess { + # Val > # Process +} + +fact DefineRings { + pg/ring[rightNeighbor] + vg/ring[nextVal] + //Val$nextVal = Ord[Val].next + (Ord[Val].last -> Ord[Val].first) +} + +sig Tick { + val: Process -> one Val, + runs: set Process, // processes scheduled to run on this tick + // for visualization + priv: set Process // the set of priviledged processes on this tick +} +{ + priv = { p : Process | Privileged[p, this] } +} + +one sig FirstProc extends Process { +} + + +fun FirstProcTrans[curVal, neighborVal : Val]: Val { + (curVal = neighborVal) => curVal.nextVal else curVal +} + +fun RestProcTrans[curVal, neighborVal : Val]: Val { + (curVal != neighborVal) => neighborVal else curVal +} + +fact LegalTrans { + all tp : Tick - to/last | + let tn = to/next[tp] | { + all p: Process | + let curVal = tp.val[p], neighborVal = tp.val[p.rightNeighbor], newVal = tn.val[p] | { + p !in tp.runs => newVal = curVal else { + p = FirstProc => + newVal = FirstProcTrans[curVal, neighborVal] + else + newVal = RestProcTrans[curVal, neighborVal] + } + } + } +} + +pred TickTrans[tp, tn : Tick] { + all p : Process | + let curVal = tp.val[p], neighborVal = tp.val[p.rightNeighbor], newVal = tn.val[p] | { + p = FirstProc => + newVal = FirstProcTrans[curVal, neighborVal] + else + newVal = RestProcTrans[curVal, neighborVal] + } +} + +/** + * whether this process can enter its critical section + * on this tick + */ +pred Privileged[p : Process, t : Tick] { + p = FirstProc => + t.val[p] = t.val[p.rightNeighbor] + else + t.val[p] != t.val[p.rightNeighbor] +} + +pred IsomorphicStates[val1, val2: Process -> one Val] { + some processMap: Process one -> one Process, + valMap: Val one -> one Val | { + FirstProc.processMap = FirstProc + all p: Process, v: Val | { + p->v in val1 iff (p.processMap) -> (v.valMap) in val2 + } + all v1,v2: Val | v1->v2 in nextVal iff (v1.valMap) -> (v2.valMap) in nextVal + all p1,p2: Process | p1->p2 in rightNeighbor + iff (p1.processMap) -> (p2.processMap) in rightNeighbor + } +} + +/** + * Find a trace that goes into a loop + * containing a bad tick, i.e. a tick + * at which two distinct processes + * try to run their critical sections + * simultaneously. In such a trace the + * algorithm never "stabilizes". + */ +pred BadSafetyTrace { + let lst = to/last | + some t : Tick - lst | { + //IsomorphicStates(ft.val, lst.val) + t.val = lst.val + Process in (to/nexts[t] + t - lst).runs + some badTick : to/nexts[t] + t | + BadTick[badTick] + } +} + +/** + * Two different processes simultaneously + * try to run their critical sections at this tick + */ +pred BadTick[badTick : Tick] { + some p1 , p2 : Process | { + p1!=p2 + Privileged[p1, badTick] + Privileged[p2, badTick] + } +} + +assert Closure { + not BadTick[to/first] => (all t : Tick | not BadTick[t]) +} + +pred TwoPrivileged { + BadTick[to/first] + some p1, p2 : Process, t1, t2 : Tick - to/first | { + p1!=p2 + Privileged[p1,t1] + Privileged[p2,t2] + } +} + +pred TraceWithoutLoop { + all t1, t2 : Tick | t1!=t2 => t1.val != t2.val +} + +pred TraceShorterThanMaxSimpleLoop { + to/first.val = to/last.val + all t : Tick - to/first - to/last | + !(t.val = to/first.val) +} + +run TraceShorterThanMaxSimpleLoop for 7 but 2 Process, 3 Val expect 1 +run TwoPrivileged for 5 but 3 Process, 4 Val expect 1 +check Closure for 5 but 5 Process, 6 Val expect 0 +//run BadSafetyTrace for 16 but 3 Process, 4 Val +//run TraceWithoutLoop for 21 but 4 Process, 5 Val + + diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/algorithms/stable_mutex_ring.thm b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/algorithms/stable_mutex_ring.thm new file mode 100644 index 00000000..5f31f3ac --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/algorithms/stable_mutex_ring.thm @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/algorithms/stable_orient_ring.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/algorithms/stable_orient_ring.als new file mode 100644 index 00000000..c6fec3fe --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/algorithms/stable_orient_ring.als @@ -0,0 +1,90 @@ +module examples/algorithms/stable_orient_ring + +/* + * A self-stabilizing algorithm for orienting uniform rings. + * Communication model is the state-reading model. + */ + +open util/boolean as bool +open util/ordering[Tick] as ord +open util/graph[Process] as graph + +sig Process { + rightNeighbor: Process, + AP1, AP2: Process +} + +fun leftNeighbor[p: Process]: Process { + p.~(rightNeighbor) +} + +fact { + all p: Process { + (p.AP1=p.rightNeighbor && p.AP2=leftNeighbor[p]) || + (p.AP2=p.rightNeighbor && p.AP1=leftNeighbor[p]) + } +} + +fact DefineRing { + graph/ring[rightNeighbor] +} + +sig Tick { + runs: set Process, + dir, S, T: Process -> one Bool, + ring_: Process -> Process +} +{ + all p: Process | p.ring_ = (p.dir=True => p.AP1 else p.AP2) +} + +pred Eq3[b1,b2,b3: Bool] { b1 = b2 && b2 = b3 } +pred Eq4[b1,b2,b3,b4: Bool] { Eq3[b1,b2,b3] && b3=b4 } + +fact Transitions { + all tp: Tick - ord/last | let tn = ord/next[tp] | + all p: Process | + let p1 = p.AP1, p2 = p.AP2, pS = tp.S, pT=tp.T, nS=tn.S, nT=tn.T | + let S1p=p1.pS, S2p=p2.pS, + T1p=p1.pT, T2p=p2.pT, + Sp = p.pS, Sn=p.nS, + Tp = p.pT, Tn=p.nT, + dirp = p.(tp.dir), dirn = p.(tn.dir) | { + p !in tp.runs => ( Sn = Sp && Tn = Tp && dirn = dirp ) else ( + S1p = S2p => ( Sn = Not[S1p] && Tn = True && dirn=dirp) else ( + (Eq3[S1p, Sp, Not[S2p]] && + Eq4[Not[T1p],Tp,T2p,True]) => + (Sn = Not[Sp] && Tn = False && dirn = True) + else ( + (Eq3[Not[S1p],Sp,S2p] && Eq4[T1p,Tp,Not[T2p],True]) => + (Sn = Not[Sp] && Tn = False && dirn = False) else ( + ((Eq3[S1p,Sp,Not[S2p]] && T1p=Tp) || + (Eq3[Not[S1p],Sp,S2p] && Tp=T2p)) => + (Tn = Not[Tp] && Sn=Sp && dirn=dirp) else ( + Sn=Sp && Tn=Tp && dirn=dirp + ) + ) + ) + ) + ) + } +} + +pred RingAtTick[t: Tick] { + let rng = t.ring_ | + graph/ring[rng] || graph/ring[~rng] +} + +assert Closure { + // if the ring is properly oriented + all t: Tick - ord/last | + RingAtTick[t] => RingAtTick[ord/next[t]] +} + +pred SomeState { + !graph/ring[ord/first.ring_] + some t: Tick | graph/ring[t.ring_] +} + +run SomeState for 1 but 2 Tick, 2 Bool, 3 Process expect 1 +check Closure for 1 but 2 Tick, 2 Bool, 3 Process expect 1 diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/algorithms/stable_orient_ring.thm b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/algorithms/stable_orient_ring.thm new file mode 100644 index 00000000..0a8cba73 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/algorithms/stable_orient_ring.thm @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/algorithms/stable_ringlead.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/algorithms/stable_ringlead.als new file mode 100644 index 00000000..577257f8 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/algorithms/stable_ringlead.als @@ -0,0 +1,180 @@ +module examples/algorithms/stable_ringlead + +/* + * Huang's self-stabilizing leader-election algorithm + * for rings. + */ + +open util/ordering[Process] as po +open util/ordering[Val] as vo +open util/ordering[State] as so +open util/graph[Process] as graph + +sig Process { + rightNeighbor: Process +} + +sig Val { + nextVal : Val +} + +fact { + graph/ring[rightNeighbor] + vo/next + (vo/last -> vo/first) = nextVal + # Val = # Process +} + +sig State { + val : Process -> one Val, + running : set Process + // for visualization + //leader : set Process +} { + //leader = { p : Process | LeaderAtState(p, this) } +} + +fact { + no so/last.running +} + +fun LeadersAtState[t : State] : set Process { + { p : Process | LeaderAtState[p,t] } +} + +pred LeaderAtState[p : Process, t : State] { ValAtState[p,t] = vo/first } + +fun ValAtState[p : Process, t : State] : Val { t.val[p] } + +fun LeftValAtState[p : Process, t : State] : Val { t.val[p.~rightNeighbor] } + +fun RightValAtState[p : Process, t : State] : Val { t.val[p.rightNeighbor] } + +fun XAtState[p : Process, t : State] : Int { + g[LeftValAtState[p,t],ValAtState[p,t]] +} + +fun YAtState[p : Process, t : State] : Int { + g[ValAtState[p,t],RightValAtState[p,t]] +} + +fun g[a, b : Val] : Int { + (a = b) => Int[# Val] else minus[b,a] +} + +fun minus[v1, v2 : Val] : Int { + Int[ (v1 = v2) => 0 + else vo/gt[v1, v2] => (# (vo/nexts[v2] & vo/prevs[v1] + v1)) + else (# (Val - (vo/nexts[v1] & vo/prevs[v2] + v1))) + ] +} + +fun Trans[oldVal : Val, x, y : Int] : Val { + ((int x = int y && int y = # Val) || (int x < int y)) => oldVal.nextVal else oldVal +} + +pred OneAtATimeTrans { + all tp: State - so/last | + let tn = so/next[tp] | + some p : Process | { + tp.running = p + TransHelper[p,tp,tn] + all other : Process - p | + ValAtState[other,tn] = ValAtState[other,tp] + } +} + +pred DDaemonTrans { + all tp: State - so/last | + let tn = so/next[tp] | { + some tp.running + all p : tp.running | TransHelper[p,tp,tn] + all other : Process - tp.running | + ValAtState[other,tn] = ValAtState[other,tp] + } +} + +pred TransHelper[p : Process, tp, tn : State] { + let oldVal = ValAtState[p, tp], + newVal = ValAtState[p, tn], + x = XAtState[p, tp], + y = YAtState[p,tp] | + newVal = Trans[oldVal, x, y] + +} + +pred StateTrans[s, s' : State] { + all p : Process | + TransHelper[p, s, s'] || ValAtState[p,s] = ValAtState[p,s'] +} + + + +pred CBadLivenessTrace { + OneAtATimeTrans + BadLivenessHelper +} + +pred DBadLivenessTrace { + DDaemonTrans + BadLivenessHelper +} + +pred BadLivenessHelper { + let ls = so/last | + some s : State - ls | { + s.val = ls.val + // fair + Process in (so/nexts[s] + s - ls).running + } + all s : State | ! Legit[s] + } + +pred CTraceWithoutLoop { + OneAtATimeTrans + all t, t' : State | t!=t' => t.val != t'.val +} + +pred DTraceWithoutLoop { + DDaemonTrans + all t, t' : State | t!=t' => { + t.val != t'.val + (t' in so/nexts[t] && t' != so/next[t]) => !StateTrans[t,t'] + } + all t : State | !Legit[t] +} + +pred ConvergingRun { + OneAtATimeTrans + !Legit[so/first] + some t : State | Legit[t] +} + +pred OnlyFairLoops { + OneAtATimeTrans + all s, s' : State | + (s' in so/nexts[s] && s'.val = s.val) => + (let loopStates = (so/nexts[s] & so/prevs[s']) + s + s' | Process in loopStates.running) +} + +assert CMustConverge { + OnlyFairLoops => (some s : State | Legit[s]) +} + +pred Legit [s : State] { + one LeadersAtState[s] + all p : Process | { + int XAtState[p,s] < # Val + int YAtState[p,s] < # Val + } + all p, p' : Process | { + int XAtState[p,s] = int XAtState[p',s] + int YAtState[p,s] = int YAtState[p',s] + } +} + +run ConvergingRun for 3 but 4 State expect 1 +run DTraceWithoutLoop for 3 but 4 State expect 1 +run DBadLivenessTrace for 3 but 4 State expect 1 +run CTraceWithoutLoop for 3 but 5 State expect 0 +run CBadLivenessTrace for 4 but 5 State expect 1 +check CMustConverge for 3 but 4 State expect 0 diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/algorithms/stable_ringlead.thm b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/algorithms/stable_ringlead.thm new file mode 100644 index 00000000..d580f3a1 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/algorithms/stable_ringlead.thm @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/case_studies/INSLabel.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/case_studies/INSLabel.als new file mode 100644 index 00000000..9f8a2024 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/case_studies/INSLabel.als @@ -0,0 +1,149 @@ +module examples/case_studies/INSLabel + +/* + * Models an Intentional Naming System (INS), a scheme for + * dynamic resource discovery in a dynamic environment. + * + * For a detailed description, see: + * http://sdg.lcs.mit.edu/pubs/2000/INS_ASE00.pdf + * + * author: Sarfraz Khurshid + */ + +sig Record {} + +sig Label {} + +sig Node { + label: Label +} + +sig LabelTree { + root: Node, + nodes: set Node, + children: nodes one -> (nodes - root) +} +{ // connected + nodes = root.*children + some root.children +} + +pred Get[db: DB, r: Record, a:Advertisement] { + root[a] = root[db] + nodes[a] = + nodes[db] & r.~(db.recs).*(~(db.children)) + anodes[a] = + anodes[db] & r.~(db.recs).*(~(db.children)) + vnodes[a] = + vnodes[db] & r.~(db.recs).*(~(db.children)) + all n: a.nodes | + n.~(a.children) = n.~(db.children) +} + +sig Attribute extends Label {} + +sig Value extends Label {} + +one sig Wildcard, Null extends Value {} + +sig AVTree extends LabelTree { + vnodes, anodes: set nodes +} +{ + root in vnodes + root.label = Null + Null !in (vnodes - root).label + anodes.label + anodes.label in Attribute + vnodes.label in Value + all n: nodes | all /* disj */ c,c': n.children | + c.label != c'.label + all a: anodes | a.children in vnodes && some a.children + all v: vnodes | v.children in anodes + no Wildcard.~label.children +} + +one sig Query extends AVTree {} +{ + all a: anodes | one a.children +} + +one sig Advertisement extends AVTree {} +{ + Wildcard !in vnodes.label +} + +one sig DB extends AVTree { + records: set Record, + recs: (vnodes - root) some -> records +} +{ + Wildcard !in vnodes.label + all a: anodes | no a.recs + all v: vnodes { + no v.children => some v.recs + no v.recs & v.^(~children).recs } + all a: anodes | all disj v,v': a.children | + (no v.*children.recs & v'.*children.recs) +} + +one sig State { + conforms: Query -> Advertisement -> Node -> Node, + lookup: DB -> Query -> Node -> Node -> Record +} + +fact ConformsFixPoint { + all q: Query | all a: Advertisement | + all nq: Node | all na: Node | + q.ConformsAux[a,nq,na] <=> + { + nq.label in Wildcard + na.label + all nq': q.children[nq] | some na': a.children[na] | + q.ConformsAux[a,nq',na'] + } +} + +pred Query.ConformsAux[a: Advertisement, nq: Node, na: Node] { + na in State.conforms[this][a][nq] +} + +pred Conforms[q: Query, a:Advertisement] { + q.ConformsAux[a, q.root, a.root] +} + +fact LookupFixPoint { + all db: DB, q: Query, T: Node, n: Node, r: Record | + r in db.LookupAux[q,T,n] <=> // record r is in the result if and only if + { + all na: n.(q.children) | all nv: na.(q.children) | // for all child av-pairs (na,nv) of av-pair n in q + some Ta: T.(db.children) { + Ta.label = na.label // Ta is a child node with attribute na + nv.label = Wildcard => // wildcard matching + r in Ta.^(db.children).(db.recs) else // r is a record of any child of Ta + (some Tv: Ta.(db.children) { // normal matching + Tv.label = nv.label // Tv is a child of Ta with value nv + no nv.(q.children) => // if Tv is a leaf-node + r in Tv.*(db.children).(db.recs) else // r is a record of Tv or of v + r in db.LookupAux[q,Tv,nv] }) } // else r is a record of the recursive call at Tv + } +} + +fun DB.LookupAux[q: Query, vd: Node, vq: Node]: set Record { // helper function for Lookup + State.lookup[this][q][vd][vq] +} + +fun Lookup[db: DB, q: Query]: set Record { // models Lookup-Name algorithm invocation + db.LookupAux[q, db.root, q.root] +} + +assert LookupConforms2 { //soundness and completeness + all q: Query | all db: DB | all r: Record | all a: Advertisement | + Get[db,r,a] => // all n: a.nodes | n.~(db.children) + {r in db.Lookup[q] <=> q.Conforms[a]} +} + +// < 10 sec +check LookupConforms2 for 4 but 1 State, 3 LabelTree, 2 Record expect 0 +// ~ 25 min +//check LookupConforms2 for 6 but 1 State, 3 LabelTree, 2 Record +//check LookupConforms2 for 6 but 1 State, 3 LabelTree, 3 Record +run Lookup for 3 expect 0 diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/case_studies/chord.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/case_studies/chord.als new file mode 100644 index 00000000..f7a042f1 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/case_studies/chord.als @@ -0,0 +1,260 @@ +module examples/case_studies/chord + +/* + * Models the chord distributed hash table lookup protocol. + * + * For a detailed description, see: + * http://www.pdos.lcs.mit.edu/papers/chord:sigcomm01/ + */ + +open util/relation as rel + +sig Id {next: Id} +fact {all i: Id | Id in i.*next} + +/** + * true iff i precedes j in the order starting at from + */ +pred less_than [from, i,j: Id] { + let next' = Id<:next - (Id->from) | j in i.^next' // if from=j, returns true if # nodes > 1 + } +pred less_than_eq [from, i,j: Id] { + let next' = Id<:next - (Id->from) | j in i.*next' + } + +sig Node {id: Id} +fact {all m,n: Node | m!=n => m.id != n.id} + + +sig NodeData { + prev, next: Node, + finger: Id -> lone Node, + closest_preceding_finger: Id -> one Node, + find_predecessor: Id -> one Node, + find_successor: Id -> one Node + } + +sig State { + active: set Node, + data: active -> one NodeData + } + +/** + * node n's next node is defined to be the m where n's finger table maps the id + * that follows n.id to m + * next holds the first entry of the finger table + */ +fact {all s: State | all n: s.active | n.(s.data).next = n.(s.data).finger[n.id.next]} + +pred NextCorrect [s: State] { + all n: s.active { + -- no intervening node (ie, close enough) + no n': s.active - n | less_than [n.id, n'.id, n.(s.data).next.id] + -- can reach all other active nodes (ie, far enough) + -- need this because can't rule out case of next being node itself (because of 1-node ring) + -- s.active in n.*(s.data.next) + n.(s.data).next != n || #s.active = 1 + } + } + +pred NextCorrect' [s: State] { +-- next seems to be correct for 1,2,3 nodes + all n: s.active | let nd = (s.data)[n] { + let next' = Id<:next - (Id -> nd.next.id) { + no n' : s.active { n'.id in n.id.^next' } + }} + } + +// valid +assert Same1 {all s: State | NextCorrect[s] => NextCorrect'[s]} +check Same1 for 3 but 1 State expect 0 + +// valid unless active condition removed +assert Same2 {all s: State | s.active = Node => (NextCorrect'[s] => NextCorrect[s])} +check Same2 for 3 but 1 State expect 0 + +-- assert NextInFinger {all s: State | all n: s.active | some n.s.data.finger[n.id.next] } + + +-- says that finger entry maps an id to a node so that there are no intervening nodes +-- between the id and the node +pred FingersCorrect [s: State] { + all nd: s.active.(s.data) | all start:nd.finger.univ | + nd.finger[start] in s.active && + (no n' : s.active | less_than [start, n'.id, nd.finger[start].id]) + } + + +pred FingersCorrect' [s: State] { + all n: s.active | let nd = (s.data)[n] | all start: Node.~(nd.finger) { + nd.finger[start] in s.active && + (let next' = Id<:next - (nd.finger[start].id -> Id) { + no n' : s.active - nd.finger[start] { + n'.id in start.*next' + } + }) + } + } + + +assert SameFC {all s: State | FingersCorrect [s] iff FingersCorrect'[s]} +check SameFC for 3 but 1 State expect 0 + + +pred ShowMeFC { + all s : State | s.active = Node && FingersCorrect[s] +} + +run ShowMeFC for 2 but 1 State expect 1 + +pred ClosestPrecedingFinger[s: State] { + all n: s.active | let nd = n.(s.data) | + all i: Id | let cpf = nd.closest_preceding_finger[i] { + no n': nd.finger[Id] + n - cpf | less_than [cpf.id, n'.id, i] + cpf in nd.finger[Id] + n + cpf.id != i || # s.active = 1 + //less_than (n.id, cpf.id, i) + } + } + + +pred ClosestPrecedingFinger'[s: State] { + all n: s.active | let nd = (s.data)[n] | all i: Id { + let next' = Id<:next - (Id -> i) { + nd.next.id in n.id.^next' => + // nd.closest_preceding_finger[i] = nd.next, + (some n1: nd.finger[Id] { + nd.closest_preceding_finger[i] = n1 + //n1 in nd.finger[Id] + n1.id in n.id.^next' + no n2: nd.finger[Id] | n2.id in n1.id.^next' + }) else + nd.closest_preceding_finger[i] = n + }} + } + + +assert SameCPF {all s: State | FingersCorrect[s] => (ClosestPrecedingFinger [s] iff ClosestPrecedingFinger' [s])} +assert SameCPF1 {all s: State | FingersCorrect[s] => (ClosestPrecedingFinger [s] => ClosestPrecedingFinger' [s])} +assert SameCPF2 { + all s: State | ((s.active = Node && FingersCorrect[s] && ClosestPrecedingFinger' [s]) + => ClosestPrecedingFinger [s]) } + +check SameCPF for 3 but 1 State expect 0 +check SameCPF1 for 2 but 1 State expect 0 +check SameCPF2 for 3 but 1 State expect 0 + + +pred ShowMeCPF { + all s : State | s.active = Node && FingersCorrect[s] && + // not ClosestPrecedingFinger(s) && ClosestPrecedingFinger'(s) + ClosestPrecedingFinger[s] + //all s : State | all nd : s.active.s.data | nd.finger[Id] = Node + # Node = 2 + # State = 1 +} + + +run ShowMeCPF for 2 but 1 State expect 1 + + +pred FindPredecessor[s: State] { + all n: s.active | let nd = n.(s.data) | all i: Id { + nd.find_predecessor[i] = + (less_than_eq [n.id, i, nd.next.id] && (n.id != i || # s.active = 1) + => n + else (nd.closest_preceding_finger[i].(s.data).find_predecessor)[i]) + } + } + + +assert FPisActive { + all s: State | FingersCorrect[s] && ClosestPrecedingFinger[s] && FindPredecessor[s] + => (all n: s.active | all nd: n.(s.data) | nd.find_predecessor[Id] in s.active) } +check FPisActive for 3 but 1 State expect 1 + + +pred FindPredecessor'[s: State] { + all n: s.active | let nd = (s.data)[n] | all i: Id { + let next' = Id<:next - (nd.next.id -> Id) { + one s.active or i in n.id.^next' => // *next' -> ^next' 1/8/02 + nd.find_predecessor[i] = n else + nd.find_predecessor[i] = + ((s.data)[nd.closest_preceding_finger[i]]).find_predecessor[i] + }} + } + + +assert SameFP {all s: State | FingersCorrect[s] // && s.active = Node + => (FindPredecessor [s] iff FindPredecessor' [s])} + +assert SameFP1 { + all s: State | FingersCorrect[s] && s.active = Node + => (FindPredecessor [s] => FindPredecessor' [s])} +assert SameFP2 { + all s: State | FingersCorrect[s] && s.active = Node + => (FindPredecessor' [s] => FindPredecessor [s])} + +check SameFP for 3 but 1 State expect 1 +check SameFP1 for 3 but 1 State expect 0 +check SameFP2 for 3 but 1 State expect 0 + + +pred FindSuccessor[s: State] { + all n: s.active | let nd = (s.data)[n] | all i: Id { + nd.find_successor[i] = ((s.data)[nd.find_predecessor[i]]).next + }} + + +// should be able to check that closest_p_f, etc returns +// only active nodes if FingersCorrect. + + +pred ShowMe1Node { + #Node = 1 + all s : State | NextCorrect[s] + State.active = Node +} + +run ShowMe1Node for 2 but 1 State, 1 Node expect 1 + +pred ShowMe1 { + #Node = 2 + #State = 1 + all s : State | NextCorrect[s] + State.active = Node +} + + +pred ShowMe2 { + #Node = 3 + #State = 1 + all s : State | NextCorrect[s] && FingersCorrect[s] + State.active = Node + //all n: NodeData | one n.finger[Id] +} + + +assert OK1 { + #Node = 3 && + #State = 1 && + (all s : State | NextCorrect[s] && FingersCorrect[s]) && + State.active = Node +} + + +run ShowMe1 for 3 expect 1 +run ShowMe2 for 3 expect 1 + +assert InjectiveIds {all i, j: Id | i!=j => i.next != j.next} +check InjectiveIds for 5 expect 0 + + +assert FindSuccessorWorks { + all s: State, i: Id | + let nd = s.active.(s.data) | + let succ = nd.find_successor [i] | + FingersCorrect [s] // && s.active = Node + => (no n': s.active | less_than [i, n'.id, succ.id]) + } +check FindSuccessorWorks for 3 but 1 State expect 1 diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/case_studies/chord2.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/case_studies/chord2.als new file mode 100644 index 00000000..ea7c3429 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/case_studies/chord2.als @@ -0,0 +1,274 @@ +module examples/case_studies/chord2 + +/* + * Models the chord distributed hash table lookup protocol. + * + * For a detailed description, see: + * http://www.pdos.lcs.mit.edu/papers/chord:sigcomm01/ + */ + +open util/relation as rel + +sig Id {next: Id} +fact {all i: Id | Id in i.*next} + +/** + * true iff i precedes j in the order starting at from + */ +pred less_than [from, i,j: Id] { + let next' = Id<:next - (Id->from) | j in i.^next' // if from=j, returns true if # nodes > 1 + } +pred less_than_eq [from, i,j: Id] { + let next' = Id<:next - (Id->from) | j in i.*next' + } + +sig Node {id: Id} +fact {all m,n: Node | m!=n => m.id != n.id} + +sig NodeData { + prev, next: Node, + finger: Id -> lone Node, + closest_preceding_finger: Id -> one Node, + find_predecessor: Id -> one Node, + find_successor: Id -> one Node + } + +sig State { + active: set Node, + data: active -> one NodeData + } + +/** + * node n's next node is defined to be the m where n's finger table maps the id + * that follows n.id to m + * next holds the first entry of the finger table + */ +fact {all s: State | all n: s.active | n.(s.data).next = n.(s.data).finger[n.id.next]} + +pred NextCorrect [s: State] { + all n: s.active { + -- no intervening node (ie, close enough) + no n': s.active - n | less_than [n.id, n'.id, n.(s.data).next.id] + -- can reach all other active nodes (ie, far enough) + -- need this because can't rule out case of next being node itself (because of 1-node ring) + -- s.active in n.*(s.data.next) + n.(s.data).next != n || #s.active = 1 + } + } + +/* +-- abortive attempt at simplifying next condition +fun NextCorrect" (s: State) { + all n: s.active | let nx = s.data.next { + s.active in n.*nx + less_than (n.id, n.id, n.nx.id) + } + } +*/ + +pred NextCorrect' [s: State] { +-- next seems to be correct for 1,2,3 nodes + all n: s.active | let nd = (s.data)[n] { + let next' = Id<:next - (Id -> nd.next.id) { + no n' : s.active { n'.id in n.id.^next' } + }} + } + +assert Same1 {all s: State | NextCorrect[s] => NextCorrect'[s]} +//check Same1 for 3 but 1 State -- valid +assert Same2 {all s: State | s.active = Node => (NextCorrect'[s] => NextCorrect[s])} +//check Same2 for 3 but 1 State -- invalid if active condition removed + +-- assert NextInFinger {all s: State | all n: s.active | some n.s.data.finger[n.id.next] } + +/** + * says that finger entry maps an id to a node so that there are no intervening nodes + * between the id and the node + */ +pred FingersCorrect [s: State] { + all nd: s.active.(s.data) | all start:nd.finger.univ | + nd.finger[start] in s.active && + (no n' : s.active | less_than [start, n'.id, nd.finger[start].id]) + } + +pred FingersCorrect' [s: State] { + all n: s.active | let nd = (s.data)[n] | all start: Node.~(nd.finger) { + nd.finger[start] in s.active && + (let next' = Id<:next - (nd.finger[start].id -> Id) { + no n' : s.active - nd.finger[start] { + n'.id in start.*next' + }})} + } + +assert SameFC {all s: State | FingersCorrect [s] iff FingersCorrect'[s]} +//check SameFC for 3 but 1 State + +pred ShowMeFC { + all s : State | s.active = Node && FingersCorrect[s] +} + +//run ShowMeFC for 2 but 1 State + +/* +fun ClosestPrecedingFinger(s: State) { + all n: s.active | let nd = n.s.data | + all i: Id | let cpf = nd.closest_preceding_finger[i] { + no n': nd.finger[Id] | less_than (cpf.id, n'.id, i) + cpf in nd.finger[Id] + n + less_than (n.id, cpf.id, i) + } + } +*/ + +pred ClosestPrecedingFinger_SAVE [s: State] { + all n: s.active | let nd = n.(s.data) | + all i: Id | let cpf = nd.closest_preceding_finger[i] { + no n': (nd.finger[Id] + n) - cpf | less_than [cpf.id, n'.id, i] + cpf in nd.finger[Id] + n + cpf.id != i || # s.active = 1 + //less_than (n.id, cpf.id, i) + } + } + +pred CPFBody [s: State, n: Node, nd: NodeData, i: Id, cpf: Node] { + no n': (nd.finger[Id] + n) - cpf | less_than [cpf.id, n'.id, i] + cpf in nd.finger[Id] + n + cpf.id != i || # s.active = 1 + } +pred ClosestPrecedingFinger[s: State] { + all n: s.active | let nd = n.(s.data) | + all i: Id | + some cpf: Node | CPFBody [s,n,nd,i,cpf] => CPFBody [s,n,nd,i,nd.closest_preceding_finger[i]] + } + +pred ClosestPrecedingFinger'[s: State] { + all n: s.active | let nd = (s.data)[n] | all i: Id { + let next' = Id<:next - (Id -> i) { + nd.next.id in n.id.^next' => + // nd.closest_preceding_finger[i] = nd.next, + (some n1: nd.finger[Id] { + nd.closest_preceding_finger[i] = n1 + //n1 in nd.finger[Id] + n1.id in n.id.^next' + no n2: nd.finger[Id] | n2.id in n1.id.^next' + }) else + nd.closest_preceding_finger[i] = n + }} + } + +assert SameCPF {all s: State | FingersCorrect[s] => (ClosestPrecedingFinger [s] iff ClosestPrecedingFinger' [s])} +assert SameCPF1 {all s: State | FingersCorrect[s] => (ClosestPrecedingFinger [s] => ClosestPrecedingFinger' [s])} +assert SameCPF2 { + all s: State | ((s.active = Node && FingersCorrect[s] && ClosestPrecedingFinger' [s]) + => ClosestPrecedingFinger [s]) } +//check SameCPF for 3 but 1 State +//check SameCPF1 for 2 but 1 State +//check SameCPF2 for 3 but 1 State + +pred ShowMeCPF { + all s : State | s.active = Node && FingersCorrect[s] && + // not ClosestPrecedingFinger(s) && ClosestPrecedingFinger'(s) + ClosestPrecedingFinger[s] + //all s : State | all nd : s.active.s.data | nd.finger[Id] = Node + # Node = 2 + # State = 1 +} + +//run ShowMeCPF for 2 but 1 State + +pred FindPredecessor[s: State] { + all n: s.active | let nd = n.(s.data) | all i: Id { + nd.find_predecessor[i] = + ((less_than_eq [n.id, i, nd.next.id] && (n.id != i || # s.active = 1)) + => n + else (nd.closest_preceding_finger[i].(s.data).find_predecessor)[i]) + } + } +-- problem : could return node that's inactive ??? + +assert FPisActive { + all s: State | FingersCorrect[s] && ClosestPrecedingFinger[s] && FindPredecessor[s] + => (all n: s.active | all nd: n.(s.data) | nd.find_predecessor[Id] in s.active) +} + +//check FPisActive for 3 but 1 State + +pred FindPredecessor'[s: State] { + all n: s.active | let nd = (s.data)[n] | all i: Id { + let next' = Id<:next - (nd.next.id -> Id) { + one s.active or i in n.id.^next' => + nd.find_predecessor[i] = n else + nd.find_predecessor[i] = + ((s.data)[nd.closest_preceding_finger[i]]).find_predecessor[i] + }} + } + +assert SameFP {all s: State | FingersCorrect[s] && s.active = Node + => (FindPredecessor [s] iff FindPredecessor' [s])} +assert SameFP1 { + all s: State | FingersCorrect[s] && s.active = Node + => (FindPredecessor [s] => FindPredecessor' [s])} +assert SameFP2 { + all s: State | FingersCorrect[s] && s.active = Node + => (FindPredecessor' [s] => FindPredecessor [s])} +//check SameFP for 3 but 1 State +//check SameFP1 for 3 but 1 State +//check SameFP2 for 3 but 1 State + +pred FindSuccessor[s: State] { + all n: s.active | let nd = (s.data)[n] | all i: Id { + nd.find_successor[i] = ((s.data)[nd.find_predecessor[i]]).next + }} + +fact { all s : State { + ClosestPrecedingFinger[s] + FindPredecessor[s] + FindSuccessor[s] + }} + +// should be able to //check that closest_p_f, etc returns +// only active nodes if FingersCorrect. + +pred ShowMe1Node { + #Node = 1 + all s : State | NextCorrect[s] + State.active = Node +} + +//run ShowMe1Node for 2 but 1 State, 1 Node +-- does the expected correct thing for 1 node. + +pred ShowMe1 { + #Node = 2 + #State = 1 + all s : State | NextCorrect[s] + State.active = Node +} + +pred ShowMe2 { + #Node = 3 + #State = 1 + all s : State | NextCorrect[s] && FingersCorrect[s] + State.active = Node + //all n: NodeData | one n.finger[Id] +} + +assert OK1 { + #Node = 3 && + #State = 1 && + (all s : State | NextCorrect[s] && FingersCorrect[s]) && + State.active = Node +} + +assert FindSuccessorWorks { + all s: State, i: Id | + let nd = s.active.(s.data) | + let succ = nd.find_successor [i] | + FingersCorrect [s] + => { + no n': s.active | less_than [i, n'.id, succ.id] + succ in s.active + } + } + +check FindSuccessorWorks for 4 but 1 State, 3 Node, 3 NodeData expect 0 diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/case_studies/chordbugmodel.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/case_studies/chordbugmodel.als new file mode 100644 index 00000000..de6c36bb --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/case_studies/chordbugmodel.als @@ -0,0 +1,232 @@ +module examples/case_studies/chord + +/* + * Models the chord distributed hash table lookup protocol. + * + * For a detailed description, see: + * http://www.pdos.lcs.mit.edu/papers/chord:sigcomm01/ + */ + +sig Id {next: Id} +fact {all i: Id | Id in i.*next} + +pred less_than [from, i,j: Id] { + let next' = Id<:next - (Id->from) | j in i.^next' +} + +pred less_than_eq [from, i,j: Id] { + let next' = Id<:next - (Id->from) | j in i.*next' +} + +sig Node {id: Id} +fact {all m,n: Node | m!=n => m.id != n.id} + +sig NodeData { + next: Node, + finger: Id -> lone Node, + closest_preceding_finger: Id -> one Node, + find_successor: Id -> one Node +} + +sig State { + active: set Node, + data: active -> one NodeData +} + +fact { + all s: State | all n: s.active | + n.(s.data).next = n.(s.data).finger[n.id.next] +} + +pred NextCorrect [s: State] { + all n: s.active | let succ = n.(s.data).next { + no n': s.active - n | less_than [n.id, n'.id, succ.id] + succ != n || #s.active = 1 + succ in s.active + } +} + +pred FingersCorrect [s: State] { + all nd: s.active.(s.data) | all start: (nd.finger).Node | + nd.finger[start] in s.active && + (no n' : s.active | less_than [start, n'.id, nd.finger[start].id]) +} + +pred save_ClosestPrecedingFinger [s: State] { + all n: s.active | let nd = n.(s.data) | + all i: Id | let cpf = nd.closest_preceding_finger[i] { + no n': (nd.finger[Id] + n) - cpf | less_than [cpf.id, n'.id, i] + cpf in nd.finger[Id] + n + cpf.id != i || # s.active = 1 + } +} + +pred save_FindSuccessor[s: State] { + all n: s.active | let nd = n.(s.data) | all i: Id { + nd.find_successor[i] = + (((less_than_eq [n.id, i, nd.next.id] && n.id != i) || # s.active = 1) + => nd.next + else + (nd.closest_preceding_finger[i].(s.data).find_successor)[i]) + } +} + +pred IrrelevantFact1 { + all s : State { + ClosestPrecedingFinger[s] + FindSuccessor[s] + } +} + +pred ShowMe1Node { + #Node = 1 + all s : State | NextCorrect[s] && FingersCorrect[s] + State.active = Node +} + +run ShowMe1Node for 2 but 1 State, 1 Node expect 1 + +pred ShowMeGood { + #Id = 4 + all s : State | NextCorrect[s] && FingersCorrect[s] + State.active = Node +} + +run ShowMeGood for 4 but 1 State, 2 Node expect 1 + +pred FindSuccessorIsCorrect[s: State] { + all i: Id | all n: s.active | + let succ = (n.(s.data)).find_successor [i] { + succ in s.active + no n': s.active | less_than [i, n'.id, succ.id] + } +} + +pred ShowMeCorrectSuccessorEg { + #Node = 3 + State.active = Node + all s: State | FingersCorrect[s] && FindSuccessorIsCorrect[s] +} + +run ShowMeCorrectSuccessorEg for 3 but 1 State expect 1 + +pred ShowMe3 { + #Id = 5 + #Node = 3 + #State = 1 + all s : State | NextCorrect[s] && !FingersCorrect[s] + State.active = Node +} + +run ShowMe3 for 5 but 1 State expect 1 + +pred FindSuccessorWorks { + IrrelevantFact1 + ! ( + all s: State | FingersCorrect[s] + => FindSuccessorIsCorrect[s] + ) +} + +assert StrongerFindSuccessorWorks { + all s: State | NextCorrect[s] => FindSuccessorIsCorrect[s] +} + +run FindSuccessorWorks for 4 but 1 State expect 0 +check StrongerFindSuccessorWorks for 4 but 1 State expect 1 + +/* +\section Variations + +In the pseudocode presented in [\cite{chord1}, +\cite{chord2}], there is some ambiguity as to what the +expression \tt<(n, n.successor]> means in boundary cases +where there is exactly one node and \tt. +The intention of the authors is that the set includes +\tt. We consider variations of the alloy model with the +bug where the set \tt<(n, n]> does not include \tt, and +observe how it affects the \tt and +the \tt routines. + +\subsection faulty \tt + +Suppose we change \tt as follows: + +\code +*/ + +pred ClosestPrecedingFinger [s: State] { + all n: s.active | let nd = n.(s.data) | + all i: Id | let cpf = nd.closest_preceding_finger[i] { + no n': (nd.finger[Id] + n) - cpf | less_than [cpf.id, n'.id, i] + cpf in nd.finger[Id] + n + cpf.id != i + } +} + +/* +The only change here is in the last line +\cite{cpf-variation}, where we removed the clause \tt< || # +s.active=1>. The assertion \tt will +still hold for scope up to 4, but \tt will fail +to generate an example! This is an example of a +over-constraint, where the inconsistency only shows up when +there is exactly one node. What happens here is that the +model requires that a closest preceding finger node has a +distinct identifier from the input identifier, but this +cannot happen if there is exactly one node and if the input +identifer equals that of the node. + +\subsection faulty \tt + +Consider the following pseudocode segment from [\cite{chord2}]: + +n.find_successor(id) + if (id in (n, n.successor]) + return n.successor; + else + n' = closest_preceding_finger(id); + return n'.find_successor(id); + +In the buggy scenario with a single node, the \tt loop +always terminates at \cite{if-condition1}, leading to an +infinite loop. + +Consider the corresponding change to \tt as follows: + +*/ + +pred FindSuccessor[s: State] { + all n: s.active | let nd = n.(s.data) | all i: Id { + nd.find_successor[i] = + ((less_than_eq [n.id, i, nd.next.id] && n.id != i) + => nd.next + else + (nd.closest_preceding_finger[i].(s.data).find_successor)[i]) + } +} + +/* +The only change here is in the fourth line +\cite{sf-variation}, where we removed the clause \tt< || # +s.active = 1>. For the same reason, the \tt loop +in this case always proceeds to the \tt clause, +and since \tt always returns +\tt (the only node in the network), we end up +with a tautological statement: + +\code + nd.find_successor[i] = n.s.data.find_successor)[i] + +This means that there is no additional constraint placed on +\tt, other than that its return type is +\tt. Now, if there is no distinction between active +and inactive nodes, that is, we have exactly one active node +in the network and no inactive ones, \tt +will return the right answer due to the type constraint, +therefore obscuring the bug. On the other hand, since we +have introduced inactive nodes, the assertion +\tt now fails with exactly one active +node and some inactive node(s), with \tt +returning an inactive node. +*/ diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/case_studies/com.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/case_studies/com.als new file mode 100644 index 00000000..8cf6f08c --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/case_studies/com.als @@ -0,0 +1,96 @@ +module examples/case_studies/com + +/* + * Model of Microsoft Component Object Model (COM) query + * interface and aggregation mechanism. + * + * For a detailed description, see: + * http://sdg.lcs.mit.edu/~dnj/publications/com-fse00.pdf + * + * author: Daniel Jackson + */ + +open util/relation as rel + +sig IID {} + +sig Interface { + qi : IID -> lone Interface, + iids : set IID, + // next two lines should use domain() or range() functions + iidsKnown : IID, + reaches : Interface +}{ + iidsKnown = dom[qi] + reaches = ran[qi] +} + +sig Component { + interfaces : set Interface, + iids : set IID, // can't do iids = interfaces.Interface$iids + first, identity : interfaces, + eqs: set Component, + aggregates : set Component +} + +fact defineEqs { + all c1, c2: Component | + c1->c2 in eqs <=> c1.identity = c2.identity +} + +fact IdentityAxiom { + some unknown : IID | all c : Component | + all i : c.interfaces | unknown.(i.qi) = c.identity +} + +fact ComponentProps { + all c : Component { + c.iids = c.interfaces.iids + all i : c.interfaces | all x : IID | x.(i.qi) in c.interfaces + } +} + +sig LegalInterface extends Interface { } +fact { all i : LegalInterface | all x : i.iidsKnown | x in x.(i.qi).iids} + +sig LegalComponent extends Component { } +fact { LegalComponent.interfaces in LegalInterface } + +fact Reflexivity { all i : LegalInterface | i.iids in i.iidsKnown } +fact Symmetry { all i, j : LegalInterface | j in i.reaches => i.iids in j.iidsKnown } +fact Transitivity { all i, j : LegalInterface | j in i.reaches => j.iidsKnown in i.iidsKnown } + +fact Aggregation { + no c : Component | c in c.^aggregates + all outer : Component | all inner : outer.aggregates | + (some inner.interfaces & outer.interfaces) + && (some o: outer.interfaces | all i: inner.interfaces - inner.first | all x: Component | (x.iids).(i.qi) = (x.iids).(o.qi)) + } + +assert Theorem1 { + all c: LegalComponent | all i: c.interfaces | i.iidsKnown = c.iids + } + +assert Theorem2 { + all outer: Component | all inner : outer.aggregates | + inner in LegalComponent => inner.iids in outer.iids + } + +assert Theorem3 { + all outer: Component | all inner : outer.aggregates | inner in outer.eqs + } + +assert Theorem4a { + all c1: Component, c2: LegalComponent | + some (c1.interfaces & c2.interfaces) => c2.iids in c1.iids + } + +assert Theorem4b { + all c1, c2: Component | some (c1.interfaces & c2.interfaces) => c1 in c2.eqs + } + +check Theorem1 for 3 expect 0 +check Theorem2 for 3 expect 0 +check Theorem3 for 3 expect 0 +check Theorem4a for 3 expect 0 +check Theorem4b for 3 expect 0 diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/case_studies/firewire.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/case_studies/firewire.als new file mode 100644 index 00000000..5de1afaf --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/case_studies/firewire.als @@ -0,0 +1,384 @@ +module examples/case_studies/firewire + +/* + * A model of leader election in the Firewire protocol. + * + * Adapted from: + * [DG+01] M.C.A. Devillers, W.O.D. GriAEoen, J.M.T Romijn, and F.W. Vaandrager. + * Verification of a leader election protocol -- formal methods applied to IEEE + * 1394. Technical Report CSI-R9728, Computing Science Institute, University of + * Nijmegen, December 1997. Also, Formal Methods in System Design, 2001. + * + * This model describes a leader election protocol used in Firewire, an IEEE + * standard for connecting consumer electronic devices. The model is a + * straightforward translation into Alloy of a model [DG+01] developed in Lynch's + * IO Automata which has been analyzed using PVS, a theorem prover, but which, as + * far as we know, has not been subjected to fully automatic analysis. We are able + * to express the key correctness property -- that exactly one leader is elected + * -- more directly, as a trace property rather than a refinement property, and + * can check it without the need for the 15 invariants used in the more + * traditional proof. And the analysis does not hardwire + * a particular topology, so would be tricky to do with a standard model checker. + * + * The network is assumed to consist of a collection of nodes connected by + * links. Each link between a pair of nodes is matched by a link in the other + * direction. Viewing a link and its dual as a single, undirected edge, the + * network as a whole is assumed to form a tree. The purpose of the algorithm is + * to construct such a tree; in the model, this is achieved by labelling some + * subset of the links as parent links (each pointing from a node to its parent), + * and by marking a single node as the root. + * + * The algorithm, described in detail elsewhere [DG+01], works briefly as + * follows. When a node detects that all of its incoming links (or all but one) + * has been marked as a parent link, it sends a message on each outgoing link, + * either an acknowledgment (indicating its willingness to act as parent), or a + * request (indicating its desire to be a child), according to whether the dual of + * the outgoing link has been marked or not. Leaf nodes (with only one incoming + * link) may thus initiate the algorithm by sending requests to their adjacent + * nodes. Performing this action changes a node's status from {\em waiting} to + * {\em active}. A node that is still waiting, and which receives a message on a + * link, may label that link a parent link. Once active, a node that receives an + * acknowledgment on a link may also label the link, but if it receives a request, + * instead changes its node status to {\em contending}. The resolving of + * contentions is modelled simplistically by a single action that arbitrarily + * labels one of the two links a pair of contending nodes. Finally, a node all of + * whose incoming links are parent links designates itself a root. + * + * The specification is given below. Each signature introduces a basic type + * and some relations whose first column has that type: + * + * \begin{itemize} + * + * \item {\em Msg} represents the type of messages. {\em Req} is the request + * message and {\em Ack} is the acknowledgment message; these are actually + * declared as singleton (keyword {\em static}) subsets of {\em Msg}, the set of + * all messages, that form a partition (keyword {\em part}). + * + * \item {\em Node} represents the nodes of the network. The relations {\em to} + * and {\em from} associate each node with a set of incoming and outgoing links + * respectively. + * + * \item {\em Link} represents the links. The relations {\em target} and {\em + * source} map a link to its end nodes; {\em reverse} maps a link to its dual. The + * facts in the signatures {\em Node} and {\em Link} ensure that all these + * relations are consistent with one another: that the incoming links of a node + * are those whose target is that node, etc. + * + * \item {\em Op} introduces a set of names for the operations of the + * protocol. This is merely for convenience; it allows us to ask for an execution + * in which named operations occur, or example. + * + * \item {\em State} represents the global states. Each state has a partition of + * the nodes into one of four statuses: {\em waiting} to participate, {\em active} + * (having sent messages on outgoing links), {\em contending} (having sent a + * request on a link and received a request on its dual), and {\em elected} + * (having designated itself as a root). A set of links are labelled as parent + * links. There is a message queue associated with each link. Finally, the state + * is associated with the operation that produced it. + * + * \item {\em Queue} represents the message queues. Each queue has a slot that + * optionally contains a message; the relation {\em slot} is a partial function + * from queues to messages. In our first attempt at a model, we represented a + * queue as a sequence (a partial function from a prefix of the integers to + * messages), but having checked that no queue ever contained more than one + * message, we simplified the model. The {\em overflow} field is included just in + * case this was a mistake; a write to a queue that already contains a message + * puts an arbitrary value there, which is easily detected. + * + * \end{itemize} + * + * The {\em facts} record the assumptions about the topology. The one named {\em + * Topology} says that there is some partial function on nodes and some root such + * that (1) every node is reachable from the root ({\tt *r} being the reflexive + * transitive closure of the relation {\tt r}); (2) there are no cycles (expressed + * by saying that the transitive closure has no intersection with the identity + * relation on nodes); and (3) the relation obtained by following the {\em source} + * relation backwards (from a node to the link for which it is a source), and then + * the {\em target} relation forwards (from the link to its target) is this + * relation, plus its transpose (so that each tree edge becomes two + * links). Although the quantifier appears to be higher-order, it will be + * skolemized away by the analyzer. + * + * The {\em functions} of the model are parameterized formulas. The function {\em + * Trans} relates a pre-state {\tt s} to a post-state {\tt s'}. It has a case for + * each operation. Look at the clause for the operation {\em WriteReqOrAck}, for + * example. If this operation is deemed to have occurred, each of the constraints + * in the curly braces must hold. The first says that the labelling of links as + * parent links is unchanged. The second constraint (the quantified formula) + * constrains with respect to the node at which the operation occurs. The + * subformulas, from first to last, say that the node belongs to the waiting set + * before and the active set afterwards; that there is at most one ({\em sole}) + * link that is incoming but not a parent link in the pre-state; that there are no + * changes to node status except at this node; that a message is queued onto each + * outgoing link; and that queues on all other links are unchanged. + * + * An 'invoked' function is simply short for the formula in its body with the + * formal arguments replaced by the actual expressions. {\em WriteQueue}, for + * example, says that if the queue's slot is not filled in the pre-state, then the + * new queue in the post-state (given the local name {\tt q}) contains the message + * {\tt m} in its slot, and has no message in its overflow. Otherwise, some + * message is placed arbitrarily in the overflow, and the slot is + * unconstrained. In {\em WriteReqOrAck}, the arguments {\tt s} and {\tt s'} are + * bound to the {\tt s} and {\tt s'} of {\em Trans}; {\tt x} is bound to one of + * the outgoing links from the set {\tt n.from}; and {\tt msg} is bound either to + * the acknowledgment or request message. + * + * The function {\em Execution} constrains the set of states. It makes use of a + * library module that defines a polymorphic ordering relation. The expression + * {\tt Ord[State]} gives an ordering on all states. The two formulas of the + * function say that {\tt Initialization} holds in the first state, and that any + * pair of adjacent states is related by {\tt Trans}. The function {\em NoRepeats} + * adds the constraints that there are no equivalent states in the trace, and that + * no stuttering occurs. + * + * The three assertions are theorems for which the analyzer will search for + * counterexamples. They assert respectively that: in every state of the trace, + * there is at most one node that has been elected; that there is some state in + * which a node has been elected; and that no queue overflows. + * + * The rest of the model is a collection of commands executed to find instances of + * the functions or counterexamples to the theorems. We started by presenting a + * variety of functions as a sanity check; here, only one is given, that asks for + * an execution involving 2 nodes, 4 links, 4 queues and a trace of 6 states. The + * standard semantics of these {\em scope} declarations in Alloy is that the + * numbers represent an upper bound, so an instance may involve fewer than 4 + * queues, for example. The ordering module (not shown here), however, for + * technical reasons, constrains the ordered set to match its scope, so a trace + * with fewer than 6 states will not be acceptable. + * + * We then established some bounds on the diameter of the state machine for + * various topology bounds. For 2 nodes and 2 links, for example, there are no + * non-repeating traces of length 4; checking traces of length 3 is thus + * sufficient in this case. The number of queues was limited to 5, to accommodate + * the empty queue, a queue containing an {\tt Ack} or {\tt Req}, and each of + * these with overflow. For 3 nodes and 6 links, a trace length of 8 suffices. + * + * We then checked that for these various topology bounds, the queues never + * overflow. Finally, we checked the correctness properties, taken advantage of + * the earlier results that justify the short traces and queues. We are thus able + * to verify the properties for all topologies involving the given number of nodes + * and links, without any assumptions about trace length, queue size or the + * particular topological structure. + * + * author: Daniel Jackson + * visualization: Robert Seater + */ + +open util/ordering[State] as ord + +abstract sig Msg {} +one sig Req, Ack extends Msg {} + +sig Node {to, from: set Link} { + to = {x: Link | x.target = this} + from = {x: Link | x.source = this} + } + +sig Link {target, source: Node, reverse: Link} { + reverse.@source = target + reverse.@target = source + } + +/** + * at most one link between a pair of nodes in a given direction + */ +fact {no x,y: Link | x!=y && x.source = y.source && x.target = y.target} + +/** + * topology is tree-like: acyclic when viewed as an undirected graph + */ +fact Topology { +some tree: Node lone -> Node, root: Node { + Node in root.*tree + no ^tree & iden & Node->Node + tree + ~tree = ~source.target + } +} + +sig Op {} +one sig Init, AssignParent, ReadReqOrAck, Elect, WriteReqOrAck, +ResolveContention, Stutter extends Op {} + +sig State { + disj waiting, active, contending, elected: set Node, + parentLinks: set Link, + queue: Link -> one Queue, + op: Op -- the operation that produced the state + } { + waiting + active + contending + elected = Node +} + +pred SameState [s, s': State] { + s.waiting = s'.waiting + s.active = s'.active + s.contending = s'.contending + s.elected = s'.elected + s.parentLinks = s'.parentLinks + all x: Link | SameQueue [s.queue[x], s'.queue[x]] + } + +pred Trans [s, s': State] { + s'.op != Init + s'.op = Stutter => SameState [s, s'] + s'.op = AssignParent => { + some x: Link { + x.target in s.waiting & s'.waiting + NoChangeExceptAt [s, s', x.target] + ! IsEmptyQueue [s, x] + s'.parentLinks = s.parentLinks + x + ReadQueue [s, s', x] + }} + s'.op = ReadReqOrAck => { + s'.parentLinks = s.parentLinks + some x: Link { + x.target in s.(active + contending) & (PeekQueue [s, x, Ack] => s'.contending else s'.active) + NoChangeExceptAt [s, s', x.target] + ! IsEmptyQueue [s, x] + ReadQueue [s', s, x] + }} + s'.op = Elect => { + s'.parentLinks = s.parentLinks + some n: Node { + n in s.active & s'.elected + NoChangeExceptAt [s, s', n] + n.to in s.parentLinks + QueuesUnchanged [s, s', Link] + }} + s'.op = WriteReqOrAck => { + -- note how this requires access to child ptr + s'.parentLinks = s.parentLinks + some n: Node { + n in s.waiting & s'.active + lone n.to - s.parentLinks + NoChangeExceptAt [s, s', n] + all x: n.from | + let msg = (x.reverse in s.parentLinks => Ack else Req) | + WriteQueue [s, s', x, msg] + QueuesUnchanged [s, s', Link - n.from] + }} + s'.op = ResolveContention => { + some x: Link { + let contenders = x.(source + target) { + contenders in s.contending & s'.active + NoChangeExceptAt [s, s', contenders] + } + s'.parentLinks = s.parentLinks + x + } + QueuesUnchanged [s, s', Link] + } +} + +pred NoChangeExceptAt [s, s': State, nodes: set Node] { + let ns = Node - nodes { + ns & s.waiting = ns & s'.waiting + ns & s.active = ns & s'.active + ns & s.contending = ns & s'.contending + ns & s.elected = ns & s'.elected + }} + +sig Queue {slot: lone Msg, overflow: lone Msg} + +pred SameQueue [q, q': Queue] { + q.slot = q'.slot && q.overflow = q'.overflow + } + +pred ReadQueue [s, s': State, x: Link] { +-- let q = s'.queue[x] | no q.(slot + overflow) + no s'.queue[x].(slot + overflow) + all x': Link - x | s'.queue[x'] = s.queue[x'] + } + +pred PeekQueue [s: State, x: Link, m: Msg] { + m = s.queue[x].slot + } + +pred WriteQueue [s, s': State, x: Link, m: Msg] { + let q = s'.queue[x] | + no s.queue[x].slot => + ( q.slot = m && no q.overflow) else + some q.overflow + } + +pred QueuesUnchanged [s, s': State, xs: set Link] { + all x: xs | s'.queue[x] = s.queue[x] + } + +pred IsEmptyQueue [s: State, x: Link] { + no s.queue[x].(slot + overflow) +-- let q = s.queue[x] | no q.(slot + overflow) + } + +pred Initialization [s: State] { + s.op = Init + Node in s.waiting + no s.parentLinks + all x: Link | IsEmptyQueue [s, x] + } + +pred Execution { + Initialization [ord/first] + all s: State - ord/last | let s' = ord/next[s] | Trans [s, s'] + } + +pred ElectionHappens { + Execution + some s: State | some s.elected + some s: State | no s.elected +} + +pred NoRepeats { + Execution + no s, s': State | s!=s' && SameState [s, s'] + no s: State | s.op = Stutter + } + +pred NoShortCuts { + all s: State | -- remove this to speed up analysis - Ord[State].last - OrdPrev (Ord[State].last) | + ! Trans [s, ord/next[ord/next[s]]] + } + +assert AtMostOneElected { + Execution => (all s: State | lone s.elected) + } + +assert OneEventuallyElected { + Execution => (some s: State | some s.elected) + } + +assert NoOverflow { + Execution => (all s: State, x: Link | no s.queue[x].overflow) + } + +run Execution for 7 Op, 2 Msg, + 2 Node, 4 Link, 4 Queue, 6 State expect 1 + +run ElectionHappens for 7 Op, 2 Msg, + exactly 3 Node, 6 Link, 3 Queue, 7 State expect 1 + +-- solution for 3 State but not for 4 State +run NoRepeats for 7 Op, 2 Msg, + 2 Node, 2 Link, 2 Queue, 4 State expect 0 + +-- solution for 8 but not 9 State +run NoRepeats for 7 Op, 2 Msg, + 3 Node, 6 Link, 6 Queue, 8 State expect 0 + +-- only 5 queues needed: just count +-- no solution: establishes at most 3 queues needed +check NoOverflow for 7 Op, 2 Msg, + 3 Node, 6 Link, 5 Queue, 9 State expect 0 + +check AtMostOneElected for 7 Op, 2 Msg, + 3 Node, 6 Link, 3 Queue, 9 State expect 0 + +check OneEventuallyElected for 7 Op, 2 Msg, + 3 Node, 6 Link, 3 Queue, 9 State expect 1 + + + +// DEFINED VARIABLES +// Defined variables are uncalled, no-argument functions. +// They are helpful for getting good visualization. +fun queued: State -> Link -> Msg { + {s: State, L: Link, m: Msg | m in L.(s.queue).slot} +} diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/case_studies/firewire.thm b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/case_studies/firewire.thm new file mode 100644 index 00000000..04793a40 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/case_studies/firewire.thm @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/case_studies/ins.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/case_studies/ins.als new file mode 100644 index 00000000..e5596229 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/case_studies/ins.als @@ -0,0 +1,115 @@ +module examples/case_studies/ins + +/* + * Models an Intentional Naming System (INS), a scheme for + * dynamic resource discovery in a dynamic environment. + * + * For a detailed description, see: + * http://sdg.lcs.mit.edu/pubs/2000/INS_ASE00.pdf + * + * author: Sarfraz Khurshid + */ + +open util/relation as rel + +sig Attribute {} +sig Value {} +sig Record {} + +one sig Wildcard extends Value {} + +sig AVTree { + values: set Value, + attributes: set Attribute, + root: values - Wildcard, + av: attributes one -> some (values - root), + va: (values - Wildcard) one -> attributes +}{ + // values (and attributes) of tree are those reachable from root + values = root.*(va.av) +} + +sig Query extends AVTree {} {all a:attributes | one a.av} + +sig DB extends AVTree { + records : set Record, + recs: (values - root) some -> records, + lookup : Query -> (values -> records) +}{ + Wildcard !in values +} + +fact AddInvariants { + all db: DB { + all v: db.values | no v.(db.recs) & v.^(~(db.av).~(db.va)).(db.recs) + all a: db.attributes | all disj v1, v2: a.(db.av) | + (some rr: *((db.va).(db.av)).(db.recs) | no v1.rr & v2.rr) + } +} + +pred Get [db: DB, r: Record, q: Query] { + q.values = r.~(db.recs).*(~(db.av).~(db.va)) + q.attributes = q.values.~(db.av) + q.root = db.root + all a : attributes| a.~(q.va) = a.~(db.va) + all v : values | v.~(q.av) = v.~(db.av) +} + +pred Conforms [db: DB, q: Query, r: Record] { + some p: Query { + db.Get[r, p] + q.va in p.va + (q.av - Attribute -> Wildcard) in p.av + } +} + +pred indSubset[db : DB, q: Query, r: set Record, v: Value] { + all a : v.(q.va) | + (a.(q.av) in a.(db.av) => r in (a.(q.av)).(q.(db.lookup))) && + (a.(q.av) = Wildcard => r in a.(db.av).*((db.va).(db.av)).(db.recs)) +} + +pred Lookup[db: DB, q: Query, found: set Record] { + all v: Value | not v.(q.va) in v.(db.va) => no v.(q.(db.lookup)) + all v: Value | all a : v.(q.va) | + a.(q.av) != Wildcard && not a.(q.av) in a.(db.av) => no v.(q.(db.lookup)) + all v: Value - Wildcard | + no v.(q.va) => v.(q.(db.lookup)) = v.*((db.va).(db.av)).(db.recs) + all v: Value | + some v.(q.va) => indSubset[db, q, v.(q.(db.lookup)), v] && + (no r: Record - v.(q.(db.lookup)) | indSubset[db, q, v.(q.(db.lookup)) + r, v]) + found = db.root.(q.(db.lookup)) +} + +assert CorrectLookup { + all db: DB | all q : Query | all r : Record | + Conforms [db,q,r] <=> db.Lookup[q, r] +} + +pred Add [me: DB, adv: Query, r: Record, db: DB] { + // restricted version - only advertisements with fresh attributes and values added + no me.attributes & adv.attributes + me.values & adv.values = me.root + me.root = adv.root + Wildcard !in adv.values + r !in me.records + db.values = me.values + adv.values + db.attributes = me.attributes + adv.attributes + db.root = me.root + db.av = me.av + adv.av + db.va = me.va + adv.va + db.recs = me.recs + ((db.values - dom[db.va]) -> r) +} + +pred RemoveWildCard[me: Query, q: Query] { + q.values = me.values - Wildcard + q.attributes = me.attributes - Wildcard.~(me.av) + q.root = me.root + q.av = me.av - Attribute -> Wildcard + q.va = me.va - Value -> Wildcard.~(me.av) +} + +assert MissingAttributeAsWildcard { + all db : DB, q, q' : Query, found: set Record | + db.Lookup[q, found] && q.RemoveWildCard[q'] => db.Lookup[q', found] +} diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/case_studies/iolus.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/case_studies/iolus.als new file mode 100644 index 00000000..cc27ba39 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/case_studies/iolus.als @@ -0,0 +1,320 @@ +module examples/case_studies/iolus + +/* + * This is a model of Iolus, a scheme for secure multicasting. + * In this scheme, nodes multicast messages to other nodes + * within a group whose membership changes dynamically. The + * group is partitioned into subgroups, arranged in a tree, + * each with its own Key Distribution Server (KDS). + * + * For a detailed description, see: + * Mana Taghdiri, "Lightweight Modelling and Automatic Analysis + * of Multicast Key Management Schemes", Masters Thesis, Dept. + * of EECS, MIT, Dec. 2002. + * http://sdg.lcs.mit.edu/pubs/theses/taghdiri_masters.pdf + * + * author: Mana Taghdiri + */ + +open util/ordering[Tick] as ord + +sig Tick {} + +/** + * It can be abstract, since the fact below says Key=GroupKey + */ +abstract sig Key {} + +/** + * It can be abstract, since the fact below says Message=DataMessage + */ +abstract sig Message { + sender : Member, + sentTime : Tick, + key : Key +} + +/** + * It can be abstract, since the fact below says KDS=GSA + */ +abstract sig KDS { + keys : Tick -> Key, + members : Tick -> Member +}{ + Monotonic[keys] + all t : Tick | let t' = ord/prev[t] { + all m : members[t]-members[t'] | Join[m, t, this] + all m : members[t']-members[t] | Leave[m, t] + } +} + +/** + * It can be abstract, since the fact below says "Member=Client" + */ +abstract sig Member { + ownedKeys : Tick -> Key, + receivedMessages : Tick -> Message +}{ + Monotonic[ownedKeys] + Monotonic[receivedMessages] +} + +fact MemberBehavior { + Init[ord/first] + all m : Member, t : Tick - ord/first | + (some msg : Message | + SendMessage[m, t, msg] || ReceiveMessage[m, t, msg]) || + (some kds : KDS | Join[m, t, kds]) || + Leave[m, t] || MemberInactive[m, t] +} + +pred Monotonic[r : Tick -> univ] { + all t : Tick | ord/prev[t].r in t.r +} + +---------------------------------------------- +sig GroupKey extends Key { + generator : GSA, + generatedTime : Tick +}{ + some c : Client | + (Join[c, generatedTime, c.server] || Leave[c, generatedTime]) && + c.server = generator +} + +sig DataMessage extends Message { + gsaID : GSA, + retransmitTime : Tick } +{ SendMessage[sender, sentTime, this] || + (some msg' : DataMessage | + Remulticast[gsaID, msg', retransmitTime, this]) } + +sig GSA extends KDS { + parent : lone GSA } +{ keys[Tick].generator = this + all t : Tick, k : keys[t] - keys[ord/prev[t]] | + k.generatedTime = t } + +sig Client extends Member { + server : GSA } +{ all t : Tick, k : ownedKeys[t] - ownedKeys[ord/prev[t]] | + k.generator = server && k.generatedTime = t } + +fact IolusProperties { + no k, k' : GroupKey | k!=k' && k.generator = k'.generator && k.generatedTime = k'.generatedTime + all g : GSA, msg : DataMessage, t : Tick | RemulticastConditions[g, msg, t] => + (some msg': DataMessage | Remulticast[g, msg, t, msg']) +} + +fact GSATree { + let root = {g : GSA | no g.parent} { + one root + GSA in root.*~parent }} + +fact { + Member = Client + KDS = GSA + Message = DataMessage + Key = GroupKey + no m, m' : DataMessage { + m!=m' + m.sender = m'.sender + m.sentTime = m'.sentTime + m.key = m'.key + m.gsaID = m'.gsaID + m.retransmitTime = m'.retransmitTime } +} + +---------------------------------------------- +pred Init[t : Tick] { + no Member.receivedMessages[t] + no Member.ownedKeys[t] + no KDS.keys[t] + no KDS.members[t] } + +pred Join[m : Member, t : Tick, kds : KDS] { + kds = m.server + JoinRequest[m, kds, t] + NoChange[m.receivedMessages, t] +} +pred JoinRequest[c : Client, gsa : GSA, t : Tick] { + c !in gsa.members[ord/prev[t]] + KeyUpdate[gsa, t] + c in gsa.members[t] } + +pred Leave[m : Member, t : Tick] { + LeaveRequest[m, m.server, t] + NoChange[m.receivedMessages, t] } + +pred LeaveRequest[c : Client, gsa : GSA, t : Tick] { + c in gsa.members[ord/prev[t]] + KeyUpdate[gsa, t] + c !in gsa.members[t] } + +pred SendMessage[m : Member, t : Tick, msg : Message] { + SendRequest[m, m.server, t, msg] + m.receivedMessages[t] = m.receivedMessages[ord/prev[t]] + msg + ConstantMembership[m, t] } + +pred SendRequest[c : Client, gsa : GSA, t : Tick, msg : DataMessage] { + c in gsa.members[t] + msg.sender = c + msg.sentTime = t + NewestKey[gsa.keys[t], msg.key] + msg.gsaID = gsa + msg.retransmitTime = t + (some gsa.parent.members[t]) => + (some msg' : DataMessage | Remulticast[gsa, msg, t, msg']) } + +pred ReceiveMessage[m : Member, t : Tick, msg : Message] { + ReceiveConditions[m, t, msg] + m.receivedMessages[t] = m.receivedMessages[ord/prev[t]] + msg } + +pred MemberInactive[m : Member, t : Tick] { + NoChange[m.receivedMessages, t] --does not constrain owned keys + ConstantMembership[m, t] } + +pred ReceiveConditions[m : Member, t : Tick, msg : Message] { + ConstantMembership[m, t] + msg !in m.receivedMessages[ord/prev[t]] + msg.retransmitTime in ord/prevs[t] + msg.key in m.ownedKeys[t] } + +pred CanReceive[m : Member, t : Tick, msg : Message] { + some msg' : DataMessage { + msg'.sentTime = msg.sentTime + msg'.sender = msg.sender + msg' in m.receivedMessages[ord/prev[t]] || ReceiveConditions[m, t, msg'] }} + +pred IsMember[m : Member, t : Tick] { + some kds : KDS | m in kds.members[t] +} + +------------------------------------------- +pred RemulticastConditions[g : GSA, msg : DataMessage, t : Tick] { + msg.retransmitTime in ord/prevs[t] + msg.key in g.keys[t] + g.parent.keys[t] + some g.parent + g - msg.gsaID } + +pred Remulticast[g : GSA, msg : DataMessage, t : Tick, msg': lone DataMessage] { + RemulticastConditions[g, msg, t] + let g' = g.parent + g - msg.gsaID | NewestKey[g'.keys[msg.sentTime], msg'.key] + msg'.sender = msg.sender + msg'.sentTime = msg.sentTime + msg'.retransmitTime = t + msg'.gsaID = g +} + +pred KeyUpdate[g : GSA, t : Tick] { + some k : Key { + GeneratedKey[g, t, k] + all c : Client | c in g.members[t] <=> k in c.ownedKeys[t] + k in g.keys[t] }} + +pred NewestKey[keys : set GroupKey, newest: lone GroupKey] { + some keys <=> some newest + newest in keys + no ord/nexts[newest.generatedTime] & keys.generatedTime } + +pred GeneratedKey[g : GSA, t : Tick, key : GroupKey] { + key.generator = g + key.generatedTime = t +} + +pred ConstantMembership[c : Client, t : Tick] { + IsMember[c, t] <=> IsMember[c, ord/prev[t]] } + + +pred NoChange[r : Tick -> univ, t : Tick] { + r[ord/prev[t]] = r[t] +} + +-------------------------------------------- +assert Acyclic { + all g : GSA | g !in g.^parent } + +//check Acyclic for 6 -- one min + +assert Connected { + all g, g' : GSA | g in g'.*(parent + ~parent) } + +//check Connected for 6 + +assert TimeProceeds { + no msg : DataMessage | msg.retransmitTime in ord/prevs[msg.sentTime] } + +//check TimeProceeds for 6 + +pred LoopFree { + no t, t' : Tick { + t!=t' + all k : KDS | k.members[t] = k.members[t'] -- no constraint on keys + all m : Member | m.receivedMessages[t] = m.receivedMessages[t'] + all m : DataMessage | m.retransmitTime = t => + (some m' : DataMessage { + m'.retransmitTime = t' + m.sender = m'.sender + m.sentTime = m'.sentTime + m.gsaID = m'.gsaID + m.key = m'.key }) + }} + +//fact NoLoop { LoopFree() } -- Property-specific diameter + +------------------------------------------------ +assert loop { + !LoopFree } +//check loop for 13 but 2 Member, 1 KDS, 1 Message + +assert NonLinearTopology { + (no g : GSA | #g.~parent > 1) || + !(some m, m' : DataMessage | Remulticast[m.gsaID, m', m.retransmitTime, m] + && !SendMessage[m.sender, m.sentTime, m] + && (some c : Client | m in c.receivedMessages[ord/nexts[m.retransmitTime]]))} + +//check NonLinearTopology for 5 but 3 KDS, 3 Member, 2 Message -- > good scenario + +assert NotOutside { + no msg : DataMessage | !IsMember[msg.sender, msg.sentTime] } + +//check NotOutside for 5 + +assert Trivial { + 0 = 1 } + +//check Trivial for 2 but 1 KDS + +assert x { + !(LoopFree && some DataMessage && + (some t : Tick | some m, m' : Member | + m!=m' && IsMember[m, t] && IsMember[m', t] && t != ord/next[ord/first])) +} + +//check x for 3 but 2 Member, 2 KDS, 2 Message + ------------------------------------------- +assert OutsiderCantRead { + no msg : Message, m : Member, t : Tick { + IsMember[msg.sender, msg.sentTime] + !IsMember[m, msg.sentTime] + CanReceive[m, t, msg] + } +} +assert OutsiderCantSend { + no msg : Message, m : Member, t : Tick { + !IsMember[msg.sender, msg.sentTime] + IsMember[m, t] + msg !in m.receivedMessages[ord/prev[t]] + CanReceive[m, t, msg] + } +} +assert InsiderCanRead { + all msg : Message, m : Member | + some t : Tick - ord/last | all t' : ord/nexts[t] | + (IsMember[msg.sender, msg.sentTime] && + IsMember[m, msg.sentTime]) => CanReceive[m, t', msg] +} + +check OutsiderCantRead for 5 but 3 Member expect 0 +//check OutsiderCantSend for 5 but 3 Member -- 5 min +//check InsiderCanRead for 9 but 2 Member, 1 KDS, 1 Message +//check InsiderCanRead for 10 but 2 Member, 2 KDS, 2 Message -- not able to check diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/case_studies/sync.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/case_studies/sync.als new file mode 100644 index 00000000..b79c5b5d --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/case_studies/sync.als @@ -0,0 +1,134 @@ +module examples/case_studies/sync + +/* + * Model of a generic file synchronizer. + * + * Adapted from: + * Reference: S. Balasubramaniam and Benjamin C. Pierce, + * "What is a File Synchronizer", Indiana University CSCI + * Technical Report #507, April 22, 1998 + * + * For a detailed description, see: + * http://sdg.lcs.mit.edu/pubs/theses/tnolte_masters.pdf + * + * author: Tina Nolte + */ + +private open util/graph[Name] as graph + +/** + * The Name atom represents the hierarchy of all name sequences + * used in the model. A Name atom represents the name, and the path + * in the sequence of names to the root excluding the RootName. + */ +sig Name { + children: set Name +} + +fact { graph/tree[children] } + +one sig RootName extends Name { } + +fact { Name in RootName.*children } + +// We assume that the empty path always + +sig FileContents { } +one sig Dir extends FileContents { } + +pred IsValidFS[fs: Name -> lone FileContents] { + all n: Name - RootName | { + // files don't have children + n.fs != Dir => no (n.^children).fs + // if a path maps to something non-nil, all prefixes do also + some n.fs => some (n.~children).fs + } + // the root of a file system must be a directory + RootName.fs = Dir +} + +pred IsValidDirty[dirty: set Name] { + all n: dirty | n.~children in dirty + RootName in dirty => some dirty - RootName +} + +pred DirtiesValid[A, B: Name -> lone FileContents, Adirty, Bdirty: set Name] { + some O: Name -> lone FileContents | { + IsValidFS[O] + { n: Name | n.O != n.A } in Adirty + { n: Name | n.O != n.B } in Bdirty + } +} + +fun RestrictFS[fs: Name -> lone FileContents, p: Name]: Name -> lone FileContents { + fs & (p.*children -> FileContents) +} + +fun RestrictFSComplement[fs: Name -> lone FileContents, p: Name]: Name -> lone FileContents { + fs & ((Name - p.*children) -> FileContents) +} + +/** + * The following function test whether a particular synchronizer + * operation satisfies the "reasonableness" conditions. + * The arguments are: + * O - the original filesystem. + * A,B - separately modified copies + * Adirty, Bdirty - sets of paths modified in A and B, respectively, from O. + * + * A',B' - results of synchronizer operation + */ +pred SyncSpec[A, B, A', B': Name -> lone FileContents, Adirty, Bdirty: set Name] { + { + IsValidFS[A] + IsValidFS[B] + IsValidDirty[Adirty] + IsValidDirty[Bdirty] + } => { + { + all p: Name | IsRelevantPath[p, A, B] => { + SyncSpecForPath[p, A, B, A', B', Adirty, Bdirty] + } + } + IsValidFS[A'] + IsValidFS[B'] + } +} + +pred SyncSpecForPath[p: Name, A, B, A', B': Name -> lone FileContents, Adirty, Bdirty: set Name] { + (p.A = p.B => (p.A' = p.A && p.B' = p.B)) + (p !in Adirty => (RestrictFS[A', p] = RestrictFS[B, p] && RestrictFS[B', p] = RestrictFS[B, p])) + (p !in Bdirty => (RestrictFS[B', p] = RestrictFS[A, p] && RestrictFS[A', p] = RestrictFS[A, p])) + ((p in Adirty && p in Bdirty && p.A != p.B) => (RestrictFS[A',p] = RestrictFS[A,p] && RestrictFS[B',p] = RestrictFS[B,p])) +} + +pred IsRelevantPath[p: Name, A, B: Name -> lone FileContents] { + p = RootName || { + (p.~children).A = Dir + (p.~children).B = Dir + } +} + +pred SyncSpecExample[A, B, A', B': Name -> lone FileContents, Adirty, Bdirty: set Name] { + IsValidFS[A] + IsValidFS[B] + IsValidDirty[Adirty] + IsValidDirty[Bdirty] + SyncSpec[A, B, A', B', Adirty, Bdirty] + A != A' +} + +//run SyncSpecExample for 3 + +pred SyncSpecNotUnique { + some A, B, A1', B1', A2', B2': Name -> lone FileContents, Adirty, Bdirty: set Name | { + IsValidFS[A] && IsValidFS[B] + IsValidDirty[Adirty] && IsValidDirty[Bdirty] + //DirtiesValid(A, B, Adirty, Bdirty) + (A1' != A2' || B1' != B2') + SyncSpec[A, B, A1', B1', Adirty, Bdirty] + SyncSpec[A, B, A2', B2', Adirty, Bdirty] + } +} + +run SyncSpecNotUnique for 5 expect 0 diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/case_studies/syncimpl.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/case_studies/syncimpl.als new file mode 100644 index 00000000..fe20377e --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/case_studies/syncimpl.als @@ -0,0 +1,107 @@ +module examples/case_studies/syncimpl + +/* + * Model of a file synchronizer reconciliation algorithm. + * + * Adapted from: + * Reference: S. Balasubramaniam and Benjamin C. Pierce, + * "What is a File Synchronizer", Indiana University CSCI + * Technical Report #507, April 22, 1998 + * + * For a detailed description, see: + * http://sdg.lcs.mit.edu/pubs/theses/tnolte_masters.pdf + * + * author: Tina Nolte + */ + +open examples/case_studies/sync as sync +open util/ordering[sync/Name] as ord +open util/relation as rel + +// Model the reconciliation algorithm + +sig ReconName extends Name { + Ain, Bin, Aout, Bout: Name->FileContents, + p_children: set Name, + first_p_child, last_p_child: lone Name, + prev_p_child: (p_children - first_p_child) -> p_children +} + +fact { + all x: ReconName { + x.p_children = ChildrenAB[x.Ain, x.Bin, x] + x.first_p_child = { pc: x.p_children | x.p_children in (pc + nexts[pc]) } + x.last_p_child = { pc: x.p_children | x.p_children in (pc + prevs[pc]) } + all p_child: x.p_children - x.first_p_child | { + let earlierChildren = prevs[p_child] & x.p_children | + p_child . (x.prev_p_child) = { earlierChild: earlierChildren | earlierChildren in (earlierChild + @prevs[earlierChild]) } + } + } +} + +fact { ReconName = Name } + +fun ChildrenAB[A, B: Name -> lone FileContents, p: Name]: set Name { + p.children & (dom[A] + dom[B]) +} + +pred reconHelper[Adirty, Bdirty: set Name] { + all p: Name { + let A = p.Ain, B = p.Bin, A' = p.Aout, B' = p.Bout | { + some p.(A+B) => { + (p !in Adirty && p !in Bdirty) => (A' = A && B' = B) else { + (p.A = Dir && p.B = Dir) => { + no p_children => { + p.Aout = p.Ain + p.Bout = p.Bin + } else { + p.first_p_child.Ain = p.Ain + p.first_p_child.Bin = p.Bin + p.Aout = p.last_p_child.Aout + p.Bout = p.last_p_child.Bout + all pchild: p.p_children - p.first_p_child | { + pchild.Ain = (pchild.(p.prev_p_child)).Aout + pchild.Bin = (pchild.(p.prev_p_child)).Bout + } + } // some p_children + } else { // !(p.A = Dir && p.B = Dir) + p !in Adirty => { + A' = RestrictFS[B, p] + RestrictFSComplement[A, p] + B' = B + } else { + p !in Bdirty => { + A' = A + B' = RestrictFS[A, p] + RestrictFSComplement[B, p] + } else { + A' = A + B' = B + } + } // not "p !in Adirty" + } // not case 2 i.e. not both are dirs + } // not both clean + } // some p.(A+B) + } // let A =, B=, A'=, B'= + } // all p: Name +} // reconHelper() + +pred recon[A, B, A', B': Name -> lone FileContents, Adirty, Bdirty: set Name] { + A = ReconName.Ain + B = ReconName.Bin + A' = ReconName.Aout + B' = ReconName.Bout + reconHelper[Adirty, Bdirty] +} + +assert Correctness { + all A, B, A', B': Name -> lone FileContents, Adirty, Bdirty: set Name | { + { + DirtiesValid[A, B, Adirty, Bdirty] + recon[A, B, A', B', Adirty, Bdirty] + //no Adirty + Bdirty + } + => + SyncSpec[A, B, A', B', Adirty, Bdirty] + } +} + +check Correctness for 4 but 2 FileContents expect 0 diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/puzzles/farmer.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/puzzles/farmer.als new file mode 100644 index 00000000..d3636486 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/puzzles/farmer.als @@ -0,0 +1,92 @@ +module examples/puzzles/farmer + +/* + * The classic river crossing puzzle. A farmer is carrying a fox, a + * chicken, and a sack of grain. He must cross a river using a boat + * that can only hold the farmer and at most one other thing. If the + * farmer leaves the fox alone with the chicken, the fox will eat the + * chicken; and if he leaves the chicken alone with the grain, the + * chicken will eat the grain. How can the farmer bring everything + * to the far side of the river intact? + * + * authors: Greg Dennis, Rob Seater + * + * Acknowledgements to Derek Rayside and his students for finding and + * fixing a bug in the "crossRiver" predicate. + */ + +open util/ordering[State] as ord + +/** + * The farmer and all his possessions will be represented as Objects. + * Some objects eat other objects when the Farmer's not around. + */ +abstract sig Object { eats: set Object } +one sig Farmer, Fox, Chicken, Grain extends Object {} + +/** + * Define what eats what when the Farmer' not around. + * Fox eats the chicken and the chicken eats the grain. + */ +fact eating { eats = Fox->Chicken + Chicken->Grain } + +/** + * The near and far relations contain the objects held on each + * side of the river in a given state, respectively. + */ +sig State { + near: set Object, + far: set Object +} + +/** + * In the initial state, all objects are on the near side. + */ +fact initialState { + let s0 = ord/first | + s0.near = Object && no s0.far +} + +/** + * Constrains at most one item to move from 'from' to 'to'. + * Also constrains which objects get eaten. + */ +pred crossRiver [from, from', to, to': set Object] { + // either the Farmer takes no items + (from' = from - Farmer - from'.eats and + to' = to + Farmer) or + // or the Farmer takes one item + (one x : from - Farmer | { + from' = from - Farmer - x - from'.eats + to' = to + Farmer + x }) +} + +/** + * crossRiver transitions between states + */ +fact stateTransition { + all s: State, s': ord/next[s] { + Farmer in s.near => + crossRiver[s.near, s'.near, s.far, s'.far] else + crossRiver[s.far, s'.far, s.near, s'.near] + } +} + +/** + * the farmer moves everything to the far side of the river. + */ +pred solvePuzzle { + ord/last.far = Object +} + +run solvePuzzle for 8 State expect 1 + +/** + * no Object can be in two places at once + * this is implied by both definitions of crossRiver + */ +assert NoQuantumObjects { + no s : State | some x : Object | x in s.near and x in s.far +} + +check NoQuantumObjects for 8 State expect 0 \ No newline at end of file diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/puzzles/farmer.thm b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/puzzles/farmer.thm new file mode 100644 index 00000000..41d7ada7 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/puzzles/farmer.thm @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/puzzles/handshake.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/puzzles/handshake.als new file mode 100644 index 00000000..8fce6178 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/puzzles/handshake.als @@ -0,0 +1,64 @@ +module examples/puzzles/handshake + +/* + * Alloy model of the Halmos handshake problem + * + * Hilary and Jocelyn are married. They invite four couples who are friends for dinner. When + * they arrive, they shake hands with each other. Nobody shakes hands with him or herself + * or with his or her spouse. After there has been some handshaking, Jocelyn jumps up on + * a chair and says "Stop shaking hands!", and then asks how many hands each person has + * shaken. All the answers are different. How many hands has Hilary shaken? + * + * The Alloy model represents the problem as a set of constraints. Properties of the spouse + * relationship and of handshaking in general are given as facts. The particular situation + * is cast as a function. + * + * There are 9 people answering, and all answers are different. Nobody can shake more than + * 8 hands. So answers must be 0..8. The one (p8 say) who answered 8 has shaken everybody's + * hand except for his or her own, and his or her spouse's. Now consider the person who shook + * 0 hands (p0 say). The persons p0 and p8 are distinct. If they are not married, then p8 cannot + * have shaken 8 hands, because he or she did not shake the hand of p0 or of his or her spouse. + * So p8's spouse to p0. Now imagine Jocelyn asking the question again, with p0 and p8 out of + * the room, and excluding hand shakes with them. Since p8 shook hands with everyone else + * except p0 and p8, everyone gives an answer one smaller than they did before, giving 0..6. + * The argument now applies recursively. So Hilary is left alone, having shaken 4 hands. + * + * author: Daniel Jackson, 11/15/01 + */ + +sig Person {spouse: Person, shaken: set Person} +one sig Jocelyn, Hilary extends Person {} + +fact ShakingProtocol { + // nobody shakes own or spouse's hand + all p: Person | no (p + p.spouse) & p.shaken + // if p shakes q, q shakes p + all p, q: Person | p in q.shaken => q in p.shaken + } + +fact Spouses { + all p, q: Person | p!=q => { + // if q is p's spouse, p is q's spouse + p.spouse = q => q.spouse = p + // no spouse sharing + p.spouse != q.spouse + } + all p: Person { + // a person is his or her spouse's spouse + p.spouse.spouse = p + // nobody is his or her own spouse + p != p.spouse + } + } + +pred Puzzle { + // everyone but Jocelyn has shaken a different number of hands + all p,q: Person - Jocelyn | p!=q => #p.shaken != #q.shaken + // Hilary's spouse is Jocelyn + Hilary.spouse = Jocelyn + } + +P10: run Puzzle for exactly 10 Person, 5 int expect 1 +P12: run Puzzle for exactly 12 Person, 5 int expect 1 +P14: run Puzzle for exactly 14 Person, 5 int expect 1 +P16: run Puzzle for exactly 16 Person, 6 int expect 1 diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/puzzles/handshake.thm b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/puzzles/handshake.thm new file mode 100644 index 00000000..0bc070b1 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/puzzles/handshake.thm @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/puzzles/hanoi.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/puzzles/hanoi.als new file mode 100644 index 00000000..f936fa5f --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/puzzles/hanoi.als @@ -0,0 +1,140 @@ +module examples/puzzles/hanoi + +/* + * Towers of Hanoi model + * + * Description of problem from http://www.cut-the-knot.com/recurrence/hanoi.html + * + * The Tower of Hanoi puzzle was invented by the French mathematician Edouard Lucas + * in 1883. We are given a tower of eight disks, initially stacked in decreasing size on + * one of three pegs. The objective is to transfer the entire tower to one of the other + * pegs, moving only one disk at a time and never a larger one onto a smaller. + * + * The Alloy model below is written so that a solution to the model is a complete + * sequence of valid moves solving an instance of the problem. We define constraints + * for the initial state (all discs on left stake), the final state (all discs on right stake), + * and each pair of adjacent states (the top disc is moved from one stake to another, + * not putting larger discs on smaller discs), and let Alloy Analyzer solve for the + * sequence of states satisfying these constraints. Since each adjacent pair of states is + * constrained to be related by a single move, it is easy to see the sequence of moves + * once you have the sequence of states. + * + * For n discs, 2^n states are needed for a solution + * (including the initial state and the final state). + * + * Performance: currently, the problem can be solved for up to 5 discs; this takes + * several minutes with the Chaff solver. + * + * author: Ilya Shlyakhter + */ + +open util/ordering[State] as states +open util/ordering[Stake] as stakes +open util/ordering[Disc] as discs + +sig Stake { } + +sig Disc { } + +/** + * sig State: the complete state of the system -- + * which disc is on which stake. An solution is a + * sequence of states. + */ +sig State { + on: Disc -> one Stake // _each_ disc is on _exactly one_ stake + // note that we simply record the set of discs on each stake -- + // the implicit assumption is that on each stake the discs + // on that stake are ordered by size with smallest disc on top + // and largest on bottom, as the problem requires. +} + +/** + * compute the set of discs on the given stake in this state. + * ~(this.on) map the stake to the set of discs on that stake. + */ +fun discsOnStake[st: State, stake: Stake]: set Disc { + stake.~(st.on) +} + +/** + * compute the top disc on the given stake, or the empty set + * if the stake is empty + */ +fun topDisc[st: State, stake: Stake]: lone Disc { + { d: st.discsOnStake[stake] | st.discsOnStake[stake] in discs/nexts[d] + d } +} + +/** + * Describes the operation of moving the top disc from stake fromStake + * to stake toStake. This function is defined implicitly but is + * nevertheless deterministic, i.e. the result state is completely + * determined by the initial state and fromStake and toStake; hence + * the "det" modifier above. (It's important to use the "det" modifier + * to tell the Alloy Analyzer that the function is in fact deterministic.) + */ +pred Move [st: State, fromStake, toStake: Stake, s': State] { + let d = st.topDisc[fromStake] | { + // all discs on toStake must be larger than d, + // so that we can put d on top of them + st.discsOnStake[toStake] in discs/nexts[d] + // after, the fromStake has the discs it had before, minus d + s'.discsOnStake[fromStake] = st.discsOnStake[fromStake] - d + // after, the toStake has the discs it had before, plus d + s'.discsOnStake[toStake] = st.discsOnStake[toStake] + d + // the remaining stake afterwards has exactly the discs it had before + let otherStake = Stake - fromStake - toStake | + s'.discsOnStake[otherStake] = st.discsOnStake[otherStake] + } +} + +/** + * there is a leftStake that has all the discs at the beginning, + * and a rightStake that has all the discs at the end + */ +pred Game1 { + Disc in states/first.discsOnStake[stakes/first] + some finalState: State | Disc in finalState.discsOnStake[stakes/last] + + // each adjacent pair of states are related by a valid move of one disc + all preState: State - states/last | + let postState = states/next[preState] | + some fromStake: Stake | { + // must have at least one disk on fromStake to be able to move + // a disc from fromStake to toStake + some preState.discsOnStake[fromStake] + // post- results from pre- by making one disc move + some toStake: Stake | preState.Move[fromStake, toStake, postState] + } +} + +/** + * there is a leftStake that has all the discs at the beginning, + * and a rightStake that has all the discs at the end + */ +pred Game2 { + Disc in states/first.discsOnStake[stakes/first] + some finalState: State | Disc in finalState.discsOnStake[stakes/last] + + // each adjacent pair of states are related by a valid move of one disc + all preState: State - states/last | + let postState = states/next[preState] | + some fromStake: Stake | + let d = preState.topDisc[fromStake] | { + // must have at least one disk on fromStake to be able to move + // a disc from fromStake to toStake + some preState.discsOnStake[fromStake] + postState.discsOnStake[fromStake] = preState.discsOnStake[fromStake] - d + some toStake: Stake | { + // post- results from pre- by making one disc move + preState.discsOnStake[toStake] in discs/nexts[d] + postState.discsOnStake[toStake] = preState.discsOnStake[toStake] + d + // the remaining stake afterwards has exactly the discs it had before + let otherStake = Stake - fromStake - toStake | + postState.discsOnStake[otherStake] = preState.discsOnStake[otherStake] + } + } + } + +run Game1 for 1 but 3 Stake, 5 Disc, 32 State expect 1 +run Game2 for 1 but 3 Stake, 3 Disc, 8 State expect 1 diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/puzzles/hanoi.thm b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/puzzles/hanoi.thm new file mode 100644 index 00000000..f56b5722 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/puzzles/hanoi.thm @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/systems/file_system.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/systems/file_system.als new file mode 100644 index 00000000..60fd959b --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/systems/file_system.als @@ -0,0 +1,59 @@ +module examples/systems/file_system + +/* + * Model of a generic file system. + */ + +abstract sig Object {} + +sig Name {} + +sig File extends Object {} { some d: Dir | this in d.entries.contents } + +sig Dir extends Object { + entries: set DirEntry, + parent: lone Dir +} { + parent = this.~@contents.~@entries + all e1, e2 : entries | e1.name = e2.name => e1 = e2 + this !in this.^@parent + this != Root => Root in this.^@parent +} + +one sig Root extends Dir {} { no parent } + +lone sig Cur extends Dir {} + +sig DirEntry { + name: Name, + contents: Object +} { + one this.~entries +} + + +/** + * all directories besides root have one parent + */ +pred OneParent_buggyVersion { + all d: Dir - Root | one d.parent +} + +/** + * all directories besides root have one parent + */ +pred OneParent_correctVersion { + all d: Dir - Root | (one d.parent && one contents.d) +} + +/** + * Only files may be linked (that is, have more than one entry) + * That is, all directories are the contents of at most one directory entry + */ +pred NoDirAliases { + all o: Dir | lone o.~contents +} + +check { OneParent_buggyVersion => NoDirAliases } for 5 expect 1 + +check { OneParent_correctVersion => NoDirAliases } for 5 expect 0 diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/systems/file_system.thm b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/systems/file_system.thm new file mode 100644 index 00000000..4b740ad3 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/systems/file_system.thm @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/systems/javatypes_soundness.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/systems/javatypes_soundness.als new file mode 100644 index 00000000..6bf50ed7 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/systems/javatypes_soundness.als @@ -0,0 +1,140 @@ +module examples/systems/javatypes + +/* + * Model of the Java type system. The TypeSoundness assertion + * claims that if a Java program type checks successfully, + * then a field will cannot be assigned an incorrect type. + * + * author: Daniel Jackson + */ + +open util/graph[Type] as graph + +abstract sig Type { + xtends: set Type + } +sig Interface extends Type {} + { xtends in Interface } +sig Class extends Type { + implements: set Interface, + fields: set Field + } { lone xtends && xtends in Class } +-- optional: best omitted to allow private etc +-- {xtends.@fields in fields} +sig Field { + declType: Type + } + +fact { + graph/dag[xtends] + } + +abstract sig Value {} +one sig Null extends Value {} +sig Object extends Value { + type: Class, + slot: Field lone -> lone Slot + } {slot.Slot = type.fields} +sig Slot {} + +abstract sig Statement {} +sig Assignment extends Statement { + var: Variable, + expr: Expr + } +sig Setter extends Statement { + field: Field, + lexpr, rexpr: Expr + } + +abstract sig Expr { + type: Type, + subexprs: set Expr + } {subexprs = this + this.^@expr} +sig Variable extends Expr { + declType: Type + } {type = declType} +sig Constructor extends Expr { + class: Class + } +sig Getter extends Expr { + field: Field, + expr: Expr + } {type = field.declType} + +sig State { + objects: set Object, + reaches: Object -> Object, + vars: set Variable, + holds: (Slot + Variable) -> lone Value, + val: Expr -> lone Value + } { + all o: Object | o.reaches = holds[o.slot[Field]] & Object + holds.Value & Variable = vars + objects = holds[vars].^reaches + all e: Expr | let v = val[e] { + e in Variable => v = holds[e] + e in Getter => v = holds[(val[e.expr]).slot[e.field]] + e in Constructor => v in Object and v.type = e.type } + } + +pred RuntimeTypesOK [s: State] { + all o: s.objects, f: o.type.fields | + let v = s.holds [o.slot [f]] | HasType [v, f.declType] + all var: s.vars | + let v = s.holds [var] | HasType [v, var.declType] + } + +pred HasType [v: Value, t: Type] { + v in Null or Subtype [v.type, t] + } + +pred Subtype [t, t': Type] { + t in Class => + (let supers = (t & Class).*(Class<:xtends) | + t' in (supers + supers.implements.*(Interface<:xtends))) + t in Interface => t' in (t & Interface).*(Interface<:xtends) + } + +pred TypeChecksSetter [stmt: Setter] { + all g: Getter & stmt.(lexpr+rexpr).subexprs | g.field in g.expr.type.fields + stmt.field in stmt.lexpr.type.fields + Subtype [stmt.rexpr.type, stmt.field.declType] + } + +pred ExecuteSetter [s, s': State, stmt: Setter] { + stmt.(rexpr+lexpr).subexprs & Variable in s.vars + s'.objects = s.objects and s'.vars = s.vars + let rval = s.val [stmt.rexpr], lval = s.val [stmt.lexpr] { + no lval & Null + s'.holds = s.holds ++ (lval.slot[stmt.field] -> rval) + } + } + +assert TypeSoundness { + all s, s': State, stmt: Setter | + {RuntimeTypesOK[s] + ExecuteSetter [s, s', stmt] + TypeChecksSetter[stmt] + } => RuntimeTypesOK[s'] + } + +fact {all o, o': Object | some o.slot[Field] & o'.slot[Field] => o = o'} +fact {all g: Getter | no g & g.^subexprs} + +fact ScopeFact { + #Assignment =< 1 + #Class =< 5 + #Interface =< 5 +} + +check TypeSoundness for 3 expect 0 + +check TypeSoundness for 2 State, 1 Assignment, +1 Statement, 5 Interface, 5 Class, 1 Null, +7 Object, 12 Expr, 3 Field, 3 Slot expect 0 + +// very slow +// check TypeSoundness for 2 State, 1 Statement, +// 10 Type, 8 Value, 12 Expr, +// 3 Field, 3 Slot expect 0 diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/systems/lists.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/systems/lists.als new file mode 100644 index 00000000..aabd5f63 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/systems/lists.als @@ -0,0 +1,48 @@ +/* + * a simple list module + * which demonstrates how to create predicates and fields that mirror each other + * thus allowing recursive constraints (even though recursive predicates are not + * currently supported by Alloy) + * author: Robert Seater + */ + +module list + +sig Thing {} +fact NoStrayThings {Thing in List.car} + +abstract sig List { + equivTo: set List, + prefixes: set List + } +sig NonEmptyList extends List { + car: one Thing, + cdr: one List + } +sig EmptyList extends List {} + +pred isFinite [L:List] {some e: EmptyList | e in L.*cdr} +fact finite {all L: List | isFinite[L]} + +fact Equivalence { + all a,b: List | (a in b.equivTo) <=> ((a.car = b.car and b.cdr in a.cdr.equivTo) and (#a.*cdr = #b.*cdr)) + } +assert reflexive {all L: List | L in L.equivTo} +check reflexive for 6 expect 0 +assert symmetric {all a,b: List | a in b.equivTo <=> b in a.equivTo} +check symmetric for 6 expect 0 +assert empties {all a,b: EmptyList | a in b.equivTo} +check empties for 6 expect 0 + +fact prefix { //a is a prefix of b + all e: EmptyList, L:List | e in L.prefixes + all a,b: NonEmptyList | (a in b.prefixes) <=> (a.car = b.car + and a.cdr in b.cdr.prefixes + and #a.*cdr < #b.*cdr) +} + +pred show { + some a, b: NonEmptyList | a!=b && b in a.prefixes + } +run show for 4 expect 1 + diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/systems/lists.thm b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/systems/lists.thm new file mode 100644 index 00000000..6da43857 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/systems/lists.thm @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/systems/marksweepgc.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/systems/marksweepgc.als new file mode 100644 index 00000000..b8081e3f --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/systems/marksweepgc.als @@ -0,0 +1,83 @@ +module examples/systems/marksweepgc + +/* + * Model of mark and sweep garbage collection. + */ + +// a node in the heap +sig Node {} + +sig HeapState { + left, right : Node -> lone Node, + marked : set Node, + freeList : lone Node +} + +pred clearMarks[hs, hs' : HeapState] { + // clear marked set + no hs'.marked + // left and right fields are unchanged + hs'.left = hs.left + hs'.right = hs.right +} + +/** + * simulate the recursion of the mark() function using transitive closure + */ +fun reachable[hs: HeapState, n: Node] : set Node { + n + n.^(hs.left + hs.right) +} + +pred mark[hs: HeapState, from : Node, hs': HeapState] { + hs'.marked = hs.reachable[from] + hs'.left = hs.left + hs'.right = hs.right +} + +/** + * complete hack to simulate behavior of code to set freeList + */ +pred setFreeList[hs, hs': HeapState] { + // especially hackish + hs'.freeList.*(hs'.left) in (Node - hs.marked) + all n: Node | + (n !in hs.marked) => { + no hs'.right[n] + hs'.left[n] in (hs'.freeList.*(hs'.left)) + n in hs'.freeList.*(hs'.left) + } else { + hs'.left[n] = hs.left[n] + hs'.right[n] = hs.right[n] + } + hs'.marked = hs.marked +} + +pred GC[hs: HeapState, root : Node, hs': HeapState] { + some hs1, hs2: HeapState | + hs.clearMarks[hs1] && hs1.mark[root, hs2] && hs2.setFreeList[hs'] +} + +assert Soundness1 { + all h, h' : HeapState, root : Node | + h.GC[root, h'] => + (all live : h.reachable[root] | { + h'.left[live] = h.left[live] + h'.right[live] = h.right[live] + }) +} + +assert Soundness2 { + all h, h' : HeapState, root : Node | + h.GC[root, h'] => + no h'.reachable[root] & h'.reachable[h'.freeList] +} + +assert Completeness { + all h, h' : HeapState, root : Node | + h.GC[root, h'] => + (Node - h'.reachable[root]) in h'.reachable[h'.freeList] +} + +check Soundness1 for 3 expect 0 +check Soundness2 for 3 expect 0 +check Completeness for 3 expect 0 diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/systems/views.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/systems/views.als new file mode 100644 index 00000000..3a5ab82b --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/systems/views.als @@ -0,0 +1,217 @@ +module examples/systems/views + +/* + * Model of views in object-oriented programming. + * + * Two object references, called the view and the backing, + * are related by a view mechanism when changes to the + * backing are automatically propagated to the view. Note + * that the state of a view need not be a projection of the + * state of the backing; the keySet method of Map, for + * example, produces two view relationships, and for the + * one in which the map is modified by changes to the key + * set, the value of the new map cannot be determined from + * the key set. Note that in the iterator view mechanism, + * the iterator is by this definition the backing object, + * since changes are propagated from iterator to collection + * and not vice versa. Oddly, a reference may be a view of + * more than one backing: there can be two iterators on the + * same collection, eg. A reference cannot be a view under + * more than one view type. + * + * A reference is made dirty when it is a backing for a view + * with which it is no longer related by the view invariant. + * This usually happens when a view is modified, either + * directly or via another backing. For example, changing a + * collection directly when it has an iterator invalidates + * it, as does changing the collection through one iterator + * when there are others. + * + * More work is needed if we want to model more closely the + * failure of an iterator when its collection is invalidated. + * + * As a terminological convention, when there are two + * complementary view relationships, we will give them types + * t and t'. For example, KeySetView propagates from map to + * set, and KeySetView' propagates from set to map. + * + * author: Daniel Jackson + */ + +open util/ordering[State] as so +open util/relation as rel + +sig Ref {} +sig Object {} + +-- t->b->v in views when v is view of type t of backing b +-- dirty contains refs that have been invalidated +sig State { + refs: set Ref, + obj: refs -> one Object, + views: ViewType -> refs -> refs, + dirty: set refs +-- , anyviews: Ref -> Ref -- for visualization + } +-- {anyviews = ViewType.views} + +sig Map extends Object { + keys: set Ref, + map: keys -> one Ref + }{all s: State | keys + Ref.map in s.refs} +sig MapRef extends Ref {} +fact {State.obj[MapRef] in Map} + +sig Iterator extends Object { + left, done: set Ref, + lastRef: lone done + }{all s: State | done + left + lastRef in s.refs} +sig IteratorRef extends Ref {} +fact {State.obj[IteratorRef] in Iterator} + +sig Set extends Object { + elts: set Ref + }{all s: State | elts in s.refs} +sig SetRef extends Ref {} +fact {State.obj[SetRef] in Set} + +abstract sig ViewType {} +one sig KeySetView, KeySetView', IteratorView extends ViewType {} +fact ViewTypes { + State.views[KeySetView] in MapRef -> SetRef + State.views[KeySetView'] in SetRef -> MapRef + State.views[IteratorView] in IteratorRef -> SetRef + all s: State | s.views[KeySetView] = ~(s.views[KeySetView']) + } + +/** + * mods is refs modified directly or by view mechanism + * doesn't handle possibility of modifying an object and its view at once? + * should we limit frame conds to non-dirty refs? + */ +pred modifies [pre, post: State, rs: set Ref] { + let vr = pre.views[ViewType], mods = rs.*vr { + all r: pre.refs - mods | pre.obj[r] = post.obj[r] + all b: mods, v: pre.refs, t: ViewType | + b->v in pre.views[t] => viewFrame [t, pre.obj[v], post.obj[v], post.obj[b]] + post.dirty = pre.dirty + + {b: pre.refs | some v: Ref, t: ViewType | + b->v in pre.views[t] && !viewFrame [t, pre.obj[v], post.obj[v], post.obj[b]] + } + } + } + +pred allocates [pre, post: State, rs: set Ref] { + no rs & pre.refs + post.refs = pre.refs + rs + } + +/** + * models frame condition that limits change to view object from v to v' when backing object changes to b' + */ +pred viewFrame [t: ViewType, v, v', b': Object] { + t in KeySetView => v'.elts = dom [b'.map] + t in KeySetView' => b'.elts = dom [v'.map] + t in KeySetView' => (b'.elts) <: (v.map) = (b'.elts) <: (v'.map) + t in IteratorView => v'.elts = b'.left + b'.done + } + +pred MapRef.keySet [pre, post: State, setRefs: SetRef] { + post.obj[setRefs].elts = dom [pre.obj[this].map] + modifies [pre, post, none] + allocates [pre, post, setRefs] + post.views = pre.views + KeySetView->this->setRefs + KeySetView'->setRefs->this + } + +pred MapRef.put [pre, post: State, k, v: Ref] { + post.obj[this].map = pre.obj[this].map ++ k->v + modifies [pre, post, this] + allocates [pre, post, none] + post.views = pre.views + } + +pred SetRef.iterator [pre, post: State, iterRef: IteratorRef] { + let i = post.obj[iterRef] { + i.left = pre.obj[this].elts + no i.done + i.lastRef + } + modifies [pre,post,none] + allocates [pre, post, iterRef] + post.views = pre.views + IteratorView->iterRef->this + } + +pred IteratorRef.remove [pre, post: State] { + let i = pre.obj[this], i' = post.obj[this] { + i'.left = i.left + i'.done = i.done - i.lastRef + no i'.lastRef + } + modifies [pre,post,this] + allocates [pre, post, none] + pre.views = post.views + } + +pred IteratorRef.next [pre, post: State, ref: Ref] { + let i = pre.obj[this], i' = post.obj[this] { + ref in i.left + i'.left = i.left - ref + i'.done = i.done + ref + i'.lastRef = ref + } + modifies [pre, post, this] + allocates [pre, post, none] + pre.views = post.views + } + +pred IteratorRef.hasNext [s: State] { + some s.obj[this].left + } + +assert zippishOK { + all + ks, vs: SetRef, + m: MapRef, + ki, vi: IteratorRef, + k, v: Ref | + let s0=so/first, + s1=so/next[s0], + s2=so/next[s1], + s3=so/next[s2], + s4=so/next[s3], + s5=so/next[s4], + s6=so/next[s5], + s7=so/next[s6] | + ({ + precondition [s0, ks, vs, m] + no s0.dirty + ks.iterator [s0, s1, ki] + vs.iterator [s1, s2, vi] + ki.hasNext [s2] + vi.hasNext [s2] + ki.this/next [s2, s3, k] + vi.this/next [s3, s4, v] + m.put [s4, s5, k, v] + ki.remove [s5, s6] + vi.remove [s6, s7] + } => no State.dirty) + } + +pred precondition [pre: State, ks, vs, m: Ref] { + // all these conditions and other errors discovered in scope of 6 but 8,3 + // in initial state, must have view invariants hold + (all t: ViewType, b, v: pre.refs | + b->v in pre.views[t] => viewFrame [t, pre.obj[v], pre.obj[v], pre.obj[b]]) + // sets are not aliases +-- ks != vs + // sets are not views of map +-- no (ks+vs)->m & ViewType.pre.views + // no iterator currently on either set +-- no Ref->(ks+vs) & ViewType.pre.views + } + +check zippishOK for 6 but 8 State, 3 ViewType expect 1 + +/** + * experiment with controlling heap size + */ +fact {all s: State | #s.obj < 5} diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/toys/birthday.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/toys/birthday.als new file mode 100644 index 00000000..092097f0 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/toys/birthday.als @@ -0,0 +1,68 @@ +module examples/toys/birthday + +/* + * Birthday Book + * + * A classic Z example to explain the basic form of an Alloy model. For the original, + * see J.M. Spivey, The Z Notation, Second Edition, Prentice Hall, 1992. + * + * A birthday book has two fields: known, a set of names (of persons whose birthdays are known), + * and date, a function from known names to dates. The operation AddBirthday adds an association + * between a name and a date; it uses the relational override operator (++), so any existing + * mapping from the name to a date is replaced. DelBirthday removes the entry for a given name. + * FindBirthday obtains the date d for a name n. The argument d is declared to be optional (that is, + * a singleton or empty set), so if there is no entry for n, d will be empty. Remind gives the set + * of names whose birthdays fall on a particular day. + * + * The assertion AddWorks says that if you add an entry, then look it up, you get back what you + * just entered. DelIsUndo says that doing DelBirthday after AddBirthday undoes it, as if the add + * had never happened. The first of these assertions is valid; the second isn't. + * + * The function BusyDay shows a case in which Remind produces more than one card. + * + * author: Daniel Jackson, 11/14/01 + */ + +sig Name {} +sig Date {} +sig BirthdayBook {known: set Name, date: known -> one Date} + +pred AddBirthday [bb, bb': BirthdayBook, n: Name, d: Date] { + bb'.date = bb.date ++ (n->d) + } + +pred DelBirthday [bb, bb': BirthdayBook, n: Name] { + bb'.date = bb.date - (n->Date) + } + +pred FindBirthday [bb: BirthdayBook, n: Name, d: lone Date] { + d = bb.date[n] + } + +pred Remind [bb: BirthdayBook, today: Date, cards: set Name] { + cards = (bb.date).today + } + +pred InitBirthdayBook [bb: BirthdayBook] { + no bb.known + } + +assert AddWorks { + all bb, bb': BirthdayBook, n: Name, d: Date, d': lone Date | + AddBirthday [bb,bb',n,d] && FindBirthday [bb',n,d'] => d = d' + } + +assert DelIsUndo { + all bb1,bb2,bb3: BirthdayBook, n: Name, d: Date| + AddBirthday [bb1,bb2,n,d] && DelBirthday [bb2,bb3,n] + => bb1.date = bb3.date + } + +check AddWorks for 3 but 2 BirthdayBook expect 0 +check DelIsUndo for 3 but 2 BirthdayBook expect 1 + +pred BusyDay [bb: BirthdayBook, d: Date]{ + some cards: set Name | Remind [bb,d,cards] && !lone cards + } + +run BusyDay for 3 but 1 BirthdayBook expect 1 diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/toys/birthday.thm b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/toys/birthday.thm new file mode 100644 index 00000000..3c3b050b --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/toys/birthday.thm @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/toys/ceilingsAndFloors.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/toys/ceilingsAndFloors.als new file mode 100644 index 00000000..e02fe438 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/toys/ceilingsAndFloors.als @@ -0,0 +1,46 @@ +module models/examples/toys/CeilingsAndFloors + +/* + * In his 1973 song, Paul Simon said "One Man's Ceiling Is Another Man's Floor". + * Does it follow that "One Man's Floor Is Another Man's Ceiling"? + * + * To see why not, check the assertion BelowToo. + * + * Perhaps simply preventing man's own floor from being his ceiling is enough, + * as is done in the Geometry constraint. BelowToo' shows that there are still + * cases where Geometry holds but the implication does not, although now + * the smallest solution has 3 Men and 3 Platforms instead of just 2 of each. + * + * What if we instead prevent floors and ceilings from being shared, + * as is done in the NoSharing constraint? The assertion BelowToo'' + * has no counterexamples, demonstrating that the implication now + * holds for all small examples. + * + * model author: Daniel Jackson (11/2001) + * modified by Robert Seater (11/2004) + */ + +sig Platform {} +sig Man {ceiling, floor: Platform} + +fact PaulSimon {all m: Man | some n: Man | n.Above[m]} + +pred Above[m, n: Man] {m.floor = n.ceiling} + +assert BelowToo { all m: Man | some n: Man | m.Above[n] } + +check BelowToo for 2 expect 1 + +pred Geometry {no m: Man | m.floor = m.ceiling} + +assert BelowToo' { Geometry => (all m: Man | some n: Man | m.Above[n]) } +check BelowToo' for 2 expect 0 +check BelowToo' for 3 expect 1 + +pred NoSharing { + no m,n: Man | m!=n && (m.floor = n.floor || m.ceiling = n.ceiling) +} + +assert BelowToo'' { NoSharing => (all m: Man | some n: Man | m.Above[n]) } +check BelowToo'' for 6 expect 0 +check BelowToo'' for 10 expect 0 diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/toys/ceilingsAndFloors.thm b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/toys/ceilingsAndFloors.thm new file mode 100644 index 00000000..b58a5b0d --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/toys/ceilingsAndFloors.thm @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/toys/genealogy.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/toys/genealogy.als new file mode 100644 index 00000000..2a9e2134 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/toys/genealogy.als @@ -0,0 +1,74 @@ +module examples/toys/genealogy + +/* + * Toy model of genealogical relationships + * + * The purpose of this model is to introduce basic concepts in Alloy. + * The signature Person introduces a set of persons; this is paritioned into + * two subsets, Man and Woman. The subsignature Adam declares a set of men + * with one element -- that is, a scalar. Similarly, Eve declares a single + * woman. + * + * The Person signature declares two fields: a person has one or zero spouses + * and a set of parents. + * + * The facts should be self-explanatory. Note that the constraint that + * spouse is a symmetric relation (that is, p is a spouse of q if q is a spouse + * of p) is written by equating the field, viewed as a relation, to its + * transpose. Since signatures have their own namespaces, and the same field + * name can refer to different fields in different relations, it is necessary + * to indicate which signature the field belongs to. This is not necessary when + * dereferencing a field, because the appropriate field is automatically + * determined by the type of the referencing expression. + * + * The command has no solutions. Given only 5 persons, it's not possible + * to have a couple distinct from Adam and Eve without incest. To understand + * the model, try weakening the constraints by commenting lines out (just + * put two hyphens at the start of a line) and rerunning the command. + * + * author: Daniel Jackson, 11/13/01 + */ + +abstract sig Person {spouse: lone Person, parents: set Person} +sig Man, Woman extends Person {} +one sig Eve extends Woman {} +one sig Adam extends Man {} + +fact Biology { + -- nobody is his or her own ancestor + no p: Person | p in p.^parents + } + +fact Bible { + -- every person except Adam and Eve has a mother and father + all p: Person - (Adam + Eve) | one mother: Woman, father: Man | + p.parents = mother + father + -- Adam and Eve have no parents + no (Adam + Eve).parents + -- Adam's spouse is Eve + Adam.spouse = Eve + } + +fact SocialNorms { + -- nobody is his or her own spouse + no p: Person | p.spouse = p + -- spouse is symmetric + spouse = ~spouse + -- a man's spouse is a woman and vice versa + Man.spouse in Woman && Woman.spouse in Man + } + +fact NoIncest { + -- can't marry a sibling + no p: Person | some p.spouse.parents & p.parents + -- can't marry a parent + no p: Person | some p.spouse & p.parents + } + +pred Show { + some p: Person - (Adam + Eve) | some p.spouse + } +run Show for 6 expect 1 + + + diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/toys/genealogy.thm b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/toys/genealogy.thm new file mode 100644 index 00000000..fbeb5cad --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/toys/genealogy.thm @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/toys/grandpa.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/toys/grandpa.als new file mode 100644 index 00000000..099cd0d5 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/toys/grandpa.als @@ -0,0 +1,88 @@ +module grandpa + +/* + * An Alloy model of the song "I Am My Own Grandpa" + * by Dwight B. Latham and Moe Jaffe + * + * The challenge is to produce a man who is his own grandfather + * without resorting to incest or time travel. Executing the predicate + * "ownGrandpa" will demonstrate how such a thing can occur. + * + * The full song lyrics, which describe an isomorophic solution, + * are included at the end of this file. + * + * model author: Daniel Jackson + */ + +abstract sig Person { + father: lone Man, + mother: lone Woman + } + +sig Man extends Person { wife: lone Woman } + +sig Woman extends Person { husband: lone Man } + +fact Biology { no p: Person | p in p.^(mother+father) } + +fact Terminology { wife = ~husband } + +fact SocialConvention { + no wife & *(mother+father).mother + no husband & *(mother+father).father + } + +fun grandpas [p: Person]: set Person { + let parent = mother + father + father.wife + mother.husband | + p.parent.parent & Man + } + +pred ownGrandpa [m: Man] { m in grandpas[m] } + +run ownGrandpa for 4 Person expect 1 + +/* defined variables: + * + * spouse = husband+wife + */ + +/* +I Am My Own Grandpa +by Dwight B. Latham and Moe Jaffe + +Many many years ago, when I was twenty-three, +I was married to a widow as pretty as can be, +This widow had a grown-up daughter who had hair of red, +My father fell in love with her and soon the two were wed. + + I'm my own grandpa, I'm my own grandpa. + It sounds funny, I know, but it really is so + I'm my own grandpa. + +This made my dad my son-in-law and changed my very life, +For my daughter was my mother, for she was my father's wife. +To complicate the matter, even though it brought me joy, +I soon became the father of a bouncing baby boy. + +My little baby thus became a brother-in-law to dad, +And so became my uncle, though it made me very sad, +For if he was my uncle then that also made him brother +To the widow's grown-up daughter, who of course was my step-mother. + +Father's wife then had a son who kept them on the run. +And he became my grandchild for he was my daughter's son. +My wife is now my mother's mother and it makes me blue, +Because although she is my wife, she's my grandmother, too. + +Oh, if my wife's my grandmother then I am her grandchild. +And every time I think of it, it nearly drives me wild. +For now I have become the strangest case you ever saw +As the husband of my grandmother, I am my own grandpa. + + I'm my own grandpa, I'm my own grandpa. + It sounds funny, I know, but it really is so + I'm my own grandpa. + I'm my own grandpa, I'm my own grandpa. + It sounds funny, I know, but it really is so + I'm my own grandpa. +*/ diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/toys/grandpa.thm b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/toys/grandpa.thm new file mode 100644 index 00000000..781b6c9f --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/toys/grandpa.thm @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/toys/javatypes.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/toys/javatypes.als new file mode 100644 index 00000000..2e60d64d --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/toys/javatypes.als @@ -0,0 +1,46 @@ +module examples/toys/javatypes + +/* + * A simple model of typing in Java. + * + * This model describes the basic notions of typing in Java. + * It ignores primitive types and null references. Each type has + * some set of subtypes. Types are partitioned into class and + * interface types. Object is a particular class. + * + * The fact TypeHierarchy says that every type is a direct or + * indirect subtype of Object; that no type is a direct or indirect + * of itself; and every type is a subtype of at most one class. + * + * An object instance has a type (its creation type) that is a class. + * A variable may hold an instance, and has a declared type. The + * fact TypeSoundness says that all instances held by a variable + * have types that are direct or indirect subtypes of the variable's + * declared type. + * + * The function Show specifies a case in which there is a class + * distinct from Object; there is some interface; and some variable + * has a declared type that is an interface. + * + * author: Daniel Jackson, 11/13/01 + */ + +abstract sig Type {subtypes: set Type} +sig Class, Interface extends Type {} +one sig Object extends Class {} +fact TypeHierarchy { + Type in Object.*subtypes + no t: Type | t in t.^subtypes + all t: Type | lone t.~subtypes & Class + } +sig Instance {type: Class} +sig Variable {holds: lone Instance, type: Type} +fact TypeSoundness { + all v: Variable | v.holds.type in v.type.*subtypes + } +pred Show { + some Class - Object + some Interface + some Variable.type & Interface + } +run Show for 3 expect 1 diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/toys/life.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/toys/life.als new file mode 100644 index 00000000..5ee1b159 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/toys/life.als @@ -0,0 +1,94 @@ +module examples/toys/life + +/* + * John Conway's Game of Life + * + * For a detailed description, see: + * http://www.math.com/students/wonders/life/life.html + * + * authors: Bill Thies, Manu Sridharan + */ + +open util/ordering[State] as ord + +sig Point { + right: lone Point, + below: lone Point +} + +fact Acyclic { + all p: Point | p !in p.^(right + below) +} + +one sig Root extends Point {} + +fact InnerSquaresCommute { + all p: Point { + p.below.right = p.right.below + some p.below && some p.right => some p.below.right + } +} + +fact TopRow { + all p: Point - Root | no p.~below => # p.*below = # Root.*below +} + +fact Connected { + Root.*(right + below) = Point +} + +pred Square { + # Root.*right = # Root.*below +} + +run Square for 6 Point, 3 State expect 1 + +pred Rectangle {} + +sig State { + live : set Point +} + +fun Neighbors[p : Point] : set Point { + p.right + p.right.below + p.below + + p.below.~right + p.~right + + p.~right.~below + p.~below + + p.~below.right +} + +fun LiveNeighborsInState[p : Point, s : State] : set Point { + Neighbors[p] & s.live +} + +pred Trans[pre, post: State, p : Point] { + let preLive = LiveNeighborsInState[p,pre] | + // dead cell w/ 3 live neighbors becomes live + (p !in pre.live && # preLive = 3) => + p in post.live + else ( + // live cell w/ 2 or 3 live neighbors stays alive + (p in pre.live && (# preLive = 2 || # preLive = 3)) => + p in post.live else p !in post.live + ) +} + +fact ValidTrans { + all pre : State - ord/last | + let post = ord/next[pre] | + all p : Point | + Trans[pre,post,p] +} + +pred Show {} + +// slow +run Show for exactly 12 Point, 3 State expect 1 + +// a small but interesting example +pred interesting { + some State.live + some Point - State.live + some right + some below +} +run interesting for exactly 6 Point, 3 State expect 1 \ No newline at end of file diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/toys/life.thm b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/toys/life.thm new file mode 100644 index 00000000..3a015d56 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/toys/life.thm @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/toys/numbering.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/toys/numbering.als new file mode 100644 index 00000000..f8c9b5f3 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/toys/numbering.als @@ -0,0 +1,129 @@ +module examples/toys/numbering + +/* + * Alloy model of paragraph numbering + * + * This model addresses some issues that arose in the design of a text tagging tool. The + * tool receives as input a text stream in which paragraphs are tagged with style names, + * along with a style sheet that indicates how paragraphs of a given style are to be numbered. + * In practice, the style sheet includes such details as what symbols to use for numbering (eg, + * roman numericals, letters of the alphabet, etc), but these details are uninteresting. + * + * In the simplest case, the styles are organized into chains. For example, there may be a + * single chain, chapter-section-subsection, so that chapters are numbered 1, 2, 3, etc, + * sections are numbered 1.1, 1.2, 1.3, etc, and subsections are numbered 1.1.1, 1.1.2, + * etc, each paragraph being numbered according to a number associated with its own + * style, and a number for each ancestor. + * + * Some styles, however, should be numbered independently of one another, but still + * according to the same ancestors. For example, we might also have a figure style + * that is numbered, like section, according to its chapter, with figures and sections in + * some arbitrary interleaving, the numbering of one not affecting the other. + * + * So in our style hierarchy, a style can have more than one "child". A more tricky complication + * allows multiple parents. We might want to have an appendix style, for example, with a + * different numbering from the chapter style, but would want section and subsection to work + * within appendix exactly as they would work within chapter. So the first section in an + * appendix numbered A might be numbered A.1, but if placed in a chapter numbered 1, + * it would be numbered 1.1 instead. + * + * To account for this, styles are organized into replacement classes. Chapter and appendix, + * for example, are replacements of one another. When a chapter style is encountered, it + * is as if the style hierarchy contains only chapter, with children section, figure and so on; + * when appendix is encountered subsequently, chapter is replaced, and figure and section + * become children of appendix. We'll call the set of styles active in the tree at a given time + * the "context". + * + * The first part focuses on the replacement mechanism. It characterizes a well-formed + * style sheet (with the fact StyleSheet), and a well-formed state (with the fact Forest). An + * operation addStyleToContext describes how the context is altered, and includes a + * precondition requiring that, for example, a child is not encountered before its parents + * (a document can't start with subsection, eg). The assertion PreservesForest checks that the + * operation preserves the well-formedness of the state; it was analyzing this that helped + * determine an appropriate precondition and the appropriate constraints in the invariant. + * + * The second part adds the numbering of styles. Note the idiom of declaring a subsignature + * and then equating it to the supersignature, thus essentially retrofitting the new fields to the + * old signature. A second operation describes how numbers are assigned; the conjunction of the + * two operations is what happens when a style is encountered. The assertion AddNeverReduces + * checks that when a style is encountered the number associated with each style in the context is + * not decreased. The first assertion is valid; the second isn't. + * + * author: Daniel Jackson, 11/15/01 + */ + +open util/relation as rel + +sig Style { + replaces, parents: set Style + } + +fact StyleSheet { + equivalence [replaces, Style] + acyclic [parents, Style] + all x: Style { + x.replaces.parents.replaces = x.parents + all y,z: x.parents | y in z.replaces + } + } + +sig State { + context: set Style, + ancestors: Style -> Style + } + +fact DefineAncestors { + all s: State, x: Style | s.ancestors [x] = x.*parents & s.context + } + +pred Forest [s: State] { + all x: s.context | + some root: s.ancestors[x] { + no root.parents + all y: s.ancestors[x] - root | one y.parents & s.context + } + all x: Style | lone x.replaces & s.context + } + +pred AddStyleToContext [s, s': State, style: Style] { + all x: style.^parents | some x.replaces & s.context + s'.context = s.context - style.replaces + style + } + +assert PreserveForest { + all s,s': State, z: Style | + Forest[s] && AddStyleToContext [s,s',z] => Forest[s'] + } + +check PreserveForest for 4 expect 0 + +sig Value {next: Value} +sig NumberedStyle extends Style { + initial: Value + } +sig NumberedState extends State { + value: Style -> one Value + } +fact {Style = NumberedStyle} +fact {State = NumberedState} + +pred AddStyleToNumbering [s, s': State, style: Style] { + s'.value[style] = (style in s.context => s.value[style].next else style.initial) + s'.context = s.context - style.replaces + style + all x: Style - style | + s'.value[x] = (style in x.^parents => x.initial else s.value[x]) + } + +pred AddStyle [s, s': State, style: Style] { + AddStyleToContext [s,s',style] + AddStyleToNumbering [s,s',style] + } + +assert AddNeverReduces { + all s,s': State, z: Style | + Forest[s] && AddStyle [s,s',z] => + (all y: s'.context | s'.value[y] in s.value[y].*next) + } + +check AddNeverReduces for 5 expect 1 + diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/toys/railway.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/toys/railway.als new file mode 100644 index 00000000..549bdb92 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/toys/railway.als @@ -0,0 +1,64 @@ +module examples/toys/railway + +/* + * A simple model of a railway system. Trains sit on segments of tracks + * and segments overlap one another. It shows a that simple gate policy + * does not ensure train safety. + * + * author: Daniel Jackson + */ + +sig Seg {next, overlaps: set Seg} +fact {all s: Seg | s in s.overlaps} +fact {all s1, s2: Seg | s1 in s2.overlaps => s2 in s1.overlaps} + +sig Train {} +sig GateState {closed: set Seg} +sig TrainState {on: Train -> lone Seg, occupied: set Seg} +fact {all x: TrainState | + x.occupied = {s: Seg | some t: Train | t.(x.on) = s} + } + +pred Safe [x: TrainState] {all s: Seg | lone s.overlaps.~(x.on)} + +pred MayMove [g: GateState, x: TrainState, ts: set Train] { + no ts.(x.on) & g.closed + } + +pred TrainsMove [x, x': TrainState, ts: set Train] { + all t: ts | t.(x'.on) in t.(x.on).next + all t: Train - ts | t.(x'.on) = t.(x.on) + } + +pred GatePolicy [g: GateState, x: TrainState] { + x.occupied.overlaps.~next in g.closed + all s1, s2: Seg | some s1.next.overlaps & s2.next => lone (s1+s2) - g.closed +} + +assert PolicyWorks { + all x, x': TrainState, g: GateState, ts: set Train | + {MayMove [g, x, ts] + TrainsMove [x, x', ts] + Safe [x] + GatePolicy [g, x] + } => Safe [x'] + } + +-- has counterexample in scope of 4 +check PolicyWorks for 2 Train, 1 GateState, 2 TrainState, 4 Seg expect 1 + +pred TrainsMoveLegal [x, x': TrainState, g: GateState, ts: set Train] { + TrainsMove [x, x', ts] + MayMove [g, x, ts] + GatePolicy [g, x] + } +run TrainsMoveLegal for 3 expect 1 + + + +// DEFINED VARIABLES +// Defined variables are uncalled, no-argument functions. +// They are helpful for getting good visualization. +fun contains [] : TrainState -> Seg -> Train { + {state: TrainState, seg: Seg, train: Train | seg = train.(state.on)} +} diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/toys/railway.thm b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/toys/railway.thm new file mode 100644 index 00000000..40baef57 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/toys/railway.thm @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/toys/trivial.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/toys/trivial.als new file mode 100644 index 00000000..71f19d3d --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/toys/trivial.als @@ -0,0 +1,8 @@ +//a trivial model whose command has no solution +module trivial + +sig S {} + +fact { 1=2 } + +run {some S} expect 0 diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/tutorial/farmer.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/tutorial/farmer.als new file mode 100644 index 00000000..94ea0d5d --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/examples/tutorial/farmer.als @@ -0,0 +1,92 @@ +module examples/tutorial/farmer + +/* + * The classic river crossing puzzle. A farmer is carrying a fox, a + * chicken, and a sack of grain. He must cross a river using a boat + * that can only hold the farmer and at most one other thing. If the + * farmer leaves the fox alone with the chicken, the fox will eat the + * chicken; and if he leaves the chicken alone with the grain, the + * chicken will eat the grain. How can the farmer bring everything + * to the far side of the river intact? + * + * authors: Greg Dennis, Rob Seater + * + * Acknowledgements to Derek Rayside and his students for finding and + * fixing a bug in the "crossRiver" predicate. + */ + +open util/ordering[State] as ord + +/** + * The farmer and all his possessions will be represented as Objects. + * Some objects eat other objects when the Farmer's not around. + */ +abstract sig Object { eats: set Object } +one sig Farmer, Fox, Chicken, Grain extends Object {} + +/** + * Define what eats what when the Farmer' not around. + * Fox eats the chicken and the chicken eats the grain. + */ +fact eating { eats = Fox->Chicken + Chicken->Grain } + +/** + * The near and far relations contain the objects held on each + * side of the river in a given state, respectively. + */ +sig State { + near: set Object, + far: set Object +} + +/** + * In the initial state, all objects are on the near side. + */ +fact initialState { + let s0 = ord/first | + s0.near = Object && no s0.far +} + +/** + * Constrains at most one item to move from 'from' to 'to'. + * Also constrains which objects get eaten. + */ +pred crossRiver [from, from', to, to': set Object] { + // either the Farmer takes no items + (from' = from - Farmer - from'.eats and + to' = to + Farmer) or + // or the Farmer takes one item + (one x : from - Farmer | { + from' = from - Farmer - x - from'.eats + to' = to + Farmer + x }) +} + +/** + * crossRiver transitions between states + */ +fact stateTransition { + all s: State, s': ord/next[s] { + Farmer in s.near => + crossRiver[s.near, s'.near, s.far, s'.far] else + crossRiver[s.far, s'.far, s.near, s'.near] + } +} + +/** + * the farmer moves everything to the far side of the river. + */ +pred solvePuzzle { + ord/last.far = Object +} + +run solvePuzzle for 8 State expect 1 + +/** + * no Object can be in two places at once + * this is implied by both definitions of crossRiver + */ +assert NoQuantumObjects { + no s : State | some x : Object | x in s.near and x in s.far +} + +check NoQuantumObjects for 8 State expect 0 \ No newline at end of file diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/util/boolean.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/util/boolean.als new file mode 100644 index 00000000..61d8744e --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/util/boolean.als @@ -0,0 +1,43 @@ +module util/boolean + +/* + * Creates a Bool type with two singleton subtypes: True + * and False. Provides common boolean operations. + * + * author: Greg Dennis + */ + +abstract sig Bool {} +one sig True, False extends Bool {} + +pred isTrue[b: Bool] { b in True } + +pred isFalse[b: Bool] { b in False } + +fun Not[b: Bool] : Bool { + Bool - b +} + +fun And[b1, b2: Bool] : Bool { + subset_[b1 + b2, True] +} + +fun Or[b1, b2: Bool] : Bool { + subset_[True, b1 + b2] +} + +fun Xor[b1, b2: Bool] : Bool { + subset_[Bool, b1 + b2] +} + +fun Nand[b1, b2: Bool] : Bool { + subset_[False, b1 + b2] +} + +fun Nor[b1, b2: Bool] : Bool { + subset_[b1 + b2, False] +} + +fun subset_[s1, s2: set Bool] : Bool { + (s1 in s2) => True else False +} diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/util/graph.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/util/graph.als new file mode 100644 index 00000000..b722ac13 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/util/graph.als @@ -0,0 +1,78 @@ +module util/graph[node] + +/* + * Utilities for some common operations and contraints + * on graphs. + * + * author: Greg Dennis + */ + +open util/relation as rel + +/** graph in undirected */ +pred undirected [r: node->node] { + symmetric[r] +} + +/** graph has no self-loops */ +pred noSelfLoops[r: node->node] { + irreflexive[r] +} + +/** graph is weakly connected */ +pred weaklyConnected[r: node->node] { + all n1, n2: node | n1 in n2.*(r + ~r) // Changed from ^ to * to permit singleton +} + +/** graph is strongly connected */ +pred stronglyConnected[r: node->node] { + all n1, n2: node | n1 in n2.*r // Changed from ^ to * to permit singleton +} + +/** graph is rooted at root */ +pred rootedAt[r: node->node, root: node] { + node in root.*r +} + +/** graph is a ring */ +pred ring [r: node->node] { + all n: node | one n.r && rootedAt[r, n] +} + +/** graph is a dag */ +pred dag [r: node->node] { + acyclic[r, node] +} + +/** graph is a forest */ +pred forest [r: node->node] { + dag[r] + all n: node | lone r.n +} + +/** graph is a tree */ +pred tree [r: node->node] { + forest[r] + lone root: node | no r.root +} + +/** graph is a tree rooted at root */ +pred treeRootedAt[r: node->node, root: node] { + forest[r] + rootedAt[r, root] +} + +/** returns the roots of the graph */ +fun roots [r: node->node] : set node { + node - node.^r +} + +/** returns the leaves of the grpah */ +fun leaves [r: node->node] : set node { + node - node.^~r +} + +/** returns the inner nodes (non-leaves) of the graph */ +fun innerNodes [r: node->node] : set node { + node - leaves[r] +} diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/util/integer.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/util/integer.als new file mode 100644 index 00000000..488c25e2 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/util/integer.als @@ -0,0 +1,112 @@ +module util/integer + +/* + * A collection of utility functions for using Integers in Alloy. + * Note that integer overflows are silently truncated to the current bitwidth + * using the 2's complement arithmetic, unless the "forbid overfows" option is + * turned on, in which case only models that don't have any overflows are + * analyzed. + */ + +fun add [n1, n2: Int] : Int { this/plus[n1, n2] } +fun plus [n1, n2: Int] : Int { n1 fun/add n2 } + +fun sub [n1, n2: Int] : Int { this/minus[n1, n2] } +fun minus [n1, n2: Int] : Int { n1 fun/sub n2 } + +fun mul [n1, n2: Int] : Int { n1 fun/mul n2 } + +/** + * Performs the division with "round to zero" semantics, except the following 3 cases + * 1) if a is 0, then it returns 0 + * 2) else if b is 0, then it returns 1 if a is negative and -1 if a is positive + * 3) else if a is the smallest negative integer, and b is -1, then it returns a + */ +fun div [n1, n2: Int] : Int { n1 fun/div n2 } + +/** answer is defined to be the unique integer that satisfies "a = ((a/b)*b) + remainder" */ +fun rem [n1, n2: Int] : Int { n1 fun/rem n2 } + +/** negate */ +fun negate [n: Int] : Int { 0 fun/sub n } + +/** equal to */ +pred eq [n1, n2: Int] { int[n1] = int[n2] } + +/** greater than */ +pred gt [n1, n2: Int] { n1 > n2 } + +/** less then */ +pred lt [n1, n2: Int] { n1 < n2 } + +/** greater than or equal */ +pred gte [n1, n2: Int] { n1 >= n2 } + +/** less than or equal */ +pred lte [n1, n2: Int] { n1 <= n2 } + +/** integer is zero */ +pred zero [n: Int] { n = 0 } + +/** positive */ +pred pos [n: Int] { n > 0 } + +/** negative */ +pred neg [n: Int] { n < 0 } + +/** non-positive */ +pred nonpos [n: Int] { n <= 0 } + +/** non-negative */ +pred nonneg [n: Int] { n >= 0 } + +/** signum (aka sign or sgn) */ +fun signum [n: Int] : Int { n<0 => (0 fun/sub 1) else (n>0 => 1 else 0) } + +/** + * returns the ith element (zero-based) from the set s + * in the ordering of 'next', which is a linear ordering + * relation like that provided by util/ordering + */ +fun int2elem[i: Int, next: univ->univ, s: set univ] : lone s { + {e: s | #^next.e = int i } +} + +/** + * returns the index of the element (zero-based) in the + * ordering of next, which is a linear ordering relation + * like that provided by util/ordering + */ +fun elem2int[e: univ, next: univ->univ] : lone Int { + Int[#^next.e] +} + +/** returns the largest integer in the current bitwidth */ +fun max:one Int { fun/max } + +/** returns the smallest integer in the current bitwidth */ +fun min:one Int { fun/min } + +/** maps each integer (except max) to the integer after it */ +fun next:Int->Int { fun/next } + +/** maps each integer (except min) to the integer before it */ +fun prev:Int->Int { ~next } + +/** given a set of integers, return the largest element */ +fun max [es: set Int]: lone Int { es - es.^prev } + +/** given a set of integers, return the smallest element */ +fun min [es: set Int]: lone Int { es - es.^next } + +/** given an integer, return all integers prior to it */ +fun prevs [e: Int]: set Int { e.^prev } + +/** given an integer, return all integers following it */ +fun nexts [e: Int]: set Int { e.^next } + +/** returns the larger of the two integers */ +fun larger [e1, e2: Int]: Int { let a=int[e1], b=int[e2] | (a b else a) } + +/** returns the smaller of the two integers */ +fun smaller [e1, e2: Int]: Int { let a=int[e1], b=int[e2] | (a a else b) } diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/util/natural.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/util/natural.als new file mode 100644 index 00000000..5d9f6246 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/util/natural.als @@ -0,0 +1,78 @@ +module util/natural + +/* + * Utility function and predicates for using the set of + * nonnegative integers (0, 1, 2, . . .). The number of + * naturals present in an analysis will be equal to the + * scope on Natural. Specifically, if the scope on Natural + * is N, then the naturals 0 through N-1 will be present. + * + * Note that the functions that return Naturals, such as + * 'add' and 'div', may return an empty set if no such + * Natural exists for that integer value. + * + * To write an Alloy model that makes use of negative + * integers, use the util/integer module instead. + * + * @author Greg Dennis + */ + +private open util/ordering[Natural] as ord +private open util/integer as integer + +sig Natural {} + +/** the integer zero */ +one sig Zero in Natural {} + +/** the integer one will be the empty set if the scope on Natural is less than two */ +lone sig One in Natural {} + +fact { + first in Zero + next[first] in One +} + +/** returns n + 1 */ +fun inc [n: Natural] : lone Natural { ord/next[n] } + +/** returns n - 1 */ +fun dec [n: Natural] : lone Natural { ord/prev[n] } + +/** returns n1 + n2 */ +fun add [n1, n2: Natural] : lone Natural { + {n: Natural | #ord/prevs[n] = #ord/prevs[n1] + #ord/prevs[n2]} +} + +/** returns n1 - n2 */ +fun sub [n1, n2: Natural] : lone Natural { + {n: Natural | #ord/prevs[n1] = #ord/prevs[n2] + #ord/prevs[n]} +} + +/** returns n1 * n2 */ +fun mul [n1, n2: Natural] : lone Natural { + {n: Natural | #ord/prevs[n] = #(ord/prevs[n1]->ord/prevs[n2])} +} + +/** returns n1 / n2 */ +fun div [n1, n2: Natural] : lone Natural { + {n: Natural | #ord/prevs[n1] = #(ord/prevs[n2]->ord/prevs[n])} +} + +/** returns true iff n1 is greater than n2 */ +pred gt [n1, n2: Natural] { ord/gt [n1, n2] } + +/** returns true iff n1 is less than n2 */ +pred lt [n1, n2: Natural] { ord/lt [n1, n2] } + +/** returns true iff n1 is greater than or equal to n2 */ +pred gte [n1, n2: Natural] { ord/gte[n1, n2] } + +/** returns true iff n1 is less than or equal to n2 */ +pred lte [n1, n2: Natural] { ord/lte[n1, n2] } + +/** returns the maximum integer in ns */ +fun max [ns: set Natural] : lone Natural { ord/max[ns] } + +/** returns the minimum integer in ns */ +fun min [ns: set Natural] : lone Natural { ord/min[ns] } diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/util/ordering.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/util/ordering.als new file mode 100644 index 00000000..e26d7bd0 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/util/ordering.als @@ -0,0 +1,106 @@ +module util/ordering[exactly elem] + +/* + * Creates a single linear ordering over the atoms in elem. It also constrains all + * the atoms to exist that are permitted by the scope on elem. That is, if the scope + * on a signature S is 5, opening util/ordering[S] will force S to have 5 elements + * and create a linear ordering over those five elements. The predicates and + * functions below provide access to properties of the linear ordering, such as + * which element is first in the ordering, or whether a given element precedes + * another. You cannotcreate multiple linear orderings over the same signature with + * this model. If you that functionality, try using the util/sequence module instead. + * + * Technical comment: + * An important constraint: elem must contain all atoms permitted by the scope. + * This is to let the analyzer optimize the analysis by setting all fields of each + * instantiation of Ord to predefined values: e.g. by setting 'last' to the highest + * atom of elem and by setting 'next' to {,,...}, where n is + * the scope of elem. Without this constraint, it might not be true that Ord.last is + * a subset of elem, and that the domain and range of Ord.next lie inside elem. + * + * author: Ilya Shlyakhter + * revisions: Daniel jackson + */ + +private one sig Ord { + First: set elem, + Next: elem -> elem +} { + pred/totalOrder[elem,First,Next] +} + +/** first */ +fun first: one elem { Ord.First } + +/** last */ +fun last: one elem { elem - (next.elem) } + +/** return a mapping from each element to its predecessor */ +fun prev : elem->elem { ~(Ord.Next) } + +/** return a mapping from each element to its successor */ +fun next : elem->elem { Ord.Next } + +/** return elements prior to e in the ordering */ +fun prevs [e: elem]: set elem { e.^(~(Ord.Next)) } + +/** return elements following e in the ordering */ +fun nexts [e: elem]: set elem { e.^(Ord.Next) } + +/** e1 is less than e2 in the ordering */ +pred lt [e1, e2: elem] { e1 in prevs[e2] } + +/** e1 is greater than e2 in the ordering */ +pred gt [e1, e2: elem] { e1 in nexts[e2] } + +/** e1 is less than or equal to e2 in the ordering */ +pred lte [e1, e2: elem] { e1=e2 || lt [e1,e2] } + +/** e1 is greater than or equal to e2 in the ordering */ +pred gte [e1, e2: elem] { e1=e2 || gt [e1,e2] } + +/** returns the larger of the two elements in the ordering */ +fun larger [e1, e2: elem]: elem { lt[e1,e2] => e2 else e1 } + +/** returns the smaller of the two elements in the ordering */ +fun smaller [e1, e2: elem]: elem { lt[e1,e2] => e1 else e2 } + +/** + * returns the largest element in es + * or the empty set if es is empty + */ +fun max [es: set elem]: lone elem { es - es.^(~(Ord.Next)) } + +/** + * returns the smallest element in es + * or the empty set if es is empty + */ +fun min [es: set elem]: lone elem { es - es.^(Ord.Next) } + +assert correct { + let mynext = Ord.Next | + let myprev = ~mynext | { + ( all b:elem | (lone b.next) && (lone b.prev) && (b !in b.^mynext) ) + ( (no first.prev) && (no last.next) ) + ( all b:elem | (b!=first && b!=last) => (one b.prev && one b.next) ) + ( !one elem => (one first && one last && first!=last && one first.next && one last.prev) ) + ( one elem => (first=elem && last=elem && no myprev && no mynext) ) + ( myprev=~mynext ) + ( elem = first.*mynext ) + (all disj a,b:elem | a in b.^mynext or a in b.^myprev) + (no disj a,b:elem | a in b.^mynext and a in b.^myprev) + (all disj a,b,c:elem | (b in a.^mynext and c in b.^mynext) =>(c in a.^mynext)) + (all disj a,b,c:elem | (b in a.^myprev and c in b.^myprev) =>(c in a.^myprev)) + } +} +run {} for exactly 0 elem expect 0 +run {} for exactly 1 elem expect 1 +run {} for exactly 2 elem expect 1 +run {} for exactly 3 elem expect 1 +run {} for exactly 4 elem expect 1 +check correct for exactly 0 elem +check correct for exactly 1 elem +check correct for exactly 2 elem +check correct for exactly 3 elem +check correct for exactly 4 elem +check correct for exactly 5 elem diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/util/relation.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/util/relation.als new file mode 100644 index 00000000..d648a77c --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/util/relation.als @@ -0,0 +1,101 @@ +module util/relation + +/* + * Utilities for some common operations and constraints + * on binary relations. The keyword 'univ' represents the + * top-level type, which all other types implicitly extend. + * Therefore, all the functions and predicates in this model + * may be applied to binary relations of any type. + * + * author: Greg Dennis + */ + +/** returns the domain of a binary relation */ +fun dom [r: univ->univ] : set (r.univ) { r.univ } + +/** returns the range of a binary relation */ +fun ran [r: univ->univ] : set (univ.r) { univ.r } + +/** r is total over the domain s */ +pred total [r: univ->univ, s: set univ] { + all x: s | some x.r +} + +/** r is a partial function over the domain s */ +pred functional [r: univ->univ, s: set univ] { + all x: s | lone x.r +} + +/** r is a total function over the domain s */ +pred function [r: univ->univ, s: set univ] { + all x: s | one x.r +} + +/** r is surjective over the codomain s */ +pred surjective [r: univ->univ, s: set univ] { + all x: s | some r.x +} + +/** r is injective */ +pred injective [r: univ->univ, s: set univ] { + all x: s | lone r.x +} + +/** r is bijective over the codomain s */ +pred bijective[r: univ->univ, s: set univ] { + all x: s | one r.x +} + +/** r is a bijection over the domain d and the codomain c */ +pred bijection[r: univ->univ, d, c: set univ] { + function[r, d] && bijective[r, c] +} + +/** r is reflexive over the set s */ +pred reflexive [r: univ -> univ, s: set univ] {s<:iden in r} + +/** r is irreflexive */ +pred irreflexive [r: univ -> univ] {no iden & r} + +/** r is symmetric */ +pred symmetric [r: univ -> univ] {~r in r} + +/** r is anti-symmetric */ +pred antisymmetric [r: univ -> univ] {~r & r in iden} + +/** r is transitive */ +pred transitive [r: univ -> univ] {r.r in r} + +/** r is acyclic over the set s */ +pred acyclic[r: univ->univ, s: set univ] { + all x: s | x !in x.^r +} + +/** r is complete over the set s */ +pred complete[r: univ->univ, s: univ] { + all x,y:s | (x!=y => x->y in (r + ~r)) +} + +/** r is a preorder (or a quasi-order) over the set s */ +pred preorder [r: univ -> univ, s: set univ] { + reflexive[r, s] + transitive[r] +} + +/** r is an equivalence relation over the set s */ +pred equivalence [r: univ->univ, s: set univ] { + preorder[r, s] + symmetric[r] +} + +/** r is a partial order over the set s */ +pred partialOrder [r: univ -> univ, s: set univ] { + preorder[r, s] + antisymmetric[r] +} + +/** r is a total order over the set s */ +pred totalOrder [r: univ -> univ, s: set univ] { + partialOrder[r, s] + complete[r, s] +} diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/util/seqrel.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/util/seqrel.als new file mode 100644 index 00000000..1fe3802d --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/util/seqrel.als @@ -0,0 +1,108 @@ +module util/seqrel[elem] + +/* + * A sequence utility for modeling sequences as just a + * relation as opposed to reifying them into sequence + * atoms like the util/sequence module does. + * + * @author Greg Dennis + */ + +open util/integer +open util/ordering[SeqIdx] as ord + +sig SeqIdx {} + +/** sequence covers a prefix of SeqIdx */ +pred isSeq[s: SeqIdx -> elem] { + s in SeqIdx -> lone elem + s.inds - ord/next[s.inds] in ord/first +} + +/** returns all the elements in this sequence */ +fun elems [s: SeqIdx -> elem]: set elem { SeqIdx.s } + +/** returns the first element in the sequence */ +fun first [s: SeqIdx -> elem]: lone elem { s[ord/first] } + +/** returns the last element in the sequence */ +fun last [s: SeqIdx -> elem]: lone elem { s[lastIdx[s]] } + +/** returns the cdr of the sequence */ +fun rest [s: SeqIdx -> elem] : SeqIdx -> elem { + (ord/next).s +} + +/** returns all but the last element of the sequence */ +fun butlast [s: SeqIdx -> elem] : SeqIdx -> elem { + (SeqIdx - lastIdx[s]) <: s +} + +/** true if the sequence is empty */ +pred isEmpty [s: SeqIdx -> elem] { no s } + +/** true if this sequence has duplicates */ +pred hasDups [s: SeqIdx -> elem] { # elems[s] < # inds[s] } + +/** returns all the indices occupied by this sequence */ +fun inds [s: SeqIdx -> elem]: set SeqIdx { s.elem } + +/** returns last index occupied by this sequence */ +fun lastIdx [s: SeqIdx -> elem]: lone SeqIdx { ord/max[inds[s]] } + +/** + * returns the index after the last index + * if this sequence is empty, returns the first index, + * if this sequence is full, returns empty set + */ +fun afterLastIdx [s: SeqIdx -> elem] : lone SeqIdx { + ord/min[SeqIdx - inds[s]] +} + +/** returns first index at which given element appears or the empty set if it doesn't */ +fun idxOf [s: SeqIdx -> elem, e: elem] : lone SeqIdx { ord/min[indsOf[s, e]] } + +/** returns last index at which given element appears or the empty set if it doesn't */ +fun lastIdxOf [s: SeqIdx -> elem, e: elem] : lone SeqIdx { ord/max[indsOf[s, e]] } + +/** returns set of indices at which given element appears or the empty set if it doesn't */ +fun indsOf [s: SeqIdx -> elem, e: elem] : set SeqIdx { s.e } + +/** + * return the result of appending e to the end of s + * just returns s if s exhausted SeqIdx + */ +fun add [s: SeqIdx -> elem, e: elem] : SeqIdx -> elem { + setAt[s, afterLastIdx[s], e] +} + +/** returns the result of setting the value at index i in sequence to e */ +fun setAt [s: SeqIdx -> elem, i: SeqIdx, e: elem] : SeqIdx -> elem { + s ++ i -> e +} + +/** returns the result of inserting value e at index i */ +fun insert [s: SeqIdx -> elem, i: SeqIdx, e: elem] : SeqIdx -> elem { + (ord/prevs[i] <: s) + (i->e) + (~(ord/next)).((ord/nexts[i] + i) <: s) +} + +/** returns the result of deleting the value at index i */ +fun delete[s: SeqIdx -> elem, i: SeqIdx] : SeqIdx -> elem { + (ord/prevs[i] <: s) + (ord/next).(ord/nexts[i] <: s) +} + +/** appended is the result of appending s2 to s1 */ +fun append [s1, s2: SeqIdx -> elem] : SeqIdx -> elem { + let shift = {i', i: SeqIdx | #ord/prevs[i'] = add[#ord/prevs[i], add[#ord/prevs[lastIdx[s1]], 1]] } | + s1 + shift.s2 +} + +/** returns the subsequence of s between from and to, inclusive */ +fun subseq [s: SeqIdx -> elem, from, to: SeqIdx] : SeqIdx -> elem { + let shift = {i', i: SeqIdx | #ord/prevs[i'] = sub[#ord/prevs[i], #ord/prevs[from]] } | + shift.((SeqIdx - ord/nexts[to]) <: s) +} + +fun firstIdx: SeqIdx { ord/first } + +fun finalIdx: SeqIdx { ord/last } \ No newline at end of file diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/util/sequence.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/util/sequence.als new file mode 100644 index 00000000..524e3cf7 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/util/sequence.als @@ -0,0 +1,166 @@ +module util/sequence[elem] + +/* + * Creates sequences (or lists) of elements. The ordered signature SeqIdx + * represents the indexes at which elements can be located, and a sequence + * is modeled as a mapping from indexes to elements. Empty sequences are + * allowed, and a sequence may have a single element appear multiple times. + * Maximum length of a sequence is determined by the scope of SeqIdx. + * + * Sequences always cover an initial segment of SeqIdx. That is, every + * sequence (except the empty sequence) begins at the first SeqIdx and does + * not have gaps in indexes that it covers. In other words, if a sequence has + * its last element at index i, then the sequence has elements at all the + * indexes that precede i. + * + * Oftentimes, a model will need to require that all sequences that could + * exist, do exist. Calling the allExist predicate will ensure that that there is + * a Seq atom for each possible sequences of elements with length less than + * or equal to the scope of SeqIdx. + * + * The functions and predicates at the bottom of this module provide all + * functionality of util/ordering on SeqIdx. + * + * revisions: Greg Dennis + */ + +open util/ordering[SeqIdx] as ord + +sig SeqIdx {} + +sig Seq { + seqElems: SeqIdx -> lone elem +} +{ + // Ensure that elems covers only an initial segment of SeqIdx, + // equal to the length of the signature + all i: SeqIdx - ord/first | some i.seqElems => some ord/prev[i].seqElems +} + +/** no two sequences are identical */ +fact canonicalizeSeqs { + no s1, s2: Seq | s1!=s2 && s1.seqElems=s2.seqElems +} + +/** invoke if you want none of the sequences to have duplicates */ +pred noDuplicates { + all s: Seq | !s.hasDups +} + +/** invoke if you want all sequences within scope to exist */ +pred allExist { + (some s: Seq | s.isEmpty) && + (all s: Seq | SeqIdx !in s.inds => (all e: elem | some s': Seq | s.add[e, s'])) +} + +/** invoke if you want all sequences within scope with no duplicates */ +pred allExistNoDuplicates { + some s: Seq | s.isEmpty + all s: Seq { + !s.hasDups + SeqIdx !in s.inds => (all e: elem - s.elems | some s': Seq | s.add[e, s']) + } +} + +/** returns element at the given index */ +fun at [s: Seq, i: SeqIdx]: lone elem { i.(s.seqElems) } + +/** returns all the elements in this sequence */ +fun elems [s: Seq]: set elem { SeqIdx.(s.seqElems) } + +/** returns the first element in the sequence */ +fun first [s:Seq]: lone elem { s.at[ord/first] } + +/** returns the last element in the sequence */ +fun last [s:Seq]: lone elem { s.at[s.lastIdx] } + +/** + * true if the argument is the "cdr" of this sequence + * false if this sequence is empty + */ +pred rest [s, r: Seq] { + !s.isEmpty + all i: SeqIdx | r.at[i] = s.at[ord/next[i]] +} + +/** true if the sequence is empty */ +pred isEmpty [s:Seq] { no s.elems } + +/** true if this sequence has duplicates */ +pred hasDups [s:Seq] { # elems[s] < # inds[s] } + +/** returns all the indices occupied by this sequence */ +fun inds [s:Seq] : set SeqIdx { elem.~(s.seqElems) } + +/** returns last index occupied by this sequence */ +fun lastIdx [s:Seq] : lone SeqIdx { ord/max[s.inds] } + +/** + * returns the index after the last index + * if this sequence is empty, returns the first index, + * if this sequence is full, returns empty set + */ +fun afterLastIdx [s:Seq] : lone SeqIdx { + ord/min[SeqIdx - s.inds] +} + +/** returns first index at which given element appears or the empty set if it doesn't */ +fun idxOf [s: Seq, e: elem] : lone SeqIdx { ord/min[s.indsOf[e]] } + +/** returns last index at which given element appears or the empty set if it doesn't */ +fun lastIdxOf [s: Seq, e: elem] : lone SeqIdx { ord/max[s.indsOf[e]] } + +/** returns set of indices at which given element appears or the empty set if it doesn't */ +fun indsOf [s: Seq, e: elem] : set SeqIdx { (s.seqElems).e } + +/** true if this starts with prefix */ +pred startsWith [s, prefix: Seq] { + all i: prefix.inds | s.at[i] = prefix.at[i] +} + +/** added is the result of appending e to the end of s */ +pred add [s: Seq, e: elem, added: Seq] { + added.startsWith[s] + added.seqElems[s.afterLastIdx] = e + #added.inds = #s.inds.add[1] +} + +/** setted is the result of setting value at index i to e */ +pred setAt [s: Seq, idx: SeqIdx, e: elem, setted: Seq] { + setted.seqElems = s.seqElems ++ idx->e +} + +/** inserts is the result of inserting value e at index i */ +pred insert [s: Seq, idx: SeqIdx, e: elem, inserted: Seq] { + inserted.at[idx] = e + all i: ord/prevs[idx] | inserted.at[i] = s.at[i] + all i: ord/nexts[idx] | inserted.at[i] = s.at[ord/prev[i]] + #inserted.inds = #s.inds.add[1] +} + +/** copies source into dest starting at destStart */ +pred copy [source, dest: Seq, destStart: SeqIdx] { + all sourceIdx : source.inds | some destIdx: SeqIdx { + ord/gte[destIdx, destStart] + dest.at[destIdx] = source.at[sourceIdx] + #ord/prevs[sourceIdx] = #(ord/prevs[destIdx] - ord/prevs[destStart]) + } +} + +/** appended is the result of appending s2 to s1 */ +pred append [s1, s2, appended: Seq] { + appended.startsWith[s1] + copy[s2, appended, s1.afterLastIdx] + #appended.inds = #s1.inds.add[#s2.inds] +} + +/** sub is the subsequence of s between from and to, inclusive */ +pred subseq [s, sub: Seq, from, to: SeqIdx] { + ord/lte[from, to] + copy[sub, s, from] + #sub.inds = #(to + ord/prevs[to] - ord/prevs[from]) +} + +fun firstIdx: SeqIdx { ord/first } + +fun finalIdx: SeqIdx { ord/last } diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/util/sequniv.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/util/sequniv.als new file mode 100644 index 00000000..efa8bf42 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/util/sequniv.als @@ -0,0 +1,137 @@ +module util/sequniv + +open util/integer as ui + +/* + * NOTE: Do not include this module manually. + * Instead, use the "seq" keyword which will automatically + * import this module with the correct additional constraints as needed. + */ + +/* + * A sequence utility for modeling sequences as just a + * relation as opposed to reifying them into sequence + * atoms like the util/sequence module does. + * + * Precondition: each input sequence must range over a prefix + * of seq/Int. + * + * Postcondition: we guarantee the returned sequence + * also ranges over a prefix of seq/Int. + * + * @author Greg Dennis + */ + +/** sequence covers a prefix of seq/Int */ +pred isSeq[s: Int -> univ] { + s in seq/Int -> lone univ + s.inds - ui/next[s.inds] in 0 +} + +/** returns all the elements in this sequence */ +fun elems [s: Int -> univ]: set (Int.s) { seq/Int . s } + +/** + * returns the first element in the sequence + * (Returns the empty set if the sequence is empty) + */ +fun first [s: Int -> univ]: lone (Int.s) { s[0] } + +/** + * returns the last element in the sequence + * (Returns the empty set if the sequence is empty) + */ +fun last [s: Int -> univ]: lone (Int.s) { s[lastIdx[s]] } + +/** + * returns the cdr of the sequence + * (Returns the empty sequence if the sequence has 1 or fewer element) + */ +fun rest [s: Int -> univ] : s { seq/Int <: ((ui/next).s) } + +/** returns all but the last element of the sequence */ +fun butlast [s: Int -> univ] : s { + (seq/Int - lastIdx[s]) <: s +} + +/** true if the sequence is empty */ +pred isEmpty [s: Int -> univ] { no s } + +/** true if this sequence has duplicates */ +pred hasDups [s: Int -> univ] { # elems[s] < # inds[s] } + +/** returns all the indices occupied by this sequence */ +fun inds [s: Int -> univ]: set Int { s.univ } + +/** + * returns last index occupied by this sequence + * (Returns the empty set if the sequence is empty) + */ +fun lastIdx [s: Int -> univ]: lone Int { ui/max[inds[s]] } + +/** + * returns the index after the last index + * if this sequence is empty, returns 0 + * if this sequence is full, returns empty set + */ +fun afterLastIdx [s: Int -> univ] : lone Int { ui/min[seq/Int - inds[s]] } + +/** returns first index at which given element appears or the empty set if it doesn't */ +fun idxOf [s: Int -> univ, e: univ] : lone Int { ui/min[indsOf[s, e]] } + +/** returns last index at which given element appears or the empty set if it doesn't */ +fun lastIdxOf [s: Int -> univ, e: univ] : lone Int { ui/max[indsOf[s, e]] } + +/** returns set of indices at which given element appears or the empty set if it doesn't */ +fun indsOf [s: Int -> univ, e: univ] : set Int { s.e } + +/** + * return the result of appending e to the end of s + * (returns s if s exhausted seq/Int) + */ +fun add [s: Int -> univ, e: univ] : s + (seq/Int->e) { + setAt[s, afterLastIdx[s], e] +} + +/** + * returns the result of setting the value at index i in sequence to e + * Precondition: 0 <= i < #s + */ +fun setAt [s: Int -> univ, i: Int, e: univ] : s + (seq/Int->e) { + s ++ i -> e +} + +/** + * returns the result of inserting value e at index i + * (if sequence was full, the original last element will be removed first) + * Precondition: 0 <= i <= #s + */ +fun insert [s: Int -> univ, i: Int, e: univ] : s + (seq/Int->e) { + seq/Int <: ((ui/prevs[i] <: s) + (i->e) + ui/prev.((ui/nexts[i] + i) <: s)) +} + +/** + * returns the result of deleting the value at index i + * Precondition: 0 <= i < #s + */ +fun delete[s: Int -> univ, i: Int] : s { + (ui/prevs[i] <: s) + (ui/next).(ui/nexts[i] <: s) +} + +/** + * appended is the result of appending s2 to s1 + * (If the resulting sequence is too long, it will be truncated) + */ +fun append [s1, s2: Int -> univ] : s1+s2 { + let shift = {i', i: seq/Int | int[i'] = ui/add[int[i], ui/add[int[lastIdx[s1]], 1]] } | + no s1 => s2 else (s1 + shift.s2) +} + +/** + * returns the subsequence of s between from and to, inclusive + * Precondition: 0 <= from <= to < #s + */ +fun subseq [s: Int -> univ, from, to: Int] : s { + let shift = {i', i: seq/Int | int[i'] = ui/sub[int[i], int[from]] } | + shift.((seq/Int - ui/nexts[to]) <: s) +} diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/util/ternary.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/util/ternary.als new file mode 100644 index 00000000..6dbc0881 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/util/ternary.als @@ -0,0 +1,50 @@ +module util/ternary + +/* + * Utilities for some common operations and constraints + * on ternary relations. The keyword 'univ' represents the + * top-level type, which all other types implicitly extend. + * Therefore, all the functions and predicates in this model + * may be applied to ternary relations of any type. + * + * author: Greg Dennis + */ + +/** returns the domain of a ternary relation */ +fun dom [r: univ->univ->univ] : set ((r.univ).univ) { (r.univ).univ } + +/** returns the range of a ternary relation */ +fun ran [r: univ->univ->univ] : set (univ.(univ.r)) { univ.(univ.r) } + +/** returns the "middle range" of a ternary relation */ +fun mid [r: univ->univ->univ] : set (univ.(r.univ)) { univ.(r.univ) } + +/** returns the first two columns of a ternary relation */ +fun select12 [r: univ->univ->univ] : r.univ { + r.univ +} + +/** returns the first and last columns of a ternary relation */ +fun select13 [r: univ->univ->univ] : ((r.univ).univ) -> (univ.(univ.r)) { + {x: (r.univ).univ, z: univ.(univ.r) | some (x.r).z} +} + +/** returns the last two columns of a ternary relation */ +fun select23 [r: univ->univ->univ] : univ.r { + univ.r +} + +/** flips the first two columns of a ternary relation */ +fun flip12 [r: univ->univ->univ] : (univ.(r.univ))->((r.univ).univ)->(univ.(univ.r)) { + {x: univ.(r.univ), y: (r.univ).univ, z: univ.(univ.r) | y->x->z in r} +} + +/** flips the first and last columns of a ternary relation */ +fun flip13 [r: univ->univ->univ] : (univ.(univ.r))->(univ.(r.univ))->((r.univ).univ) { + {x: univ.(univ.r), y: univ.(r.univ), z: (r.univ).univ | z->y->x in r} +} + +/** flips the last two columns of a ternary relation */ +fun flip23 [r: univ->univ->univ] : ((r.univ).univ)->(univ.(univ.r))->(univ.(r.univ)) { + {x: (r.univ).univ, y: univ.(univ.r), z: univ.(r.univ) | x->z->y in r} +} diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/models/util/time.als b/Source/eu.modelwriter.alloyanalyzer/bin/models/util/time.als new file mode 100644 index 00000000..b6f9d8ca --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/models/util/time.als @@ -0,0 +1,53 @@ +open util/ordering[Time] + +sig Time { } + +let dynamic[x] = x one-> Time + +let dynamicSet[x] = x -> Time + +let then [a, b, t, t'] { + some x:Time | a[t,x] && b[x,t'] +} + +let while = while3 + +let while9 [cond, body, t, t'] { + some x:Time | (cond[t] => body[t,x] else t=x) && while8[cond,body,x,t'] +} + +let while8 [cond, body, t, t'] { + some x:Time | (cond[t] => body[t,x] else t=x) && while7[cond,body,x,t'] +} + +let while7 [cond, body, t, t'] { + some x:Time | (cond[t] => body[t,x] else t=x) && while6[cond,body,x,t'] +} + +let while6 [cond, body, t, t'] { + some x:Time | (cond[t] => body[t,x] else t=x) && while5[cond,body,x,t'] +} + +let while5 [cond, body, t, t'] { + some x:Time | (cond[t] => body[t,x] else t=x) && while4[cond,body,x,t'] +} + +let while4 [cond, body, t, t'] { + some x:Time | (cond[t] => body[t,x] else t=x) && while3[cond,body,x,t'] +} + +let while3 [cond, body, t, t'] { + some x:Time | (cond[t] => body[t,x] else t=x) && while2[cond,body,x,t'] +} + +let while2 [cond, body, t, t'] { + some x:Time | (cond[t] => body[t,x] else t=x) && while1[cond,body,x,t'] +} + +let while1 [cond, body, t, t'] { + some x:Time | (cond[t] => body[t,x] else t=x) && while0[cond,body,x,t'] +} + +let while0 [cond, body, t, t'] { + !cond[t] && t=t' +} diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/org/sat4j/messages.properties b/Source/eu.modelwriter.alloyanalyzer/bin/org/sat4j/messages.properties new file mode 100644 index 00000000..2b3f0f00 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/org/sat4j/messages.properties @@ -0,0 +1,8 @@ +Lanceur.wrong.framework=Wrong framework: try minisat or ubcsat +MoreThanSAT.0=Satisfiable \! +MoreThanSAT.1=BackBone: +MoreThanSAT.2=Counting solutions... +MoreThanSAT.3=Number of solutions: +MoreThanSAT.4=Unsatisfiable\! +MoreThanSAT.5=Unsatisfiable (trivial)\! +MoreThanSAT.6=Timeout, sorry\! diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/overview.html b/Source/eu.modelwriter.alloyanalyzer/bin/overview.html new file mode 100644 index 00000000..0fd29581 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/overview.html @@ -0,0 +1,33 @@ + +SAT4J: a SATisfiability library for Java. + +
+/*******************************************************************************
+* SAT4J: a SATisfiability library for Java Copyright (C) 2004-2008 Daniel Le Berre
+*
+* All rights reserved. This program and the accompanying materials
+* are made available under the terms of the Eclipse Public License v1.0
+* which accompanies this distribution, and is available at
+* http://www.eclipse.org/legal/epl-v10.html
+*
+* Alternatively, the contents of this file may be used under the terms of
+* either the GNU Lesser General Public License Version 2.1 or later (the
+* "LGPL"), in which case the provisions of the LGPL are applicable instead
+* of those above. If you wish to allow use of your version of this file only
+* under the terms of the LGPL, and not to allow others to use your version of
+* this file under the terms of the EPL, indicate your decision by deleting
+* the provisions above and replace them with the notice and other provisions
+* required by the LGPL. If you do not delete the provisions above, a recipient
+* may use your version of this file under the terms of the EPL or the LGPL.
+* 
+* Based on the original MiniSat specification from:
+* 
+* An extensible SAT solver. Niklas Een and Niklas Sorensson. Proceedings of the
+* Sixth International Conference on Theory and Applications of Satisfiability
+* Testing, LNCS 2919, pp 502-518, 2003.
+*
+* See www.minisat.se for the original solver in C++.
+* 
+*******************************************************************************/
+
+ \ No newline at end of file diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/sat4j.version b/Source/eu.modelwriter.alloyanalyzer/bin/sat4j.version new file mode 100644 index 00000000..a27f8f26 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/sat4j.version @@ -0,0 +1 @@ +2.3.2.v20120709 diff --git a/Source/eu.modelwriter.alloyanalyzer/bin/target/META-INF/MANIFEST.MF b/Source/eu.modelwriter.alloyanalyzer/bin/target/META-INF/MANIFEST.MF new file mode 100644 index 00000000..f14a9926 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/bin/target/META-INF/MANIFEST.MF @@ -0,0 +1,31 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: %bundleName +Bundle-SymbolicName: org.sat4j.core +Bundle-Version: 2.3.2.v20120709 +Export-Package: org.sat4j;version="2.3.2.v20120709", + org.sat4j.core;version="2.3.2.v20120709", + org.sat4j.minisat;version="2.3.2.v20120709", + org.sat4j.minisat.constraints;version="2.3.2.v20120709", + org.sat4j.minisat.constraints.card;version="2.3.2.v20120709", + org.sat4j.minisat.constraints.cnf;version="2.3.2.v20120709", + org.sat4j.minisat.core;version="2.3.2.v20120709", + org.sat4j.minisat.learning;version="2.3.2.v20120709", + org.sat4j.minisat.orders;version="2.3.2.v20120709", + org.sat4j.minisat.restarts;version="2.3.2.v20120709", + org.sat4j.opt;version="2.3.2.v20120709", + org.sat4j.reader;version="2.3.2.v20120709", + org.sat4j.specs;version="2.3.2.v20120709", + org.sat4j.tools;version="2.3.2.v20120709", + org.sat4j.tools.xplain;version="2.3.2.v20120709" +Bundle-Vendor: %providerName +Bundle-Localization: plugin +Built-By: Daniel Le Berre +Main-Class: org.sat4j.BasicLauncher +Specification-Title: SAT4J +Specification-Version: NA +Specification-Vendor: Daniel Le Berre +Implementation-Title: SAT4J +Implementation-Version: 2.3.2.v20120709 +Implementation-Vendor: CRIL CNRS UMR 8188 - Universite d'Artois +Bundle-RequiredExecutionEnvironment: J2SE-1.5 diff --git a/Source/eu.modelwriter.alloyanalyzer/build.properties b/Source/eu.modelwriter.alloyanalyzer/build.properties new file mode 100644 index 00000000..20001ebb --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/build.properties @@ -0,0 +1,9 @@ +source.alloyanalyzer.jar = src/ +bin.includes = META-INF/,\ + lib/alloy4.2.jar,\ + alloyanalyzer.jar,\ + bin/,\ + build.properties,\ + .settings/,\ + .project,\ + .classpath diff --git a/Source/eu.modelwriter.alloyanalyzer/lib/alloy4.2.jar b/Source/eu.modelwriter.alloyanalyzer/lib/alloy4.2.jar new file mode 100644 index 00000000..3be21612 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/lib/alloy4.2.jar differ diff --git a/Source/eu.modelwriter.alloyanalyzer/src/META-INF/MANIFEST.MF b/Source/eu.modelwriter.alloyanalyzer/src/META-INF/MANIFEST.MF new file mode 100644 index 00000000..c10f81ea --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/META-INF/MANIFEST.MF @@ -0,0 +1,4 @@ +Manifest-Version: 1.0 +Created-By: 1.5.0 (Sun Microsystems Inc.) +Main-Class: edu.mit.csail.sdg.alloy4whole.SimpleGUI + diff --git a/Source/eu.modelwriter.alloyanalyzer/src/README.TXT b/Source/eu.modelwriter.alloyanalyzer/src/README.TXT new file mode 100644 index 00000000..7cd623ea --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/README.TXT @@ -0,0 +1,63 @@ +The Alloy Analyzer + + The Alloy Analyzer is a tool developed by the Software Design + Group (http://sdg.csail.mit.edu/) for analyzing models written in + Alloy, a simple structural modeling language based on first-order + logic. The tool can generate instances of invariants, simulate + the execution of operations (even those defined implicitly), and + check user-specified properties of a model. Alloy and its + analyzer have been used primarily to explore abstract software + designs. Its use in analyzing code for conformance to a + specification and as an automatic test case generator are being + investigated in ongoing research projects. + + See the web page for a description of what's new in Alloy: + + http://alloy.mit.edu/ + + +Detailed Instructions: + + 1. Java 5 or later + + Java runtimes are available at no economic charge from Sun and + IBM and others. One may have come pre-installed in your OS. + Alloy does not currently work with gcj because of its limited + library support. + + 2. Running Alloy on Mac OS X + + Just double-click on the dmg file, + then drag the Alloy application into your application directory. + + 3. Running Alloy on other platforms + + Just double-click on the jar file, or type: + + java -jar alloy4.jar + +The source code for the Alloy Analyzer is available +under the MIT license. + +The Alloy Analyzer utilizes several third-party packages whose code may +be distributed under a different license (see the various LICENSE files +in the distribution for details). We are extremely grateful to the authors +of these packages for making their source code freely available. + + * Kodkod + http://web.mit.edu/~emina/www/kodkod.html + + * CUP Parser Generator for Java + http://www2.cs.tum.edu/projects/cup/ + + * JFlex scanner generator for Java + http://jflex.de/ + + * The zChaff solver + http://www.princeton.edu/~chaff/zchaff.html + + * The MiniSat solver + http://www.cs.chalmers.se/Cs/Research/FormalMethods/MiniSat/ + + * The SAT4J solver + http://www.sat4j.org/ diff --git a/Source/eu.modelwriter.alloyanalyzer/src/README.md b/Source/eu.modelwriter.alloyanalyzer/src/README.md new file mode 100644 index 00000000..9942d4bc --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/README.md @@ -0,0 +1,45 @@ +Alloy Analyzer (source code mirror) +=================================== + +Summary +------- +This is a copy of the source code for [MIT's Alloy Analyzer model checking tool](http://alloy.mit.edu/alloy/). +It also includes an Ant build.xml script, which is not part of the original MIT source code. +This copy was created to facilitate modification to the core Alloy tool (the parts which fall +under the `edu.mit` package structure). + +It was created as follows (not necessarily in this order): + +1. Downloaded the JAR file located at: http://alloy.mit.edu/alloy/downloads/alloy4.2.jar +2. Extracted the JAR file. +3. Added this `README.md` file and a `build.xml` file. +3. Deleted core `.class` files (using the _clean_ target in `build.xml`) + +Building +-------- +The Ant build.xml script contains the following targets: + +- _build_: Compiles the `.java` files under the `edu` directory. + + Other directories are not touched; it is assumed that these contain libraries + which have been pre-compiled. + + The auto-generated parser and lexer `.java` files (located in the `edu/mit/csail/sdg/alloy4compiler/parser` directory) + are neither deleted nor generated by the Ant script. The directory already contains shell scripts + to re-generate them using JFlex and CUP. +- _dist_: Creates an executable JAR file in the `dist` directory. This JAR file looks essentially like the official + Alloy JAR file released by MIT. +- _all_: Runs _dist_. +- _clean_: Deletes the `dist` directory and all class files under the `edu` directory. + +Notes +----- + +- As per the manifest, the main class is `edu.mit.csail.sdg.alloy4whole.SimpleGUI`. +- The version number and build date which the tool displays are not accurate. + These are set in the `edu.mit.csail.sdg.alloy4.Version` class, and are supposed to be + updated by the build script when building a release. + This project was not intended to create official releases, so it was left as-is. +- There is a class `edu.mit.csail.sdg.alloy4.MailBug` which includes logic to email + crash reports to MIT. You should change this class if you are modifying the source code + and creating your own release. diff --git a/Source/eu.modelwriter.alloyanalyzer/src/about.html b/Source/eu.modelwriter.alloyanalyzer/src/about.html new file mode 100644 index 00000000..99988c9b --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/about.html @@ -0,0 +1,56 @@ + + + + +About + + +

About This Content

+ +

June, 2010

+

License

+ +

The Eclipse Foundation makes available all content in this plug-in ("Content"). Unless otherwise indicated below, the Content is provided to you under the terms and conditions of the Eclipse Public License Version 1.0 ("EPL"). A copy of the EPL is available at http://www.eclipse.org/legal/epl-v10.html. For purposes of the EPL, "Program" will mean the Content.

+ +

If you did not receive this Content directly from the Eclipse Foundation, the Content is being redistributed by another party ("Redistributor") and different terms and conditions may apply to your use of any object code in the Content. Check the Redistributor’s license that was provided with the Content. If no such license exists, contact the Redistributor. Unless otherwise indicated below, the terms and conditions of the EPL still apply to any source code in the Content and such source code may be obtained at http://www.eclipse.org.

+ +

Third Party Content

+

The Content includes items that have been sourced from third parties as set out below. If you did not receive this Content directly from the Eclipse Foundation, the following is provided for informational purposes only, and you should look to the Redistributor’s license for terms and conditions of use.

+

SAT4J 2.3.2 SUBSET (Core)

+

The SAT4J project makes available all content in this plug-in ("Content"). Your use of the Content is governed by the terms and conditions of the Eclipse Public License Version 1.0 ("EPL"). A copy of the EPL is available at http://www.eclipse.org/legal/epl-v10.html. For purposes of the EPL, "Program" will mean the Content.

+

Alternatively, the Content may be obtained from the SAT4J project website at http://www.sat4j.org/ for use under the terms of either the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), in which case the provisions of the LGPL are applicable instead of those above. If you wish to allow use of your version of the Content only under the terms of the LGPL, and not to allow others to use your version of this Content under the terms of the EPL, indicate your decision by deleting the provisions above and replace them with the notice and other provisions required by the LGPL. If you do not delete the provisions above, a recipient may use your version of this file under the terms of the EPL or the LGPL.

+

The Eclipse Foundation elects to include this software in this distribution under the EPL license. The source code for this plug-in can be obtained from the SAT4J project website at http://www.sat4j.org/

+ +

SAT4J includes content that was obtained under licenses that differ from the SAT4J licenses.
+ The content in the following classes
+ +

    +
  • org/sat4j/core/Vec.java
  • +
  • org/sat4j/core/VecInt.java
  • +
  • org/sat4j/minisat/core/Solver.java
  • +
+

is based on code obtained from the Minisat 1.1.4 implementation, the source code for which can be found at www.minisat.se, under the following permissive license:
+
+ MiniSat -- Copyright (c) 2003-2005, Niklas Een, Niklas Sorensson
+
+ Permission is hereby granted, free of charge, to any person obtaining a
+ copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+ without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+ permit persons to whom the Software is furnished to do so, subject to
+ the following conditions:
+
+ The above copyright notice and this permission notice shall be included
+ in all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

+ + diff --git a/Source/eu.modelwriter.alloyanalyzer/src/amd64-linux/berkmin b/Source/eu.modelwriter.alloyanalyzer/src/amd64-linux/berkmin new file mode 100644 index 00000000..f0aa2cee Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/src/amd64-linux/berkmin differ diff --git a/Source/eu.modelwriter.alloyanalyzer/src/amd64-linux/libminisat.so b/Source/eu.modelwriter.alloyanalyzer/src/amd64-linux/libminisat.so new file mode 100644 index 00000000..d286aa4b Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/src/amd64-linux/libminisat.so differ diff --git a/Source/eu.modelwriter.alloyanalyzer/src/amd64-linux/libminisatprover.so b/Source/eu.modelwriter.alloyanalyzer/src/amd64-linux/libminisatprover.so new file mode 100644 index 00000000..392474ac Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/src/amd64-linux/libminisatprover.so differ diff --git a/Source/eu.modelwriter.alloyanalyzer/src/amd64-linux/libzchaff.so b/Source/eu.modelwriter.alloyanalyzer/src/amd64-linux/libzchaff.so new file mode 100644 index 00000000..dc9c229d Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/src/amd64-linux/libzchaff.so differ diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/A4Reporter.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/A4Reporter.java new file mode 100644 index 00000000..85f61920 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/A4Reporter.java @@ -0,0 +1,186 @@ +/* + * Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4; + +/** + * This class receives diagnostic, progress, and warning messages from Alloy4. (This default + * implementation ignores all calls; you should subclass it to do the appropriate screen output) + */ + +public class A4Reporter { + + /** If nonnull, then we will forward requests to this reporter. */ + private final A4Reporter parent; + + /** This is a pre-constructed instance that simply ignores all calls. */ + public static final A4Reporter NOP = new A4Reporter(); + + /** Constructs a default A4Reporter object that does nothing. */ + public A4Reporter() { + parent = null; + } + + /** Constructs an A4Reporter that forwards each method to the given A4Reporter. */ + public A4Reporter(A4Reporter reporter) { + parent = reporter; + } + + /** + * This method is called at various points to report the current progress; it is intended as a + * debugging aid for the developers; the messages are generally not useful for end users. + */ + public void debug(String msg) { + if (parent != null) + parent.debug(msg); + } + + /** This method is called by the parser to report parser events. */ + public void parse(String msg) { + if (parent != null) + parent.parse(msg); + } + + /** + * This method is called by the typechecker to report the type for each + * field/function/predicate/assertion, etc. + */ + public void typecheck(String msg) { + if (parent != null) + parent.typecheck(msg); + } + + /** This method is called by the typechecker to report a nonfatal type error. */ + public void warning(ErrorWarning msg) { + if (parent != null) + parent.warning(msg); + } + + /** This method is called by the ScopeComputer to report the scope chosen for each sig. */ + public void scope(String msg) { + if (parent != null) + parent.scope(msg); + } + + /** + * This method is called by the BoundsComputer to report the bounds chosen for each sig and each + * field. + */ + public void bound(String msg) { + if (parent != null) + parent.bound(msg); + } + + /** + * This method is called by the translator just before it begins generating CNF. + * + * @param solver - the solver chosen by the user (eg. SAT4J, MiniSat...) + * @param bitwidth - the integer bitwidth chosen by the user + * @param maxseq - the scope on seq/Int chosen by the user + * @param skolemDepth - the skolem function depth chosen by the user (0, 1, 2...) + * @param symmetry - the amount of symmetry breaking chosen by the user (0...) + */ + public void translate(String solver, int bitwidth, int maxseq, int skolemDepth, int symmetry) { + if (parent != null) + parent.translate(solver, bitwidth, maxseq, skolemDepth, symmetry); + } + + /** + * This method is called by the translator just after it generated the CNF. + * + * @param primaryVars - the total number of primary variables + * @param totalVars - the total number of variables including the number of primary variables + * @param clauses - the total number of clauses + */ + public void solve(int primaryVars, int totalVars, int clauses) { + if (parent != null) + parent.solve(primaryVars, totalVars, clauses); + } + + /** + * If solver==KK or solver==CNF, this method is called by the translator after it constructed the + * Kodkod or CNF file. + * + * @param filename - the Kodkod or CNF file generated by the translator + */ + public void resultCNF(String filename) { + if (parent != null) + parent.resultCNF(filename); + } + + /** + * If solver!=KK and solver!=CNF, this method is called by the translator if the formula is + * satisfiable. + * + * @param command - this is the original Command used to generate this solution + * @param solvingTime - this is the number of milliseconds the solver took to obtain this result + * @param solution - the satisfying A4Solution object + */ + public void resultSAT(Object command, long solvingTime, Object solution) { + if (parent != null) + parent.resultSAT(command, solvingTime, solution); + } + + /** + * If solver!=KK and solver!=CNF, this method is called by the translator before starting the + * unsat core minimization. + * + * @param command - this is the original Command used to generate this solution + * @param before - the size of the unsat core before calling minimization + */ + public void minimizing(Object command, int before) { + if (parent != null) + parent.minimizing(command, before); + } + + /** + * If solver!=KK and solver!=CNF, this method is called by the translator after performing the + * unsat core minimization. + * + * @param command - this is the original Command used to generate this solution + * @param before - the size of the unsat core before calling minimization + * @param after - the size of the unsat core after calling minimization + */ + public void minimized(Object command, int before, int after) { + if (parent != null) + parent.minimized(command, before, after); + } + + /** + * If solver!=KK and solver!=CNF, this method is called by the translator if the formula is + * unsatisfiable. + * + * @param command - this is the original Command used to generate this solution + * @param solvingTime - this is the number of milliseconds the solver took to obtain this result + * @param solution - the unsatisfying A4Solution object + */ + public void resultUNSAT(Object command, long solvingTime, Object solution) { + if (parent != null) + parent.resultUNSAT(command, solvingTime, solution); + } + + /** + * This method is called by the A4SolutionWriter when it is writing a particular sig, field, or + * skolem. + */ + public void write(Object expr) { + if (parent != null) + parent.write(expr); + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/ByteBuffer.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/ByteBuffer.java new file mode 100644 index 00000000..265f9e19 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/ByteBuffer.java @@ -0,0 +1,130 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4; + +import java.io.IOException; +import java.io.RandomAccessFile; +import java.io.UnsupportedEncodingException; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.zip.Deflater; + +/** Mutable; implements a growable array of bytes. + * + *

This class is more efficient than Java's ByteArrayOutputStream when writing large amount of data, + * because ByteArrayOutputStream will resize and copy entire existing contents every time the array needs to grow, + * whereas this class maintains a linked list of arrays (so when capacity is expanded we don't need to copy old data) + */ + +public final class ByteBuffer { + + /** The size per chunk. */ + private static final int SIZE = 65536; + + /** The list of chunks allocated so far; always has at least one chunk; every chunk is always exactly of size SIZE. */ + private final LinkedList list = new LinkedList(); + + /** The number of bytes stored in the latest chunk; every chunk before that is always fully filled. */ + private int n = 0; + + /** Construct an empty byte buffer. */ + public ByteBuffer() { list.add(new byte[SIZE]); } + + /** Write the given byte into this byte buffer. */ + private ByteBuffer w(int b) { + if (n==SIZE) { list.add(new byte[SIZE]); n=0; } + byte[] array = list.getLast(); + array[n] = (byte)b; + n++; + return this; + } + + /** Write the given array of bytes into this byte buffer. */ + private ByteBuffer write(byte[] b, int offset, int len) { + if (b==null || len<=0) return this; else if (n==SIZE) { list.add(new byte[SIZE]); n=0; } + while(true) { // loop invariant: len>0 and SIZE>n + byte[] array = list.getLast(); + if (len <= (SIZE-n)) { System.arraycopy(b, offset, array, n, len); n += len; return this; } + System.arraycopy(b, offset, array, n, SIZE-n); + offset += (SIZE-n); + len -= (SIZE-n); + n = 0; + list.add(new byte[SIZE]); + } + } + + /** Write the given String into this byte buffer (by converting the String into its UTF-8 representation) */ + public ByteBuffer write(String string) { + if (string.length() == 0) return this; + byte[] b; + try { b = string.getBytes("UTF-8"); } catch(UnsupportedEncodingException ex) { return this; } // exception not possible + return write(b, 0, b.length); + } + + /** Write the given number into this byte buffer, followed by a space. */ + public ByteBuffer writes(long x) { + return write(Long.toString(x)).w(' '); + } + + /** Write the given number into this byte buffer (truncated to the range -32767..+32767), followed by a space. */ + public strictfp ByteBuffer writes(double x) { + // These extreme values shouldn't happen, but we want to protect against them + if (Double.isNaN(x)) return write("0 "); else if (x>32767) return write("32767 "); else if (x<-32767) return write("-32767 "); + long num = (long)(x * 1000000); + if (num>=32767000000L) return write("32767 "); else if (num<=(-32767000000L)) return write("-32767 "); + // Now, regular doubles... let's allow up to 6 digits after the decimal point + if (num<0) { w('-'); num = -num; } + String str = Long.toString(num); + int len = str.length(); + if (len<=6) { + w('.'); + while(len<6) { w('0'); len++; } + return write(str).w(' '); + } + return write(str.substring(0, str.length()-6)).w('.').write(str.substring(str.length()-6)).w(' '); + } + + /** Write the entire content into the given file using Flate compression (see RFC1951) then return the number of bytes written. */ + public long dumpFlate(RandomAccessFile os) throws IOException { + Deflater zip = new Deflater(Deflater.BEST_COMPRESSION); + byte[] output = new byte[8192]; + Iterator it = list.iterator(); // when null, that means we have told the Deflater that no more input would be coming + long ans = 0; // the number of bytes written out so far + while(true) { + if (it!=null && zip.needsInput() && it.hasNext()) { + byte[] in = it.next(); + if (in == list.getLast()) { zip.setInput(in, 0, n); it=null; zip.finish(); } else { zip.setInput(in, 0, SIZE); } + } + if (it==null && zip.finished()) break; + int count = zip.deflate(output); + if (count > 0) { + ans = ans + count; + if (ans < 0) throw new IOException("Data too large to be written to the output file."); + os.write(output, 0, count); + } + } + return ans; + } + + /** Write the entire content into the given file as-is, then return the number of bytes written. */ + public long dump(RandomAccessFile os) throws IOException { + if (list.size() >= (Long.MAX_VALUE / SIZE)) throw new IOException("Data too large to be written to the output file."); + byte[] last = list.getLast(); + for(byte[] x: list) if (x!=last) os.write(x); + if (n>0) os.write(last, 0, n); + return ((long)(list.size()-1)) * SIZE + n; + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/Computer.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/Computer.java new file mode 100644 index 00000000..3e490f45 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/Computer.java @@ -0,0 +1,26 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4; + +/** This defines a compute() method that takes an Object input and produces a String output. */ + +public interface Computer { + + /** This method takes an Object input and produces a String output. + * @throws Exception if an error occurred during the computation. + */ + public String compute (Object input) throws Exception; +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/ConstList.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/ConstList.java new file mode 100644 index 00000000..ffdfcae7 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/ConstList.java @@ -0,0 +1,154 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4; + +import java.io.Serializable; +import java.util.AbstractList; +import java.util.Collection; +import java.util.List; +import java.util.ArrayList; +import java.util.RandomAccess; + +/** Immutable; implements a list based on equals(); null values are allowed. + * + * @param - the type of element + */ + +public final class ConstList extends AbstractList implements Serializable, RandomAccess { + + /** Mutable; this implements a modifiable list that can be used to construct a ConstList; null values are allowed. + * + * @param - the type of element + */ + public static final class TempList { + + /** The underlying list. */ + private final ArrayList list; + + /** Nonnull iff this list is no longer modifiable. */ + private ConstList clist; + + /** Construct an empty TempList. */ + public TempList() { list = new ArrayList(); } + + /** Construct an empty TempList with initial capacity of n (if n<=0, the list will be given a default capacity of 0) */ + public TempList(int n) { list = new ArrayList(n>=0 ? n : 0); } + + /** Construct a new TempList whose initial content is n references to the given elem (if n<=0, the created list is empty) */ + public TempList(int n, T elem) { list = new ArrayList(n>0 ? n : 0); while(n>0) { list.add(elem); n--; } } + + /** Construct a new TempList whose initial content is equal to the given collection. */ + public TempList(Collection all) { list = new ArrayList(all); } + + /** Construct a new TempList whose initial content is equal to the given array. */ + public TempList(T... all) { list = new ArrayList(all.length); for(int i=0; i clear() { chk(); list.clear(); return this; } + + /** Appends the given element to the list, then return itself. */ + public TempList add(T elem) { chk(); list.add(elem); return this; } + + /** Appends the elements in the given collection to the list, then return itself. */ + public TempList addAll(Iterable all) { + chk(); + if (all instanceof Collection) list.addAll((Collection)all); else if (all!=null) { for(T x: all) list.add(x); } + return this; + } + + /** Changes the i-th element to be the given element, then return itself. */ + public TempList set(int index, T elem) { chk(); list.set(index, elem); return this; } + + /** Makes this TempList unmodifiable, then construct a ConstList backed by this TempList. */ + @SuppressWarnings("unchecked") + public ConstList makeConst() { if (clist==null) clist=(list.isEmpty() ? emptylist : new ConstList(list)); return clist; } + } + + /** This ensures this class can be serialized reliably. */ + private static final long serialVersionUID = 0; + + /** The underlying unmodifiable list. */ + private final List list; + + /** This caches an unmodifiable empty list. */ + @SuppressWarnings("unchecked") + private static final ConstList emptylist = new ConstList(new ArrayList(0)); + + /** Construct a ConstList with the given list as its backing store. */ + private ConstList(List list) { + this.list = list; + } + + /** Return an unmodifiable empty list. */ + @SuppressWarnings("unchecked") + public static ConstList make() { + return (ConstList) emptylist; + } + + /** Return an unmodifiable list consisting of "n" references to "elem". + * (If n<=0, we'll return an unmodifiable empty list) + */ + public static ConstList make(int n, T elem) { + if (n <= 0) return make(); + ArrayList ans = new ArrayList(n); + while(n > 0) { ans.add(elem); n--; } + return new ConstList(ans); + } + + /** Return an unmodifiable list with the same elements as the given collection. + * (If collection==null, we'll return an unmodifiable empty list) + */ + public static ConstList make(Iterable collection) { + if (collection == null) return make(); + if (collection instanceof ConstList) return (ConstList) collection; + if (collection instanceof Collection) { + Collection col = (Collection)collection; + if (col.isEmpty()) return make(); else return new ConstList(new ArrayList(col)); + } + ArrayList ans = null; + for(T x: collection) { + if (ans == null) ans = new ArrayList(); + ans.add(x); + } + if (ans==null) return make(); else return new ConstList(ans); + } + + /** Returns the i-th element + * @throws ArrayIndexOutOfBoundsException if the given index doesn't exist + */ + @Override public T get(int index) { return list.get(index); } + + /** Returns the number of elements in this list. */ + @Override public int size() { return list.size(); } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/ConstMap.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/ConstMap.java new file mode 100644 index 00000000..601922da --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/ConstMap.java @@ -0,0 +1,83 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4; + +import java.util.Collection; +import java.util.Collections; +import java.util.Set; +import java.util.AbstractMap; +import java.util.Map; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.io.Serializable; + +/** Immutable; implements a map based on hashCode() and equals(); null key and values are allowed. + * + * @param - the type of key + * @param - the type of value + */ + +public final class ConstMap extends AbstractMap implements Serializable { + + /** This ensures this class can be serialized reliably. */ + private static final long serialVersionUID = 0; + + /** The underlying Collections.unmodifiableMap map. */ + private final Map map; + + /** This caches a read-only empty map. */ + private static final ConstMap emptymap = new ConstMap(new HashMap(0)); + + /** Constructs an unmodifiable map with the given map as the backing store. */ + private ConstMap(Map map) { + this.map = Collections.unmodifiableMap(map); + } + + /** Returns an unmodifiable empty map. */ + @SuppressWarnings("unchecked") + public static ConstMap make() { + return (ConstMap) emptymap; + } + + /** Returns an unmodifiable map with the same entries and traversal order as the given map. + * (If map==null, we'll return an unmodifiable empty map) + */ + public static ConstMap make(Map map) { + if (map instanceof ConstMap) return (ConstMap)map; + if (map == null || map.isEmpty()) return make(); else return new ConstMap(new LinkedHashMap(map)); + } + + /** Returns an unmodifiable view of the mappings in this map. */ + @Override public Set> entrySet() { return map.entrySet(); } + + /** Returns an unmodifiable view of the keys in this map. */ + @Override public Set keySet() { return map.keySet(); } // overridden for performance + + /** Returns an unmodifiable view of the values in this map. */ + @Override public Collection values() { return map.values(); } // overridden for performance + + /** Returns the number of (key, value) mapping in this map. */ + @Override public int size() { return map.size(); } // overridden for performance + + /** Returns true if exists at least one (k, v) mapping where (k==null ? key==null : k.equals(key)) */ + @Override public boolean containsKey(Object key) { return map.containsKey(key); } // overridden for performance + + /** Returns true if exists at least one (k, v) mapping where (v==null ? value==null : v.equals(value)) */ + @Override public boolean containsValue(Object value) { return map.containsValue(value); } // overridden for performance + + /** Returns the value associated with the key (or null if not found); null is also returned if the given key maps to null. */ + @Override public V get(Object key) { return map.get(key); } // overridden for performance +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/ConstSet.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/ConstSet.java new file mode 100644 index 00000000..f7d79121 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/ConstSet.java @@ -0,0 +1,74 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4; + +import java.util.AbstractSet; +import java.util.Collections; +import java.util.Set; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.Iterator; +import java.io.Serializable; + +/** Immutable; implements a set based on hashCode() and equals(); null value is allowed. + * + * @param - the type of element + */ + +public final class ConstSet extends AbstractSet implements Serializable { + + /** This ensures this class can be serialized reliably. */ + private static final long serialVersionUID = 0; + + /** The underlying Collections.unmodifiableSet set. */ + private final Set set; + + /** This caches a readonly empty Set. */ + private static final ConstSet emptyset = new ConstSet(new HashSet(0)); + + /** Constructs an unmodifiable map with the given set as the backing store. */ + private ConstSet(Set set) { + this.set = Collections.unmodifiableSet(set); + } + + /** Returns an unmodifiable empty set. */ + @SuppressWarnings("unchecked") + public static ConstSet make() { + return (ConstSet) emptyset; + } + + /** Returns an unmodifiable set with the same elements and traversal order as the given set. + * (If set==null, we'll return an unmodifiable empty set) + */ + public static ConstSet make(Iterable collection) { + if (collection instanceof ConstSet) return (ConstSet)collection; + LinkedHashSet ans = null; + if (collection != null) for(K element: collection) { + if (ans == null) ans = new LinkedHashSet(); + ans.add(element); + } + if (ans==null) return make(); else return new ConstSet(ans); + } + + /** Returns the number of objects in this set. */ + @Override public int size() { return set.size(); } + + /** Returns a read-only iterator over this set. */ + @Override public Iterator iterator() { return set.iterator(); } + + /** Returns true if the given object is in this set. */ + @Override public boolean contains(Object element) { return set.contains(element); } // overridden for performance +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/DirectedGraph.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/DirectedGraph.java new file mode 100644 index 00000000..319e3278 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/DirectedGraph.java @@ -0,0 +1,80 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4; + +import java.util.List; +import java.util.ArrayList; +import java.util.Map; +import java.util.IdentityHashMap; + +/** Mutable; implements a directed graph; null node is allowed. + * + *

Note: it uses n1==n2 for comparing nodes rather than using n1.equals(n2) + * + * @param - the type of node + */ + +public final class DirectedGraph { + + /** This substitutes for null nodes. This allows hasPath() method to use put() to both insert and test membership in one step. */ + private static final Object NULL = new Object(); + + /** This field maps each node X to a list of "neighbor nodes" that X can reach by following directed edges zero or more times. */ + private final Map> nodeToTargets = new IdentityHashMap>(); + + /** Constructs an empty graph. */ + public DirectedGraph () { } + + /** Add a directed edge from start node to end node (if there wasn't such an edge already). */ + public void addEdge (N start, N end) { + if (start == end) return; + Object a = (start==null ? NULL : start); + Object b = (end==null ? NULL : end); + List targets = nodeToTargets.get(a); + if (targets == null) { + targets = new ArrayList(); + targets.add(b); + nodeToTargets.put(a, targets); + } else { + for (int i = targets.size()-1; i >= 0; i--) if (targets.get(i) == b) return; + targets.add(b); + } + } + + /** Returns whether there is a directed path from start node to end node by following directed edges 0 or more times (breath-first). */ + public boolean hasPath (N start, N end) { + if (start == end) return true; + Object a = (start==null ? NULL : start); + Object b = (end==null ? NULL : end); + List todo = new ArrayList(); + Map visited = new IdentityHashMap(); + // The correctness and guaranteed termination relies on following three invariants: + // (1) Every time we add X to "visited", we also simultaneously add X to "todo". + // (2) Every time we add X to "todo", we also simultaneously add X to "visited". + // (3) Nothing is ever removed. + visited.put(a, a); + todo.add(a); + for(int k = 0; k < todo.size(); k++) { // use an integer loop since we will be adding to the "todo" list as we iterate + List targets = nodeToTargets.get(todo.get(k)); + if (targets != null) for (int i = targets.size()-1; i >= 0; i--) { + Object next = targets.get(i); + if (next == b) { addEdge(start, end); return true; } // Cache so that later hasPath(start,end) returns true immediately + if (visited.put(next, next) == null) todo.add(next); + } + } + return false; + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/Env.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/Env.java new file mode 100644 index 00000000..be5e1643 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/Env.java @@ -0,0 +1,106 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4; + +import java.util.LinkedList; +import java.util.Map; +import java.util.LinkedHashMap; + +/** Mutable; implements a undoable map based on hashCode() and equals(); null key and values are allowed. + * + *

To be more precise, every key is internally mapped to a list of values. + *
The put(X,Y) method appends Y onto the end of X's list. + *
The get(X) method returns the last element in X's list. + *
The remove(X) method removes the last element in X's list. + * + *

This is very useful for representing lexical scoping: when a local + * variable is introduced with the same name as an existing variable, + * the new variable "hides" the old mapping; and when the new variable falls + * out of scope, the previous mapping is once again "revealed". + * + * @param - the type for Value + */ + +public final class Env { + + /** If a key is bound to one or more values, this stores the first value. + *

+ * For example: if key K is bound to list of values V1,V2,V3...Vn, then map1.get(K) returns V1 + *

+ * Invariant: map2.containsKey(x) implies (map1.containsKey(x) && map2.get(x).size()>0) + */ + private final Map map1 = new LinkedHashMap(); + + /** If a key is bound to more than one value, this stores every value except the first value. + *

+ * For example: if key K is bound to list of values V1,V2,V3...Vn, then map2.get(K) returns the sublist V2..Vn + *

+ * Invariant: map2.containsKey(x) implies (map1.containsKey(x) && map2.get(x).size()>0) + */ + private final Map> map2 = new LinkedHashMap>(); + + /** Constructs an initially empty environment. */ + public Env () { } + + /** Returns true if the key is mapped to one or more values. */ + public boolean has (K key) { return map1.containsKey(key); } + + /** Returns the latest value associated with the key (and returns null if none). + * + *

Since null is also a possible value, if you get null as the answer, + * you need to call has(key) to determine whether the key really has a mapping or not. + */ + public V get (K key) { + LinkedList list = map2.get(key); + return (list != null) ? list.getLast() : map1.get(key); + } + + /** Associates the key with the value (which can be null). */ + public void put (K key, V value) { + LinkedList list = map2.get(key); + if (list != null) { + list.add(value); + } else if (!map1.containsKey(key)) { + map1.put(key, value); + } else { + list = new LinkedList(); + list.add(value); + map2.put(key, list); + } + } + + /** Removes the latest mapping for the key (and if the key had previous mappings, they become visible). + * If there are no mappings for the key, then this method does nothing. + */ + public void remove (K key) { + LinkedList list = map2.get(key); + if (list == null) map1.remove(key); else if (list.size() == 1) map2.remove(key); else list.removeLast(); + } + + /** Removes all mappings. */ + public void clear() { + map1.clear(); + map2.clear(); + } + + /** Make a shallow copy of this environment. */ + public Env dup() { + Env ans = new Env(); + ans.map1.putAll(map1); + for(Map.Entry> e: map2.entrySet()) ans.map2.put(e.getKey(), new LinkedList(e.getValue())); + return ans; + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/Err.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/Err.java new file mode 100644 index 00000000..af8f7c53 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/Err.java @@ -0,0 +1,56 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4; + +/** Immutable; this is the abstract parent class of the various possible errors. */ + +public abstract class Err extends Exception { + + /** This ensures this class can be serialized reliably. */ + private static final long serialVersionUID = 0; + + /** This stores the filename/line/column information (Pos.UNKNOWN if unknown) (never null) */ + public final Pos pos; + + /** The actual error message (never null) */ + public final String msg; + + /** Constructs a new Err object. + * @param pos - the filename/line/row information (can be null if unknown) + * @param msg - the actual error message (can be null) + * @param cause - if nonnull, it will be recorded as the cause of this exception + */ + Err(Pos pos, String msg, Throwable cause) { + super((msg==null ? "" : msg), cause); + this.pos = (pos==null ? Pos.UNKNOWN : pos); + this.msg = (msg==null ? "" : msg); + } + + /** Two Err objects are equal if the type, position, and message are the same. */ + @Override public final boolean equals(Object other) { + if (this==other) return true; else if (other==null || getClass()!=other.getClass()) return false; + Err that = (Err) other; + return pos.equals(that.pos) && msg.equals(that.msg); + } + + /** Returns a hash code consistent with equals() */ + @Override public final int hashCode() { + return msg.hashCode(); + } + + /** Returns this exception type, its error message, and its complete stack trace as a String. */ + public final String dump() { return MailBug.dump(this); } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/ErrorAPI.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/ErrorAPI.java new file mode 100644 index 00000000..01e62c05 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/ErrorAPI.java @@ -0,0 +1,48 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4; + +/** Immutable; this represents an API usage error. */ + +public final class ErrorAPI extends Err { + + /** This ensures this class can be serialized reliably. */ + private static final long serialVersionUID = 0; + + /** Constructs a new API usage error. + * @param msg - the actual error message (can be null) + */ + public ErrorAPI(String msg) { super(null, msg, null); } + + /** Constructs a new API usage error with "cause" as the underlying cause. + * @param msg - the actual error message (can be null) + * @param cause - if nonnull, it is the cause of this exception + */ + public ErrorAPI(String msg, Throwable cause) { super(null, msg, cause); } + + /** Constructs a new API usage error. + * @param pos - the filename/line/row information (can be null if unknown) + * @param msg - the actual error message (can be null) + */ + public ErrorAPI(Pos pos, String msg) { super(pos, msg, null); } + + /** Returns a textual description of the error. */ + @Override public String toString() { + if (pos==Pos.UNKNOWN) return "API usage error:\n"+msg; + if (pos.filename.length()>0) return "API usage error in "+pos.filename+" at line "+pos.y+" column "+pos.x+":\n"+msg; + return "API usage error at line " + pos.y + " column " + pos.x + ":\n" + msg; + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/ErrorFatal.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/ErrorFatal.java new file mode 100644 index 00000000..812b4da3 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/ErrorFatal.java @@ -0,0 +1,48 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4; + +/** Immutable; this represents a fatal error. */ + +public final class ErrorFatal extends Err { + + /** This ensures this class can be serialized reliably. */ + private static final long serialVersionUID = 0; + + /** Constructs a new fatal error. + * @param msg - the actual error message (can be null) + */ + public ErrorFatal(String msg) { super(null, msg, null); } + + /** Constructs a new fatal error with "cause" as the underlying cause. + * @param msg - the actual error message (can be null) + * @param cause - if nonnull, it is the cause of this exception + */ + public ErrorFatal(String msg, Throwable cause) { super(null, msg, cause); } + + /** Constructs a new fatal error. + * @param pos - the filename/line/row information (can be null if unknown) + * @param msg - the actual error message (can be null) + */ + public ErrorFatal(Pos pos, String msg) { super(pos, msg, null); } + + /** Returns a textual description of the error. */ + @Override public String toString() { + if (pos==Pos.UNKNOWN) return "Fatal error:\n"+msg; + if (pos.filename.length()>0) return "Fatal error in "+pos.filename+" at line "+pos.y+" column "+pos.x+":\n"+msg; + return "Fatal error at line " + pos.y + " column " + pos.x + ":\n" + msg; + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/ErrorSyntax.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/ErrorSyntax.java new file mode 100644 index 00000000..11497995 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/ErrorSyntax.java @@ -0,0 +1,48 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4; + +/** Immutable; this represents a syntax error that should be reported to the user. */ + +public final class ErrorSyntax extends Err { + + /** This ensures this class can be serialized reliably. */ + private static final long serialVersionUID = 0; + + /** Constructs a new syntax error. + * @param msg - the actual error message (can be null) + */ + public ErrorSyntax(String msg) { super(null, msg, null); } + + /** Constructs a new syntax error with "cause" as the underlying cause. + * @param msg - the actual error message (can be null) + * @param cause - if nonnull, it is the cause of this exception + */ + public ErrorSyntax(String msg, Throwable cause) { super(null, msg, cause); } + + /** Constructs a new syntax error. + * @param pos - the filename/line/row information (can be null if unknown) + * @param msg - the actual error message (can be null) + */ + public ErrorSyntax(Pos pos, String msg) { super(pos, msg, null); } + + /** Returns a textual description of the error. */ + @Override public String toString() { + if (pos==Pos.UNKNOWN) return "Syntax error:\n"+msg; + if (pos.filename.length()>0) return "Syntax error in "+pos.filename+" at line "+pos.y+" column "+pos.x+":\n"+msg; + return "Syntax error at line " + pos.y + " column " + pos.x + ":\n" + msg; + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/ErrorType.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/ErrorType.java new file mode 100644 index 00000000..518a720d --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/ErrorType.java @@ -0,0 +1,48 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4; + +/** Immutable; this represents a type error that should be reported to the user. */ + +public final class ErrorType extends Err { + + /** This ensures this class can be serialized reliably. */ + private static final long serialVersionUID = 0; + + /** Constructs a new type error. + * @param msg - the actual error message (can be null) + */ + public ErrorType(String msg) { super(null, msg, null); } + + /** Constructs a new type error with "cause" as the underlying cause. + * @param msg - the actual error message (can be null) + * @param cause - if nonnull, it is the cause of this exception + */ + public ErrorType(String msg, Throwable cause) { super(null, msg, cause); } + + /** Constructs a new type error. + * @param pos - the filename/line/row information (can be null if unknown) + * @param msg - the actual error message (can be null) + */ + public ErrorType(Pos pos, String msg) { super(pos, msg, null); } + + /** Returns a textual description of the error. */ + @Override public String toString() { + if (pos==Pos.UNKNOWN) return "Type error:\n"+msg; + if (pos.filename.length()>0) return "Type error in "+pos.filename+" at line "+pos.y+" column "+pos.x+":\n"+msg; + return "Type error at line " + pos.y + " column " + pos.x + ":\n" + msg; + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/ErrorWarning.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/ErrorWarning.java new file mode 100644 index 00000000..959b8314 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/ErrorWarning.java @@ -0,0 +1,48 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4; + +/** Immutable; this represents a nonfatal warning that should be reported to the user. */ + +public final class ErrorWarning extends Err { + + /** This ensures this class can be serialized reliably. */ + private static final long serialVersionUID = 0; + + /** Constructs a new warning. + * @param msg - the actual error message (can be null) + */ + public ErrorWarning(String msg) { super(null, msg, null); } + + /** Constructs a new warning with "cause" as the underlying cause. + * @param msg - the actual error message (can be null) + * @param cause - if nonnull, it is the cause of this exception + */ + public ErrorWarning(String msg, Throwable cause) { super(null, msg, cause); } + + /** Constructs a new warning. + * @param pos - the filename/line/row information (can be null if unknown) + * @param msg - the actual error message (can be null) + */ + public ErrorWarning(Pos pos, String msg) { super(pos, msg, null); } + + /** Returns a textual description of the error. */ + @Override public String toString() { + if (pos==Pos.UNKNOWN) return msg; + if (pos.filename.length()>0) return "Line "+pos.y+" column "+pos.x+" in "+pos.filename+":\n"+msg; + return "Line " + pos.y + " column " + pos.x + ":\n" + msg; + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/JoinableList.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/JoinableList.java new file mode 100644 index 00000000..e7bf4b42 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/JoinableList.java @@ -0,0 +1,92 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4; + +import java.io.Serializable; +import java.util.AbstractList; + +/** Immutable; implements a list where it is combine them; null values are NOT allowed. */ + +public final class JoinableList extends AbstractList implements Serializable { + + /** This ensures the class can be serialized reliably. */ + private static final long serialVersionUID = 0; + + /** The number of items stored in this list. + *

Invariant: count == (pre!=null ? pre.count : 0) + (item!=null ? 1 : 0) + (post!=null ? post.count : 0) + */ + private final int count; + + /** The list of items before "this.item"; may be null. */ + private final JoinableList pre; + + /** The list of items after "this.item"; may be null. */ + private final JoinableList post; + + /** If nonnull, it stores an item. */ + private final E item; + + /** Construct a JoinableList object. */ + private JoinableList(int count, JoinableList pre, E item, JoinableList post) { + this.count = count; + this.pre = pre; + this.item = item; + this.post = post; + } + + /** Construct an empty list. */ + public JoinableList() { this(0, null, null, null); } + + /** Construct a list containing a single item, or return an empty list if item==null. */ + public JoinableList(E item) { this((item!=null ? 1 : 0), null, item, null); } + + /** Returns a list that represents the concatenation of this list and that list. */ + public JoinableList make(JoinableList that) { + if (that == null || that.count == 0) return this; else if (count == 0) return that; + int sum = count + that.count; + if (sum < count) throw new OutOfMemoryError(); // integer overflow + if (post != null) return new JoinableList(sum, this, null, that); else return new JoinableList(sum, pre, item, that); + } + + /** Returns a list that represents the result of appending newItem onto this list; if newItem==null we return this list as-is. */ + public JoinableList make(E newItem) { + if (newItem == null) return this; + int sum = count + 1; // integer overflow + if (sum < 1) throw new OutOfMemoryError(); + if (post != null) return new JoinableList(sum, this, newItem, null); + if (item != null) return new JoinableList(sum, pre, item, new JoinableList(newItem)); + return new JoinableList(sum, pre, newItem, null); + } + + /** If the list if nonempty, arbitrarily return one of the item, otherwise throw ArrayIndexOutOfBoundsException. */ + public E pick() { if (item!=null) return item; else return get(0); } + + /** Return the i-th element + * @throws ArrayIndexOutOfBoundsException if the given index doesn't exist + */ + @Override public E get(int i) { + if (i < 0 || i >= count) throw new ArrayIndexOutOfBoundsException(); + JoinableList x = this; + while(true) { + int pre = (x.pre == null) ? 0 : x.pre.count; + if (i < pre) { x = x.pre; continue; } + if (x.item == null) { i = i - pre; x = x.post; } else if (i != pre) { i = i - pre - 1; x = x.post; } else return x.item; + } + } + + /** Returns the number of elements in this list. */ + @Override public int size() { return count; } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/Listener.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/Listener.java new file mode 100644 index 00000000..b992f1de --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/Listener.java @@ -0,0 +1,30 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4; + +/** This defines an interface for receiving events. */ + +public interface Listener { + + /** This defines the list of possible events. */ + enum Event { CLICK, STATUS_CHANGE, FOCUSED, CTRL_PAGE_UP, CTRL_PAGE_DOWN, CARET_MOVED}; + + /** This method is called when the given zero-argument-event occurs. */ + public Object do_action(Object sender, Event event); + + /** This method is called when the given single-argument-event occurs. */ + public Object do_action(Object sender, Event event, Object arg); +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/Listeners.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/Listeners.java new file mode 100644 index 00000000..2f559505 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/Listeners.java @@ -0,0 +1,46 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4; + +import java.util.ArrayList; +import edu.mit.csail.sdg.alloy4.Listener.Event; + +/** This stores a list of listeners. */ + +public final class Listeners { + + /** The actual list of listeners. */ + private final ArrayList listeners = new ArrayList(); + + /** Construct a empty list of listeners. */ + public Listeners() { } + + /** Add a listener to this group of listeners (if not already in the list) */ + public void add(Listener listener) { + for(Listener x: listeners) if (x == listener) return; + listeners.add(listener); + } + + /** Send the following zero-argument event to every listener. */ + public void fire(Object sender, Event event) { + for(Listener x: listeners) x.do_action(sender, event); + } + + /** Send the following one-argument event to every listener. */ + public void fire(Object sender, Event event, Object arg) { + for(Listener x: listeners) x.do_action(sender, event, arg); + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/MacUtil.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/MacUtil.java new file mode 100644 index 00000000..fa9bc391 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/MacUtil.java @@ -0,0 +1,77 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4; + +import javax.swing.SwingUtilities; +import com.apple.eawt.Application; +import com.apple.eawt.ApplicationAdapter; +import com.apple.eawt.ApplicationEvent; +import com.apple.eawt.ApplicationListener; + +/** This class provides better integration on Mac OS X. + * + *

You must not call any methods here if you're not on Mac OS X, + * since that triggers the loading of com.apple.eawt.* which are not available on other platforms. + * + *

Thread Safety: Safe. + */ + +public final class MacUtil { + + /** Constructor is private, since this class never needs to be instantiated. */ + private MacUtil() { } + + /** The cached Application object. */ + private static Application app = null; + + /** The previous ApplicationListener (or null if there was none). */ + private static ApplicationListener listener = null; + + /** Register a Mac OS X "ApplicationListener"; if there was a previous listener, it will be removed first. + * @param reopen - when the user clicks on the Dock icon, we'll call reopen.run() using SwingUtilities.invokeLater + * @param about - when the user clicks on About Alloy4, we'll call about.run() using SwingUtilities.invokeLater + * @param open - when a file needs to be opened, we'll call open.run(filename) using SwingUtilities.invokeLater + * @param quit - when the user clicks on Quit, we'll call quit.run() using SwingUtilities.invokeAndWait + */ + public synchronized static void registerApplicationListener + (final Runnable reopen, final Runnable about, final Runner open, final Runnable quit) { + if (app == null) app = new Application(); else if (listener != null) app.removeApplicationListener(listener); + listener = new ApplicationAdapter() { + @Override public void handleReOpenApplication(ApplicationEvent arg) { + SwingUtilities.invokeLater(reopen); + } + @Override public void handleAbout(ApplicationEvent arg) { + arg.setHandled(true); + SwingUtilities.invokeLater(about); + } + @Override public void handleOpenFile(ApplicationEvent arg) { + final String filename = arg.getFilename(); + SwingUtilities.invokeLater(new Runnable() { + public void run() { open.run(filename); } + }); + } + @Override public void handleQuit(ApplicationEvent arg) { + try { + if (SwingUtilities.isEventDispatchThread()) quit.run(); else SwingUtilities.invokeAndWait(quit); + } catch (Throwable e) { + // Nothing we can do; we're already trying to quit! + } + arg.setHandled(false); + } + }; + app.addApplicationListener(listener); + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/MailBug.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/MailBug.java new file mode 100644 index 00000000..334e56f4 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/MailBug.java @@ -0,0 +1,276 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4; + +import java.lang.Thread.UncaughtExceptionHandler; +import java.util.Map; +import java.util.prefs.BackingStoreException; +import java.util.prefs.Preferences; +import java.io.BufferedInputStream; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.net.URL; +import java.net.URLConnection; +import java.awt.Color; +import java.awt.Dimension; +import javax.swing.JScrollPane; +import javax.swing.JTextArea; +import javax.swing.JTextField; +import javax.swing.SwingUtilities; +import javax.swing.border.EmptyBorder; +import javax.swing.border.LineBorder; + +/** This class asks the user for permission to email a bug report when an uncaught exception occurs. */ + +public final class MailBug implements UncaughtExceptionHandler, Runnable { + + /** The version number of the most recent Alloy4 (as queried from alloy.mit.edu); -1 if alloy.mit.edu has not replied yet. */ + private static int latestAlloyVersion = -1; + + /** The name of the most recent Alloy4 (as queried from alloy.mit.edu); "unknown" if alloy.mit.edu has not replied yet. */ + private static String latestAlloyVersionName = "unknown"; + + /** The URL where the bug report should be sent. */ + private static final String ALLOY_URL = "http://alloy.mit.edu/postbug4.php"; + + /** The URL where the current version info can be queried. */ + private static final String ALLOY_NOW = "http://alloy.mit.edu/alloy4/download/alloy4.txt"; + + /** If alloy.mit.edu has replied, then return the latest Alloy build number, else return -1. */ + public static int latestBuildNumber() { synchronized(MailBug.class) { return latestAlloyVersion; } } + + /** If alloy.mit.edu has replied, then return the latest Alloy build name, else return "unknown" */ + public static String latestBuildName() { synchronized(MailBug.class) { return latestAlloyVersionName; } } + + /** Construct a new MailBug object. */ + private MailBug() { } + + /** Setup the uncaught-exception-handler and use a separate thread to query alloy.mit.edu for latest version number. */ + public static void setup() { + if (Thread.getDefaultUncaughtExceptionHandler() != null) return; + MailBug x = new MailBug(); + Thread.setDefaultUncaughtExceptionHandler(x); + new Thread(x).start(); + } + + /** This method concatenates a Throwable's message and stack trace and all its causes into a single String. */ + public static String dump (Throwable ex) { + StringBuilder sb = new StringBuilder(); + while(ex != null) { + sb.append(ex.getClass()).append(": ").append(ex.getMessage()).append('\n'); + StackTraceElement[] trace = ex.getStackTrace(); + if (trace != null) for(int n = trace.length, i = 0; i < n; i++) sb.append(trace[i]).append('\n'); + ex = ex.getCause(); + if (ex != null) sb.append("caused by...\n"); + } + return sb.toString().trim(); + } + + /** This method returns true if the exception appears to be a Sun Java GUI bug. */ + private static boolean isGUI(Throwable ex) { + // If the root of the stack trace is within Java framework itself, + // and no where is Alloy, Kodkod, or SAT4J anywhere along the trace, + // then it's almost *always* a Sun Java GUI bug from what we've seen. + // And it's better to ignore it rather than kill the file that the user is editing. + while(ex != null) { + StackTraceElement[] trace = ex.getStackTrace(); + for(int n=(trace==null ? 0 : trace.length), i=0; i 0) { + connection.setDoOutput(true); + out = connection.getOutputStream(); + out.write(send.getBytes("UTF-8")); + out.close(); + out = null; + } + in = connection.getInputStream(); + bis = new BufferedInputStream(in); + StringBuilder sb = new StringBuilder(); + int i; + while((i = bis.read()) >= 0) { sb.append((char)(i<=0x7F ? i : '?')); } + ans = Util.convertLineBreak(sb.toString()); + } catch (Throwable ex) { + ans = failure; + } finally { + Util.close(bis); + Util.close(in); + Util.close(out); + } + return ans; + } + + /** This method will query alloy.mit.edu for the latest version number. */ + public void run() { + String result = readAll(ALLOY_NOW + "?buildnum=" + Version.buildNumber() + "&builddate=" + Version.buildDate(), "", ""); + if (!result.startsWith("Alloy Build ")) return; + // Now that we know we're online, try to remove the old ill-conceived "Java WebStart" versions of Alloy4 BETA1..BETA7 + //Subprocess.exec(20000, new String[]{ + // "javaws", "-silent", "-offline", "-uninstall", "http://alloy.mit.edu/alloy4/download/alloy4.jnlp"}); + // Now parse the result + int num = 0; + boolean found = false; + for(int i=0, len=result.length(); ; i++) { + if (i >= len) return; // malformed + char c = result.charAt(i); + if (!(c>='0' && c<='9')) { if (!found) continue; else { result = result.substring(i).trim(); break; } } + found = true; + num = num*10 + (c - '0'); + } + synchronized(MailBug.class) { latestAlloyVersionName = result; latestAlloyVersion = num; } + } + + /** This method sends the crash report then displays alloy.mit.edu's reply in a text window. */ + private static void sendCrashReport (Thread thread, Throwable ex, String email, String problem) { + try { + final String report = prepareCrashReport(thread, ex, email, problem); + final String alt = "Sorry. An error has occurred in posting the bug report.\n" + + "Please email this report to alloy@mit.edu directly.\n\n" + dump(ex); + final JTextArea status = OurUtil.textarea("Sending the bug report... please wait...", + 10, 40, false, true, new LineBorder(Color.GRAY)); + new Thread(new Runnable() { + public void run() { + final String output = readAll(ALLOY_URL, report, alt); + SwingUtilities.invokeLater(new Runnable() { + public void run() { + status.setText(output); + status.setCaretPosition(0); + } + }); + } + }).start(); + OurDialog.showmsg("Sending the bug report... please wait...", status); + } finally { + System.exit(1); + } + } + + /** This method is an exception handler for uncaught exceptions. */ + public void uncaughtException (Thread thread, Throwable ex) { + if (isGUI(ex)) return; + final int ver; + final String name; + synchronized(MailBug.class) { ver = latestAlloyVersion; name = latestAlloyVersionName; } + if (ex!=null) { + System.out.flush(); + System.err.flush(); + System.err.println("Exception: " + ex.getClass()); + System.err.println("Message: " + ex); + System.err.println("Stacktrace:"); + System.err.println(dump(ex)); + System.err.flush(); + } + final String yes = "Send the Bug Report", no = "Don't Send the Bug Report"; + final JTextField email = OurUtil.textfield("", 20, new LineBorder(Color.DARK_GRAY)); + final JTextArea problem = OurUtil.textarea("", 50, 50, true, false, new EmptyBorder(0,0,0,0)); + final JScrollPane scroll = OurUtil.scrollpane(problem, new LineBorder(Color.DARK_GRAY), new Dimension(300, 200)); + for(Throwable ex2 = ex; ex2 != null; ex2 = ex2.getCause()) { + if (ex2 instanceof StackOverflowError) OurDialog.fatal(new Object[] { + "Sorry. The Alloy Analyzer has run out of stack space.", + " ", + "Try simplifying your model or reducing the scope.", + "And try reducing Options->SkolemDepth to 0.", + "And try increasing Options->Stack.", + " ", + "There is no way for Alloy to continue execution, so pressing OK will shut down Alloy." + }); + if (ex2 instanceof OutOfMemoryError) OurDialog.fatal(new Object[] { + "Sorry. The Alloy Analyzer has run out of memory.", + " ", + "Try simplifying your model or reducing the scope.", + "And try reducing Options->SkolemDepth to 0.", + "And try increasing Options->Memory.", + " ", + "There is no way for Alloy to continue execution, so pressing OK will shut down Alloy." + }); + } + if (ver > Version.buildNumber()) OurDialog.fatal(new Object[] { + "Sorry. A fatal error has occurred.", + " ", + "You are running Alloy Analyzer " + Version.version(), + "but the most recent is Alloy Analyzer "+ name, + " ", + "Please try to upgrade to the newest version", + "as the problem may have already been fixed.", + " ", + "There is no way for Alloy to continue execution, so pressing OK will shut down Alloy." + }); + if (OurDialog.yesno(new Object[] { + "Sorry. A fatal internal error has occurred.", + " ", + "You may submit a bug report (via HTTP).", + "The error report will include your system", + "configuration, but no other information.", + " ", + "If you'd like to be notified about a fix,", + "please describe the problem and enter your email address.", + " ", + OurUtil.makeHT("Email:", 5, email, null), + OurUtil.makeHT("Problem:", 5, scroll, null) + }, yes, no)) sendCrashReport(thread, ex, email.getText(), problem.getText()); + System.exit(1); + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/OurAntiAlias.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/OurAntiAlias.java new file mode 100644 index 00000000..9fe1d854 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/OurAntiAlias.java @@ -0,0 +1,86 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4; + +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.RenderingHints; +import java.util.WeakHashMap; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JTextPane; +import javax.swing.text.DefaultHighlighter; + +/** Graphical convenience methods for managing and constructing antialias-capable components. + * + *

Thread Safety: Can be called only by the AWT event thread. + */ + +public final class OurAntiAlias { + + /** This constructor is private, since this utility class never needs to be instantiated. */ + private OurAntiAlias() { } + + /** Use anti-alias or not. */ + private static boolean antiAlias = Util.onMac() || Util.onWindows(); + + /** Stores weak references of all objects that need to be redrawn when anti-alias setting changes. */ + private static WeakHashMap map = new WeakHashMap(); + + /** Changes whether anti-aliasing should be done or not (when changed, we will automatically repaint all affected components). */ + public static void enableAntiAlias(boolean enableAntiAlias) { + if (antiAlias == enableAntiAlias || Util.onMac() || Util.onWindows()) return; + antiAlias = enableAntiAlias; + for(JComponent x: map.keySet()) if (x!=null) { x.invalidate(); x.repaint(); x.validate(); } + } + + /** Constructs an antialias-capable JLabel. + * @param attributes - see {@link edu.mit.csail.sdg.alloy4.OurUtil#make OurUtil.make(component, attributes...)} + */ + public static JLabel label(String label, Object... attributes) { + JLabel ans = new JLabel(label) { + static final long serialVersionUID = 0; + @Override public void paint(Graphics gr) { + if (antiAlias && gr instanceof Graphics2D) { + ((Graphics2D)gr).setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + } + super.paint(gr); + } + }; + OurUtil.make(ans, attributes); + map.put(ans, Boolean.TRUE); + return ans; + } + + /** Constructs an antialias-capable JTextPane with a DefaultHighlighter associated with it. + * @param attributes - see {@link edu.mit.csail.sdg.alloy4.OurUtil#make OurUtil.make(component, attributes...)} + */ + public static JTextPane pane(Object... attributes) { + JTextPane ans = new JTextPane() { + static final long serialVersionUID = 0; + @Override public void paint(Graphics gr) { + if (antiAlias && gr instanceof Graphics2D) { + ((Graphics2D)gr).setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + } + super.paint(gr); + } + }; + OurUtil.make(ans, attributes); + ans.setHighlighter(new DefaultHighlighter()); + map.put(ans, Boolean.TRUE); + return ans; + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/OurBorder.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/OurBorder.java new file mode 100644 index 00000000..3338ea76 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/OurBorder.java @@ -0,0 +1,89 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4; + +import java.awt.Color; +import java.awt.Component; +import java.awt.Graphics; +import java.awt.Insets; +import javax.swing.border.Border; + +/** Graphical border on zero, one, two, three, or all four sides of a component. + * + *

Thread Safety: Can be called only by the AWT event thread. + */ + +public final class OurBorder implements Border { + + /** non-null if we want to draw a border line of that Color above the component. */ + private final Color top; + + /** non-null if we want to draw a border line of that Color to the left of the component. */ + private final Color left; + + /** non-null if we want to draw a border line of that Color below the component. */ + private final Color bottom; + + /** non-null if we want to draw a border line of that Color to the right of the component. */ + private final Color right; + + /** Construct a Border object that draws a border on 0, 1, 2, 3, or all 4 sides of the component. + * Note: it paints the borders top, bottom, left, then right. + * @param top - nonnull if we want to draw a border line (with that color) above the component + * @param left - nonnull if we want to draw a border line (with that color) to the left of the component + * @param bottom - nonnull if we want to draw a border line (with that color) below the component + * @param right - nonnull if we want to draw a border line (with that color) to the right of the component + */ + public OurBorder (Color top, Color left, Color bottom, Color right) { + this.top = top; + this.left = left; + this.bottom = bottom; + this.right = right; + } + + /** Construct a Border object that draws a light gray line on 0, 1, 2, 3, or all 4 sides of the component. + * Note: it paints the borders top, bottom, left, then right. + * @param top - true if we want to draw a Color.LIGHT_GRAY border line above the component + * @param left - true if we want to draw a Color.LIGHT_GRAY border line to the left of the component + * @param bottom - true if we want to draw a Color.LIGHT_GRAY border line below the component + * @param right - true if we want to draw a Color.LIGHT_GRAY border line to the right of the component + */ + public OurBorder (boolean top, boolean left, boolean bottom, boolean right) { + this.top = top ? Color.LIGHT_GRAY : null; + this.left = left ? Color.LIGHT_GRAY : null; + this.bottom = bottom ? Color.LIGHT_GRAY : null; + this.right = right ? Color.LIGHT_GRAY : null; + } + + /** This method is called by Swing to actually draw the borders. */ + public void paintBorder (Component component, Graphics graphics, int x, int y, int width, int height) { + if (width<1 || height<1) return; + Color old = graphics.getColor(); + if (top != null) { graphics.setColor(top); graphics.drawLine(x, y, x+width-1, y ); } + if (bottom != null) { graphics.setColor(bottom); graphics.drawLine(x, y+height-1, x+width-1, y+height-1); } + if (left != null) { graphics.setColor(left); graphics.drawLine(x, y, x, y+height-1); } + if (right != null) { graphics.setColor(right); graphics.drawLine(x+width-1, y, x+width-1, y+height-1); } + graphics.setColor(old); + } + + /** This method is called by Swing to retrieve the dimension of the border. */ + public Insets getBorderInsets (Component c) { + return new Insets(top!=null?1:0, left!=null?1:0, bottom!=null?1:0, right!=null?1:0); + } + + /** This method is called by Swing to find out whether this border object needs to fill in its own background. */ + public boolean isBorderOpaque() { return true; } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/OurCheckbox.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/OurCheckbox.java new file mode 100644 index 00000000..34109763 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/OurCheckbox.java @@ -0,0 +1,99 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4; + +import java.awt.Color; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import javax.swing.BoxLayout; +import javax.swing.Icon; +import javax.swing.JCheckBox; +import javax.swing.JLabel; +import javax.swing.JPanel; + +/** Graphical checkbox. + * + *

Thread Safety: Can be called only by the AWT event thread. + */ + +public abstract class OurCheckbox extends JPanel { + + /** This ensures the class can be serialized reliably. */ + private static final long serialVersionUID = 0; + + /** The icon to use when the checkbox is off. */ + public static final Icon OFF = OurUtil.loadIcon("images/cb0.gif"); + + /** The icon to use when the checkbox is on. */ + public static final Icon ON = OurUtil.loadIcon("images/cb1.gif"); + + /** The icon to use when the checkbox is off entirely. */ + public static final Icon ALL_OFF = OurUtil.loadIcon("images/tcb01.gif"); + + /** The icon to use when the checkbox is on entirely. */ + public static final Icon ALL_ON = OurUtil.loadIcon("images/tcb02.gif"); + + /** The icon to use when the checkbox is off due to inheritance. */ + public static final Icon INH_OFF = OurUtil.loadIcon("images/tcb03.gif"); + + /** The icon to use when the checkbox is on due to inheritance. */ + public static final Icon INH_ON = OurUtil.loadIcon("images/tcb04.gif"); + + /** The underlying JCheckBox object. */ + private final JCheckBox jbox; + + /** The JLabel object for displaying a label next to the checkbox. */ + private final JLabel jlabel; + + /** Constructs a OurCheckbox object. + * @param label - the label to display next to the checkbox + * @param tooltip - the tool tip to show when the mouse hovers over this checkbox + * @param icon - the initial icon to display (should be one of ON/OFF/ALL_ON/ALL_OFF/INH_ON/INH_OFF) + */ + public OurCheckbox(String label, String tooltip, Icon icon) { + setLayout(new BoxLayout(this, BoxLayout.X_AXIS)); + jbox = new JCheckBox(icon); + jbox.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + Icon icon = do_action(); + if (icon != jbox.getIcon()) jbox.setIcon(icon); + } + }); + jbox.setMaximumSize(jbox.getPreferredSize()); + jbox.setToolTipText(tooltip); + jlabel = OurUtil.label(label, tooltip); + if (icon==ON || icon==OFF) { add(jbox); add(jlabel); } else { add(jlabel); add(jbox); } + setAlignmentX(RIGHT_ALIGNMENT); + } + + /** This method is called when the user clicks on the checkbox; subclasses should override this to provide the custom behavior. */ + public abstract Icon do_action(); + + /** This method is called by Swing to enable/disable a component. */ + @Override public final void setEnabled(boolean enabled) { + if (jbox != null) jbox.setEnabled(enabled); + if (jlabel != null) jlabel.setEnabled(enabled); + // jbox and jlabel may be null if during the constructor, some method call causes Swing to call this method early + } + + /** This method is called by Swing to change its background color. */ + @Override public final void setBackground(Color color) { + super.setBackground(color); + if (jbox != null) jbox.setBackground(color); + if (jlabel != null) jlabel.setBackground(color); + // jbox and jlabel may be null if during the constructor, some method call causes Swing to call this method early + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/OurCombobox.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/OurCombobox.java new file mode 100644 index 00000000..3b25d327 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/OurCombobox.java @@ -0,0 +1,97 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4; + +import java.awt.Color; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.event.ActionListener; +import java.awt.event.ActionEvent; +import java.util.Vector; +import javax.swing.Icon; +import javax.swing.JComboBox; +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.ListCellRenderer; +import javax.swing.border.EmptyBorder; + +/** Graphical combobox. + * + *

Thread Safety: Can be called only by the AWT event thread. + */ + +public class OurCombobox extends JComboBox { + + /** This ensures the class can be serialized reliably. */ + private static final long serialVersionUID = 0; + + /** This caches a preconstructed JLabel that is used for the rendering of each Combo value. */ + private static JLabel jlabel; + + /** Subclass can override this method to provide the custom text for any given value (It should return "" if no text is needed) */ + public String do_getText(Object value) { return String.valueOf(value); } + + /** Subclass can override this method to provide the custom icon for any given value (It should return null if no icon is needed) */ + public Icon do_getIcon(Object value) { return null; } + + /** Subclass can override this method to react upon selection change. */ + public void do_changed(Object newValue) { } + + /** This helper method makes a copy of the list, and then optionally prepend null at the beginning of the list. */ + private static Vector do_copy (Object[] list, boolean addNull) { + Vector answer = new Vector(list.length + (addNull ? 1 : 0)); + if (addNull) answer.add(null); + for(int i=0; i 25) height = 25; // Otherwise, the height is too big on Windows + setPreferredSize(new Dimension(width, height)); + setMaximumSize(new Dimension(width, height)); + if (!Util.onWindows() && !Util.onMac()) setBorder(new EmptyBorder(4, 3, 4, 0)); + } + if (initialValue != null) { setSelectedItem(initialValue); } + addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { do_changed(getSelectedItem()); } + }); + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/OurConsole.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/OurConsole.java new file mode 100644 index 00000000..1bf57a79 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/OurConsole.java @@ -0,0 +1,279 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Event; +import java.awt.Font; +import java.awt.Insets; +import java.awt.Rectangle; +import java.awt.event.ActionEvent; +import java.awt.event.FocusAdapter; +import java.awt.event.FocusEvent; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import javax.swing.AbstractAction; +import javax.swing.JPanel; +import javax.swing.JScrollBar; +import javax.swing.JScrollPane; +import javax.swing.JTextPane; +import javax.swing.KeyStroke; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import javax.swing.text.AttributeSet; +import javax.swing.text.BadLocationException; +import javax.swing.text.Caret; +import javax.swing.text.MutableAttributeSet; +import javax.swing.text.SimpleAttributeSet; +import javax.swing.text.StyleConstants; +import javax.swing.text.StyledDocument; + +/** Graphical input/output prompt. + * + *

This class's constructor takes a Computer object, then constructs a JScrollPane + * in which the user can type commands, and the output from the Computer object will be displayed. + * Empty input lines are ignored. + * This interactive prompt supports UP and DOWN arrow command histories and basic copy/cut/paste editing. + * + *

For each user input, if the Computer object returns a String, it is displayed in blue. + * But if the Computer object throws an exception, the exception will be displayed in red. + * + *

Thread Safety: Can be called only by the AWT event thread. + */ + +public final class OurConsole extends JScrollPane { + + /** This ensures the class can be serialized reliably. */ + private static final long serialVersionUID = 0; + + /** The style for default text. */ + private final AttributeSet plain = style("Verdana", 14, false, Color.BLACK, 0); + + /** The style for bold text. */ + private final AttributeSet bold = style("Verdana", 14, true, Color.BLACK, 0); + + /** The style for successful result. */ + private final AttributeSet good = style("Verdana", 14, false, Color.BLUE, 15); + + /** The style for failed result. */ + private final AttributeSet bad = style("Verdana", 14, false, Color.RED, 15); + + /** The number of characters that currently exist above the horizontal divider bar. + * (The interactive console is composed of a JTextPane which contains 0 or more input/output pairs, followed + * by a horizontal divider bar, followed by an embedded sub-JTextPane (where the user can type in the next input)) + */ + private int len = 0; + + /** The main JTextPane containing 0 or more input/output pairs, followed by a horizontal bar, followed by this.sub */ + private final JTextPane main = do_makeTextPane(false, 5, 5, 5); + + /** The sub JTextPane where the user can type in the next command. */ + private final JTextPane sub = do_makeTextPane(true, 10, 10, 0); + + /** The history of all commands entered so far, plus an extra String representing the user's next command. */ + private final List history = new ArrayList(); { history.add(""); } + + /** The position in this.history that is currently showing. */ + private int browse = 0; + + /** Helper method that construct a mutable style with the given font name, font size, boldness, color, and left indentation. */ + static MutableAttributeSet style(String fontName, int fontSize, boolean boldness, Color color, int leftIndent) { + MutableAttributeSet s = new SimpleAttributeSet(); + StyleConstants.setFontFamily(s, fontName); + StyleConstants.setFontSize(s, fontSize); + StyleConstants.setBold(s, boldness); + StyleConstants.setForeground(s, color); + StyleConstants.setLeftIndent(s, leftIndent); + return s; + } + + /** Construct a JScrollPane that allows the user to interactively type in commands and see replies. + * + * @param computer - this object is used to evaluate the user input + * + * @param syntaxHighlighting - if true, the "input area" will be syntax-highlighted + * + * @param initialMessages - this is a list of String and Boolean; each String is printed to the screen as is, + * and Boolean.TRUE will turn subsequent text bold, and Boolean.FALSE will turn subsequent text non-bold. + */ + public OurConsole(final Computer computer, boolean syntaxHighlighting, Object... initialMessages) { + super(VERTICAL_SCROLLBAR_AS_NEEDED, HORIZONTAL_SCROLLBAR_AS_NEEDED); + if (syntaxHighlighting) { sub.setDocument(new OurSyntaxUndoableDocument("Verdana", 14)); } + setViewportView(main); + // show the initial message + AttributeSet st = plain; + for(Object x: initialMessages) { + if (x instanceof Boolean) st = (Boolean.TRUE.equals(x) ? bold : plain); else do_add(-1, String.valueOf(x), st); + } + do_add(-1, "\n", plain); // we must add a linebreak to ensure that subsequent text belong to a "different paragraph" + // insert the divider and the sub JTextPane + StyledDocument doc = main.getStyledDocument(); + JPanel divider = new JPanel(); divider.setBackground(Color.LIGHT_GRAY); divider.setPreferredSize(new Dimension(1,1)); + MutableAttributeSet dividerStyle = new SimpleAttributeSet(); StyleConstants.setComponent(dividerStyle, divider); + MutableAttributeSet inputStyle = new SimpleAttributeSet(); StyleConstants.setComponent(inputStyle, sub); + len = doc.getLength(); + do_add(-1, " \n", dividerStyle); // The space character won't be displayed; it will instead be drawn as a divider + do_add(-1, " \n", inputStyle); // The space character won't be displayed; it will instead display the input buffer + final Caret subCaret = sub.getCaret(), mainCaret = main.getCaret(); + // When caret moves in the sub JTextPane, we cancel any active selection in the main JTextPane + subCaret.addChangeListener(new ChangeListener() { + public void stateChanged(ChangeEvent e) { + if (mainCaret.getMark() != mainCaret.getDot()) mainCaret.setDot(mainCaret.getDot()); + } + }); + // When caret moves in the main JTextPane, we cancel any active selection in the sub JTextPane + mainCaret.addChangeListener(new ChangeListener() { + public void stateChanged(ChangeEvent e) { + if (subCaret.getMark() != subCaret.getDot()) subCaret.setDot(subCaret.getDot()); + } + }); + // now, create the paste/copy/cut actions + AbstractAction alloy_paste = new AbstractAction("alloy_paste") { + static final long serialVersionUID = 0; + public void actionPerformed(ActionEvent x) { sub.paste(); } + }; + AbstractAction alloy_copy = new AbstractAction("alloy_copy") { + static final long serialVersionUID = 0; + public void actionPerformed(ActionEvent x) { + if (sub.getSelectionStart() != sub.getSelectionEnd()) sub.copy(); else main.copy(); + } + }; + AbstractAction alloy_cut = new AbstractAction("alloy_cut") { + static final long serialVersionUID = 0; + public void actionPerformed(ActionEvent x) { + if (sub.getSelectionStart() != sub.getSelectionEnd()) sub.cut(); else main.copy(); + } + }; + // create the keyboard associations: ctrl-{c,v,x,insert} and shift-{insert,delete} + for(JTextPane x: Arrays.asList(main, sub)) { + x.getActionMap().put("alloy_paste", alloy_paste); + x.getActionMap().put("alloy_copy", alloy_copy); + x.getActionMap().put("alloy_cut", alloy_cut); + x.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_V, Event.CTRL_MASK), "alloy_paste"); + x.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_C, Event.CTRL_MASK), "alloy_copy"); + x.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_X, Event.CTRL_MASK), "alloy_cut"); + x.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_INSERT, Event.SHIFT_MASK), "alloy_paste"); + x.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_INSERT, Event.CTRL_MASK), "alloy_copy"); + x.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, Event.SHIFT_MASK), "alloy_cut"); + } + // configure so that, upon receiving focus, we automatically focus and scroll to the sub-JTextPane + FocusAdapter focus = new FocusAdapter() { + public void focusGained(FocusEvent e) { + sub.requestFocusInWindow(); + sub.scrollRectToVisible(new Rectangle(0, sub.getY(), 1, sub.getHeight())); + } + }; + addFocusListener(focus); + sub.addFocusListener(focus); + main.addFocusListener(focus); + // configure so that mouse clicks in the main JTextPane will immediately transfer focus to the sub JTextPane + main.addMouseListener(new MouseAdapter() { + public void mousePressed(MouseEvent e) { sub.requestFocusInWindow(); } + public void mouseClicked(MouseEvent e) { sub.requestFocusInWindow(); } + }); + // configure the behavior for PAGE_UP, PAGE_DOWN, UP, DOWN, TAB, and ENTER + sub.addKeyListener(new KeyListener() { + public void keyTyped(KeyEvent e) { + if (e.getKeyChar() == '\t') { e.consume(); } + if (e.getKeyChar() == '\n') { e.consume(); String cmd = sub.getText(); sub.setText(""); do_command(computer, cmd); } + } + public void keyPressed(KeyEvent e) { + if (e.getKeyCode() == KeyEvent.VK_ENTER || e.getKeyCode()==KeyEvent.VK_TAB) e.consume(); + if (e.getKeyCode() == KeyEvent.VK_PAGE_UP) { e.consume(); do_pageup(); } + if (e.getKeyCode() == KeyEvent.VK_PAGE_DOWN) { e.consume(); do_pagedown(); } + if (e.getKeyCode() == KeyEvent.VK_UP) { + e.consume(); + if (browse == history.size() - 1) { history.set(browse, sub.getText()); } + if (browse > 0 && browse - 1 < history.size()) { browse--; sub.setText(history.get(browse)); } + } + if (e.getKeyCode() == KeyEvent.VK_DOWN) { + e.consume(); + if (browse < history.size() - 1) { browse++; sub.setText(history.get(browse)); } + } + } + public void keyReleased(KeyEvent e) { + if (e.getKeyCode() == KeyEvent.VK_ENTER || e.getKeyCode() == KeyEvent.VK_TAB) e.consume(); + } + }); + } + + /** This helper method constructs a JTextPane with the given settings. */ + private static JTextPane do_makeTextPane(boolean editable, int topMargin, int bottomMargin, int otherMargin) { + JTextPane x = OurAntiAlias.pane(Color.BLACK, Color.WHITE, new Font("Verdana", Font.PLAIN, 14)); + x.setEditable(editable); + x.setAlignmentX(0); + x.setAlignmentY(0); + x.setCaretPosition(0); + x.setMargin(new Insets(topMargin, otherMargin, bottomMargin, otherMargin)); + return x; + } + + /** This method processes a user command. */ + private void do_command(Computer computer, String cmd) { + cmd = cmd.trim(); + if (cmd.length()==0) return; + StyledDocument doc = main.getStyledDocument(); + if (history.size()>=2 && cmd.equals(history.get(history.size()-2))) { + // If the user merely repeated the most recent command, then don't grow the history + history.set(history.size()-1, ""); + } else { + // Otherwise, grow the history + history.set(history.size()-1, cmd); + history.add(""); + } + browse = history.size()-1; + // display the command + int old = doc.getLength(); do_add(len, cmd+"\n\n", plain); len += (doc.getLength() - old); + // perform the computation + boolean isBad = false; + try { cmd = computer.compute(cmd); } catch(Throwable ex) { cmd = ex.toString(); isBad = true; } + int savePosition = len; + // display the outcome + old = doc.getLength(); do_add(len, cmd.trim()+"\n\n", (isBad ? bad : good)); len += (doc.getLength() - old); + // indent the outcome + main.setSelectionStart(savePosition+1); main.setSelectionEnd(len); main.setParagraphAttributes(good, false); + // redraw then scroll to the bottom + invalidate(); + repaint(); + validate(); + sub.scrollRectToVisible(new Rectangle(0, sub.getY(), 1, sub.getHeight())); + do_pagedown(); // need to do this after the validate() so that the scrollbar knows the new limit + } + + /** Performs "page up" in the JScrollPane. */ + private void do_pageup() { + JScrollBar bar = getVerticalScrollBar(); + bar.setValue(bar.getValue() - 200); + } + + /** Performs "page down" in the JScrollPane. */ + private void do_pagedown() { + JScrollBar bar = getVerticalScrollBar(); + bar.setValue(bar.getValue() + 200); + } + + /** Insert the given text into the given location and with the given style if where>=0; append the text if where<0. */ + private void do_add(int where, String text, AttributeSet style) { + StyledDocument doc = main.getStyledDocument(); + try { doc.insertString(where >= 0 ? where : doc.getLength(), text, style); } catch(BadLocationException ex) { } + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/OurDialog.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/OurDialog.java new file mode 100644 index 00000000..9246113f --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/OurDialog.java @@ -0,0 +1,247 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4; + +import java.io.File; +import java.io.FilenameFilter; +import java.util.Locale; +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.FileDialog; +import java.awt.Frame; +import java.awt.GraphicsEnvironment; +import java.awt.HeadlessException; +import java.awt.event.KeyListener; +import java.awt.event.KeyEvent; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JComboBox; +import javax.swing.JComponent; +import javax.swing.JDialog; +import javax.swing.JFileChooser; +import javax.swing.JFrame; +import javax.swing.JOptionPane; +import javax.swing.JScrollPane; +import javax.swing.JTextArea; +import javax.swing.JTextField; +import javax.swing.filechooser.FileFilter; +import static javax.swing.JOptionPane.YES_NO_OPTION; +import static javax.swing.JOptionPane.QUESTION_MESSAGE; +import static javax.swing.JOptionPane.WARNING_MESSAGE; +import static javax.swing.JOptionPane.ERROR_MESSAGE; + +/** Graphical dialog methods for asking the user some questions. + * + *

Thread Safety: Can be called only by the AWT event thread. + */ + +public final class OurDialog { + + /** The constructor is private, since this utility class never needs to be instantiated. */ + private OurDialog() { } + + /** Helper method for constructing an always-on-top modal dialog. */ + private static Object show(String title, int type, Object message, Object[] options, Object initialOption) { + if (options == null) { options = new Object[]{"Ok"}; initialOption = "Ok"; } + JOptionPane p = new JOptionPane(message, type, JOptionPane.DEFAULT_OPTION, null, options, initialOption); + p.setInitialValue(initialOption); + JDialog d = p.createDialog(null, title); + p.selectInitialValue(); + d.setAlwaysOnTop(true); + d.setVisible(true); + d.dispose(); + return p.getValue(); + } + + /** Popup the given informative message, then ask the user to click Close to close it. */ + public static void showmsg(String title, Object... msg) { + JButton dismiss = new JButton(Util.onMac() ? "Dismiss" : "Close"); + Object[] objs = new Object[msg.length + 1]; + System.arraycopy(msg, 0, objs, 0, msg.length); + objs[objs.length - 1] = OurUtil.makeH(null, dismiss, null); + JOptionPane about = new JOptionPane(objs, JOptionPane.PLAIN_MESSAGE, JOptionPane.DEFAULT_OPTION, null, new Object[]{}); + JDialog dialog = about.createDialog(null, title); + dismiss.addActionListener(Runner.createDispose(dialog)); + dialog.setAlwaysOnTop(true); + dialog.setVisible(true); + dialog.dispose(); + } + + /** Popup the given error message. */ + public static void alert(Object message) { + show("Error", ERROR_MESSAGE, message, null, null); + } + + /** Popup the given error message, then terminate the program. */ + public static void fatal(Object message) { + try { show("Fatal Error", ERROR_MESSAGE, message, null, null); } finally { System.exit(1); } + } + + /** Ask if the user wishes to save the file, discard the file, or cancel the entire operation (default is cancel). + * @return 'c' if cancel, 's' if save, 'd' if discard + */ + public static char askSaveDiscardCancel(String description) { + description = description + " has not been saved. Do you want to"; + Object ans = show( + "Warning", WARNING_MESSAGE, + new String[] {description, "cancel the operation, close the file without saving, or save it and close?"}, + new Object[] {"Save", "Don't Save", "Cancel"}, + "Cancel" + ); + return (ans == "Save") ? 's' : (ans == "Don't Save" ? 'd' : 'c'); + } + + /** Ask if the user really wishes to overwrite the file (default is no). + * @return true if the user wishes to overwrite the file, false if the user does not wish to overwrite the file. + */ + public static boolean askOverwrite(String filename) { + return "Overwrite" == show("Warning: file already exists", WARNING_MESSAGE, + new String[] {"The file \"" + filename + "\"", "already exists. Do you wish to overwrite it?"}, + new String[] {"Overwrite", "Cancel"}, + "Cancel" + ); + } + + /** This caches the result of the call to get all fonts. */ + private static String[] allFonts = null; + + /** Returns true if a font with that name exists on the system (comparison is case-insensitive). */ + public synchronized static boolean hasFont(String fontname) { + if (allFonts == null) allFonts = GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames(); + for(int i = 0; i < allFonts.length; i++) if (fontname.compareToIgnoreCase(allFonts[i]) == 0) return true; + return false; + } + + /** Asks the user to choose a font; returns "" if the user cancels the request. */ + public synchronized static String askFont() { + if (allFonts == null) allFonts = GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames(); + JComboBox jcombo = new OurCombobox(allFonts); + Object ans = show("Font", JOptionPane.INFORMATION_MESSAGE, + new Object[] {"Please choose the new font:", jcombo}, new Object[] {"Ok", "Cancel"}, "Cancel" + ); + Object value = jcombo.getSelectedItem(); + if (ans=="Ok" && (value instanceof String)) return (String)value; else return ""; + } + + /** True if we should use AWT (instead of Swing) to display the OPEN and SAVE dialog. */ + private static boolean useAWT = Util.onMac(); + + /** Use the platform's preferred file chooser to ask the user to select a file. + *
Note: if it is a save operation, and the user didn't include an extension, then we'll add the extension. + * @param isOpen - true means this is an Open operation; false means this is a Save operation + * @param dir - the initial directory (or null if we want to use the default) + * @param ext - the file extension (including "."; using lowercase letters; for example, ".als") or "" + * @param description - the description for the given extension + * @return null if the user didn't choose anything, otherwise it returns the selected file + */ + public static File askFile (boolean isOpen, String dir, final String ext, final String description) { + if (dir == null) dir = Util.getCurrentDirectory(); + if (!(new File(dir).isDirectory())) dir = System.getProperty("user.home"); + dir = Util.canon(dir); + String ans; + if (useAWT) { + Frame parent = new Frame("Alloy File Dialog"); // this window is unused and not shown; needed by FileDialog and nothing more + FileDialog open = new FileDialog(parent, isOpen ? "Open..." : "Save..."); + open.setAlwaysOnTop(true); + open.setMode(isOpen ? FileDialog.LOAD : FileDialog.SAVE); + open.setDirectory(dir); + if (ext.length()>0) open.setFilenameFilter(new FilenameFilter() { + public boolean accept(File dir, String name) { return name.toLowerCase(Locale.US).endsWith(ext); } + }); + open.setVisible(true); // This method blocks until the user either chooses something or cancels the dialog. + parent.dispose(); + if (open.getFile() == null) return null; else ans = open.getDirectory() + File.separatorChar + open.getFile(); + } else { + try { + JFileChooser open = new JFileChooser(dir) { + private static final long serialVersionUID = 0; + public JDialog createDialog(Component parent) throws HeadlessException { + JDialog dialog = super.createDialog(null); + dialog.setAlwaysOnTop(true); + return dialog; + } + }; + open.setDialogTitle(isOpen ? "Open..." : "Save..."); + open.setApproveButtonText(isOpen ? "Open" : "Save"); + open.setDialogType(isOpen ? JFileChooser.OPEN_DIALOG : JFileChooser.SAVE_DIALOG); + if (ext.length()>0) open.setFileFilter(new FileFilter() { + public boolean accept(File file) { return !file.isFile() || file.getPath().toLowerCase(Locale.US).endsWith(ext); } + public String getDescription() { return description; } + }); + if (open.showDialog(null, null) != JFileChooser.APPROVE_OPTION || open.getSelectedFile() == null) return null; + ans = open.getSelectedFile().getPath(); + } catch(Exception ex) { + // Some combination of Windows version and JDK version will trigger this failure. + // In such a case, we'll fall back to using the "AWT" file open dialog + useAWT = true; + return askFile(isOpen, dir, ext, description); + } + } + if (!isOpen) { + int lastSlash = ans.lastIndexOf(File.separatorChar); + int lastDot = (lastSlash>=0) ? ans.indexOf('.', lastSlash) : ans.indexOf('.'); + if (lastDot < 0) ans = ans + ext; + } + return new File(Util.canon(ans)); + } + + /** Display "msg" in a modal dialog window, and ask the user to choose "yes" versus "no" (default is "no"). */ + public static boolean yesno(Object msg, String yes, String no) { + return show("Question", WARNING_MESSAGE, msg, new Object[]{yes, no}, no) == yes; + } + + /** Display "msg" in a modal dialog window, and ask the user to choose "Yes" versus "No" (default is "no"). */ + public static boolean yesno(Object msg) { return yesno(msg, "Yes", "No"); } + + /** Display a modal dialog window containing the "objects"; returns true iff the user clicks Ok. */ + public static boolean getInput(String title, Object... objects) { + // If there is a JTextField or a JTextArea here, then let the first JTextField or JTextArea be the initially focused widget + Object main = "Ok"; + for(Object obj: objects) if (obj instanceof JTextField || obj instanceof JTextArea) { main = obj; break; } + // Construct the dialog panel + final JOptionPane pane = new JOptionPane(objects, QUESTION_MESSAGE, YES_NO_OPTION, null, new Object[]{"Ok", "Cancel"}, main); + final JDialog dialog = pane.createDialog(null, title); + // For each JTextField and JCheckBox, add a KeyListener that detects VK_ENTER and treat it as if the user clicked OK + for(Object obj: objects) if (obj instanceof JTextField || obj instanceof JCheckBox) { + ((JComponent)obj).addKeyListener(new KeyListener() { + public void keyPressed(KeyEvent e) { if (e.getKeyCode()==KeyEvent.VK_ENTER) { pane.setValue("Ok"); dialog.dispose(); } } + public void keyReleased(KeyEvent e) { } + public void keyTyped(KeyEvent e) { } + }); + } + dialog.setAlwaysOnTop(true); + dialog.setVisible(true); // This method blocks until the user either chooses something or cancels the dialog. + dialog.dispose(); + return pane.getValue() == "Ok"; + } + + /** Display a simple non-modal window showing some text. */ + public static JFrame showtext(String title, String text) { + JFrame window = new JFrame(title); + JButton done = new JButton("Close"); + done.addActionListener(Runner.createDispose(window)); + JScrollPane scrollPane = OurUtil.scrollpane(OurUtil.textarea(text, 20, 60, false, false)); + window.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); + window.getContentPane().setLayout(new BorderLayout()); + window.getContentPane().add(scrollPane, BorderLayout.CENTER); + window.getContentPane().add(done, BorderLayout.SOUTH); + window.pack(); + window.setSize(500, 500); + window.setLocationRelativeTo(null); + window.setVisible(true); + return window; + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/OurHighlighter.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/OurHighlighter.java new file mode 100644 index 00000000..183ff4d9 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/OurHighlighter.java @@ -0,0 +1,58 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4; + +import java.awt.Color; +import java.awt.Graphics; +import java.awt.Rectangle; +import java.awt.Shape; +import javax.swing.text.BadLocationException; +import javax.swing.text.Highlighter; +import javax.swing.text.JTextComponent; + +/** Graphica highlighter. + * + *

Thread Safety: Can be called only by the AWT event thread. + */ + +public final class OurHighlighter implements Highlighter.HighlightPainter { + + /** The color to use when drawing highlights. */ + public final Color color; + + /** Construct a highlighter with the given color. */ + public OurHighlighter(Color color) { this.color = color; } + + /** This method is called by Swing to draw highlights. */ + public void paint(Graphics gr, int start, int end, Shape shape, JTextComponent text) { + Color old = gr.getColor(); + gr.setColor(color); + try { + Rectangle box = shape.getBounds(), a = text.getUI().modelToView(text, start), b = text.getUI().modelToView(text, end); + if (a.y == b.y) { + // same line (Note: furthermore, if start==end, then we draw all the way to the right edge) + Rectangle r = a.union(b); + gr.fillRect(r.x, r.y, (r.width<=1 ? (box.x + box.width - r.x) : r.width), r.height); + } else { + // Multiple lines; (Note: on first line we'll draw from "start" and extend to rightmost) + gr.fillRect(a.x, a.y, box.x + box.width - a.x, a.height); + if (a.y + a.height < b.y) gr.fillRect(box.x, a.y + a.height, box.width, b.y - (a.y + a.height)); + gr.fillRect(box.x, b.y, b.x - box.x, b.height); + } + } catch (BadLocationException e) { } // Failure to highlight is not fatal + gr.setColor(old); + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/OurPDFWriter.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/OurPDFWriter.java new file mode 100644 index 00000000..7346cb72 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/OurPDFWriter.java @@ -0,0 +1,277 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4; + +import java.awt.Color; +import java.awt.Polygon; +import java.awt.Shape; +import java.awt.geom.PathIterator; +import java.io.IOException; +import java.io.RandomAccessFile; + +/** Graphical convenience methods for producing PDF files. + * + *

This implementation explicitly generates a very simple 8.5 inch by 11 inch one-page PDF consisting of graphical operations. + * Hopefully this class will no longer be needed in the future once Java comes with better PDF support. + */ + +public final strictfp class OurPDFWriter { + + /** The filename. */ + private final String filename; + + /** The page width (in terms of dots). */ + private final long width; + + /** The page height (in terms of dots). */ + private final long height; + + /** Latest color expressed as RGB (-1 if none has been explicitly set so far) */ + private int color = -1; + + /** Latest line style (0=normal, 1=bold, 2=dotted, 3=dashed) */ + private int line = 0; + + /** The buffer that will store the list of graphical operations issued so far (null if close() has been called successfully) */ + private ByteBuffer buf = new ByteBuffer(); + + /** Begin a blank PDF file with the given dots-per-inch and the given scale (the given file, if existed, will be overwritten) + * @throws IllegalArgumentException if dpi is less than 50 or is greater than 3000 + */ + public OurPDFWriter(String filename, int dpi, double scale) { + if (dpi<50 || dpi>3000) throw new IllegalArgumentException("The DPI must be between 50 and 3000"); + this.filename = filename; + width = dpi*8L + (dpi/2L); // "8.5 inches" + height = dpi*11L; // "11 inches" + // Write the default settings, and flip (0, 0) into the top-left corner of the page, scale the page, then leave 0.5" margin + buf.write("q\n" + "1 J\n" + "1 j\n" + "[] 0 d\n" + "1 w\n" + "1 0 0 -1 0 ").writes(height).write("cm\n"); + buf.writes(scale).write("0 0 ").writes(scale).writes(dpi/2.0).writes(dpi/2.0).write("cm\n"); + buf.write("1 0 0 1 ").writes(dpi/2.0).writes(dpi/2.0).write("cm\n"); + } + + /** Changes the color for subsequent graphical drawing. */ + public OurPDFWriter setColor(Color color) { + int rgb = color.getRGB() & 0xFFFFFF, r = (rgb>>16), g = (rgb>>8) & 0xFF, b = (rgb & 0xFF); + if (this.color == rgb) return this; else this.color = rgb; // no need to change + buf.writes(r/255.0).writes(g/255.0).writes(b/255.0).write("RG\n"); + buf.writes(r/255.0).writes(g/255.0).writes(b/255.0).write("rg\n"); + return this; + } + + /** Changes the line style to be normal. */ + public OurPDFWriter setNormalLine() { if (line!=0) buf.write("1 w [] 0 d\n"); line=0; return this; } + + /** Changes the line style to be bold. */ + public OurPDFWriter setBoldLine() { if (line!=1) buf.write("2 w [] 0 d\n"); line=1; return this; } + + /** Changes the line style to be dotted. */ + public OurPDFWriter setDottedLine() { if (line!=2) buf.write("1 w [1 3] 0 d\n"); line=2; return this; } + + /** Changes the line style to be dashed. */ + public OurPDFWriter setDashedLine() { if (line!=3) buf.write("1 w [6 3] 0 d\n"); line=3; return this; } + + /** Shifts the coordinate space by the given amount. */ + public OurPDFWriter shiftCoordinateSpace(int x, int y) { buf.write("1 0 0 1 ").writes(x).writes(y).write("cm\n"); return this; } + + /** Draws a line from (x1, y1) to (x2, y2). */ + public OurPDFWriter drawLine(int x1, int y1, int x2, int y2) { + buf.writes(x1).writes(y1).write("m ").writes(x2).writes(y2).write("l S\n"); return this; + } + + /** Draws a circle of the given radius, centered at (0, 0). */ + public OurPDFWriter drawCircle(int radius, boolean fillOrNot) { + double k = (0.55238 * radius); // Approximate a circle using 4 cubic bezier curves + buf.writes( radius).write("0 m "); + buf.writes( radius).writes( k).writes( k).writes( radius).write("0 ") .writes( radius).write("c "); + buf.writes( -k).writes( radius).writes(-radius).writes( k).writes(-radius).write("0 c "); + buf.writes(-radius).writes( -k).writes( -k).writes(-radius).write("0 ") .writes(-radius).write("c "); + buf.writes( k).writes(-radius).writes( radius).writes( -k).writes(radius) .write(fillOrNot ? "0 c f\n" : "0 c S\n"); + return this; + } + + /** Draws a shape. */ + public OurPDFWriter drawShape(Shape shape, boolean fillOrNot) { + if (shape instanceof Polygon) { + Polygon obj = (Polygon)shape; + for(int i = 0; i < obj.npoints; i++) buf.writes(obj.xpoints[i]).writes(obj.ypoints[i]).write(i==0 ? "m\n" : "l\n"); + buf.write("h\n"); + } else { + double moveX = 0, moveY = 0, nowX = 0, nowY = 0, pt[] = new double[6]; + for(PathIterator it = shape.getPathIterator(null); !it.isDone(); it.next()) switch(it.currentSegment(pt)) { + case PathIterator.SEG_MOVETO: + nowX = moveX = pt[0]; nowY = moveY = pt[1]; buf.writes(nowX).writes(nowY).write("m\n"); break; + case PathIterator.SEG_CLOSE: + nowX = moveX; nowY = moveY; buf.write("h\n"); break; + case PathIterator.SEG_LINETO: + nowX = pt[0]; nowY = pt[1]; buf.writes(nowX).writes(nowY).write("l\n"); break; + case PathIterator.SEG_CUBICTO: + nowX = pt[4]; nowY = pt[5]; + buf.writes(pt[0]).writes(pt[1]).writes(pt[2]).writes(pt[3]).writes(nowX).writes(nowY).write("c\n"); break; + case PathIterator.SEG_QUADTO: // Convert quadratic bezier into cubic bezier using de Casteljau algorithm + double px = nowX + (pt[0] - nowX)*(2.0/3.0), qx = px + (pt[2] - nowX)/3.0; + double py = nowY + (pt[1] - nowY)*(2.0/3.0), qy = py + (pt[3] - nowY)/3.0; + nowX = pt[2]; nowY = pt[3]; + buf.writes(px).writes(py).writes(qx).writes(qy).writes(nowX).writes(nowY).write("c\n"); break; + } + } + buf.write(fillOrNot ? "f\n" : "S\n"); + return this; + } + + /* PDF File Structure Summary: + * =========================== + * + * File should ideally start with the following 13 bytes: "%PDF-1.3" 10 "%" -127 10 10 + * Now comes one or more objects. + * One simple single-page arrangement is to have exactly 5 objects in this order: FONT, CONTENT, PAGE, PAGES, and CATALOG. + * + * Font Object (1 because FONT is #1) + * ================================== + * + * 1 0 obj << /Type /Font /Subtype /Type1 /BaseFont /Helvetica /Encoding /WinAnsiEncoding >> endobj\n\n + * + * Content Object (2 because CONTENT is #2) (${LEN} is the number of bytes in ${CONTENT} when compressed) + * ====================================================================================================== + * + * 2 0 obj << /Length ${LEN} /Filter /FlateDecode >> stream\r\n${CONTENT}endstream endobj\n\n + * + * Here is a quick summary of various PDF Graphics operations + * ========================================================== + * + * $x $y m --> begins a new path at the given coordinate + * $x $y l --> add the segment (LASTx,LASTy)..($x,$y) to the current path + * $cx $cy $x $y v --> add the bezier curve (LASTx,LASTy)..(LASTx,LASTy)..($cx,$cy)..($x,$y) to the current path + * $cx $cy $x $y y --> add the bezier curve (LASTx,LASTy)....($cx,$cy).....($x,$y)...($x,$y) to the current path + * $ax $ay $bx $by $x $y c --> add the bezier curve (LASTx,LASTy)....($ax,$ay)....($bx,$by)..($x,$y) to the current path + * h --> close the current subpath by straightline segment from current point to the start of this subpath + * $x $y $w $h re --> append a rectangle to the current path as a complete subpath with lower-left corner at $x $y + * + * S --> assuming we've just described a path, draw the path + * f --> assuming we've just described a path, fill the path + * B --> assuming we've just described a path, fill then draw the path + * + * q --> saves the current graphics state + * 1 J --> sets the round cap + * 1 j --> sets the round joint + * [] 0 d --> sets the dash pattern as SOLID + * [4 6] 0 d --> sets the dash pattern as 4 UNITS ON then 6 UNITS OFF + * 5 w --> sets the line width as 5 UNITS + * $a $b $c $d $e $f cm --> appends the given matrix; for example, [1 0 0 1 dx dy] means "translation to dx dy" + * $R $G $B RG --> sets the stroke color (where 0 <= $R <= 1, etc) + * $R $G $B rg --> sets the fill color (where 0 <= $R <= 1, etc) + * Q --> restores the current graphics state + * + * Page Object (3 because PAGE is #3) (4 beacuse PAGES is #4) (2 because CONTENTS is #2) + * ===================================================================================== + * + * 3 0 obj << /Type /Page /Parent 4 0 R /Contents 2 0 R >> endobj\n\n + * + * Pages Object (4 because PAGES is #4) (3 because PAGE is #3) (${W} is 8.5*DPI, ${H} is 11*DPI) (1 because FONT is #1) + * ==================================================================================================================== + * + * 4 0 obj << /Type /Pages /Count 1 /Kids [3 0 R] /MediaBox [0 0 ${W} ${H}] /Resources << /Font << /F1 1 0 R >> >> >> endobj\n\n + * + * Catalog Object (5 because CATALOG is #5) (4 because PAGES is #4) + * ================================================================ + * + * 5 0 obj << /Type /Catalog /Pages 4 0 R >> endobj\n\n + * + * END_OF_FILE format (assuming we have obj1 obj2 obj3 obj4 obj5 where obj5 is the "PDF Catalog") + * ============================================================================================== + * + * xref\n + * 0 6\n // 6 is because it's the number of objects plus 1 + * 0000000000 65535 f\r\n + * ${offset1} 00000 n\r\n // ${offset1} is byte offset of start of obj1, left-padded-with-zero until you get exactly 10 digits + * ${offset2} 00000 n\r\n // ${offset2} is byte offset of start of obj2, left-padded-with-zero until you get exactly 10 digits + * ${offset3} 00000 n\r\n // ${offset3} is byte offset of start of obj3, left-padded-with-zero until you get exactly 10 digits + * ${offset4} 00000 n\r\n // ${offset4} is byte offset of start of obj4, left-padded-with-zero until you get exactly 10 digits + * ${offset5} 00000 n\r\n // ${offset5} is byte offset of start of obj5, left-padded-with-zero until you get exactly 10 digits + * trailer\n + * <<\n + * /Size 6\n // 6 is because it's the number of objects plus 1 + * /Root 5 0 R\n // 5 is because it's the Catalog Object's object ID + * >>\n + * startxref\n + * ${xref}\n // $xref is the byte offset of the start of this entire "xref" paragraph + * %%EOF\n + */ + + /** Helper method that writes the given String to the output file, then return the number of bytes written. */ + private static int out(RandomAccessFile file, String string) throws IOException { + byte[] array = string.getBytes("UTF-8"); + file.write(array); + return array.length; + } + + /** Close and save this PDF object. */ + public void close() throws IOException { + if (buf == null) return; // already closed + final boolean compressOrNot = true; + RandomAccessFile out = null; + try { + String space = " "; // reserve 20 bytes for the file size, which is far far more than enough + final long fontID = 1, contentID = 2, pageID = 3, pagesID = 4, catalogID = 5, offset[] = new long[6]; + // Write %PDF-1.3, followed by a non-ASCII comment to force the PDF into binary mode + out = new RandomAccessFile(filename, "rw"); + out.setLength(0); + byte[] head = new byte[]{'%', 'P', 'D', 'F', '-', '1', '.', '3', 10, '%', -127, 10, 10}; + out.write(head); + long now = head.length; + // Font + offset[1] = now; + now += out(out, fontID + " 0 obj << /Type /Font /Subtype /Type1 /BaseFont" + + " /Helvetica /Encoding /WinAnsiEncoding >> endobj\n\n"); + // Content + offset[2] = now; + now += out(out, contentID + " 0 obj << /Length " + space + + (compressOrNot ? " /Filter /FlateDecode" : "") + " >> stream\r\n"); + buf.write("Q\n"); + final long ct = compressOrNot ? buf.dumpFlate(out) : buf.dump(out); + now += ct + out(out, "endstream endobj\n\n"); + // Page + offset[3] = now; + now += out(out, pageID + " 0 obj << /Type /Page /Parent " + pagesID + " 0 R /Contents " + contentID + " 0 R >> endobj\n\n"); + // Pages + offset[4] = now; + now += out(out, pagesID + " 0 obj << /Type /Pages /Count 1 /Kids [" + pageID + " 0 R] /MediaBox [0 0 " + + width + " " + height + "] /Resources << /Font << /F1 " + fontID + " 0 R >> >> >> endobj\n\n"); + // Catalog + offset[5] = now; + now += out(out, catalogID + " 0 obj << /Type /Catalog /Pages " + pagesID + " 0 R >> endobj\n\n"); + // Xref + String xr = "xref\n" + "0 " + offset.length + "\n"; + for(int i = 0; i < offset.length; i++) { + String txt = Long.toString(offset[i]); + while(txt.length() < 10) txt = "0" + txt; // must be exactly 10 characters long + if (i==0) xr = xr + txt + " 65535 f\r\n"; else xr = xr + txt + " 00000 n\r\n"; + } + // Trailer + xr = xr + "trailer\n<<\n/Size " + offset.length + "\n/Root " + catalogID + " 0 R\n>>\n" + "startxref\n" + now + "\n%%EOF\n"; + out(out, xr); + out.seek(offset[2]); + out(out, contentID + " 0 obj << /Length " + ct); // move the file pointer back so we can write out the real Content Size + out.close(); + buf = null; // only set buf to null if the file was saved successfully and no exception was thrown + } catch(Throwable ex) { + Util.close(out); + if (ex instanceof IOException) throw (IOException)ex; + if (ex instanceof OutOfMemoryError) throw new IOException("Out of memory trying to save the PDF file to " + filename); + if (ex instanceof StackOverflowError) throw new IOException("Out of memory trying to save the PDF file to " + filename); + throw new IOException("Error writing the PDF file to " + filename + " (" + ex + ")"); + } + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/OurPNGWriter.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/OurPNGWriter.java new file mode 100644 index 00000000..c22bad08 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/OurPNGWriter.java @@ -0,0 +1,148 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4; + +import java.io.IOException; +import java.io.File; +import java.io.RandomAccessFile; +import java.awt.image.BufferedImage; +import javax.imageio.ImageIO; + +/** Graphical convenience methods for producing PNG files. */ + +public final strictfp class OurPNGWriter { + + /** The constructor is private, since this utility class never needs to be instantiated. */ + private OurPNGWriter () { } + + /** Writes the image as a PNG file with the given horizontal and vertical dots-per-inch. */ + public static void writePNG (BufferedImage image, String filename, double dpiX, double dpiY) throws IOException { + try { + ImageIO.write(image, "PNG", new File(filename)); // some versions of Java sometimes throws an exception during saving... + setDPI(filename, dpiX, dpiY); + } catch(Throwable ex) { + if (ex instanceof IOException) throw (IOException)ex; + if (ex instanceof StackOverflowError) throw new IOException("Out of memory trying to save the PNG file to " + filename); + if (ex instanceof OutOfMemoryError) throw new IOException("Out of memory trying to save the PNG file to " + filename); + throw new IOException("Error writing the PNG file to " + filename + " (" + ex + ")"); + } + } + + /* PNG consists of a "8 byte header" followed by one or more CHUNK... + * + * Each CHUNK: + * =========== + * 4 bytes: an integer N expressed with most-significant-byte first + * 4 bytes: Chunk Type + * N bytes: Chunk Data + * 4 bytes: Checksum (this checksum is computed over the Chunk Type and Chunk Data) + * + * Each PNG must contain an IDAT chunk (this is the actual pixels of the image) + * + * Each PNG may contain an optional pHYs chunk that describes the horizontal and vertical dots-per-meter information. + * If such a chunk exists, it must come before (though not necessarily immediately before) the IDAT chunk. + * + * pHYs CHUNK: + * =========== + * 4 bytes: 0 , 0 , 0 , 9 + * 4 bytes: 'p' , 'H' , 'Y' , 's' + * 4 bytes: horizontal dots per meter (most-significant-byte first) + * 4 bytes: vertical dots per meter (most-significant-byte first) + * 1 byte: 1 + * 4 bytes: Checksum + */ + + /** Modifies the given PNG file to have the given horizontal and vertical dots-per-inch. */ + private static void setDPI (String filename, double dpiX, double dpiY) throws IOException { + RandomAccessFile f = null; + try { + f = new RandomAccessFile(filename, "rw"); + for(long total=f.length(), pos=8; pos>>24, x>>>16, x>>>8, x, y>>>24, y>>>16, y>>>8, y, 1}); + } + + /** Write the given chunk into the given file; Note: data.length must be at least 4. */ + private static void writeChunk (RandomAccessFile file, int[] data) throws IOException { + int crc = (-1), len = data.length - 4; + file.write((len>>>24) & 255); file.write((len>>>16) & 255); file.write((len>>>8) & 255); file.write(len & 255); + for(int i=0; i>> 8); file.write(x & 255); } + crc = crc ^ (-1); + file.write((crc>>>24) & 255); file.write((crc>>>16) & 255); file.write((crc>>>8) & 255); file.write(crc & 255); + } + + /** This precomputed table makes it faster to calculate CRC; this is based on the suggestion in the PNG specification. */ + private static final int[] table = new int[] { + 0,1996959894,-301047508,-1727442502,124634137,1886057615,-379345611,-1637575261,249268274 + ,2044508324,-522852066,-1747789432,162941995,2125561021,-407360249,-1866523247,498536548,1789927666 + ,-205950648,-2067906082,450548861,1843258603,-187386543,-2083289657,325883990,1684777152,-43845254 + ,-1973040660,335633487,1661365465,-99664541,-1928851979,997073096,1281953886,-715111964,-1570279054 + ,1006888145,1258607687,-770865667,-1526024853,901097722,1119000684,-608450090,-1396901568,853044451 + ,1172266101,-589951537,-1412350631,651767980,1373503546,-925412992,-1076862698,565507253,1454621731 + ,-809855591,-1195530993,671266974,1594198024,-972236366,-1324619484,795835527,1483230225,-1050600021 + ,-1234817731,1994146192,31158534,-1731059524,-271249366,1907459465,112637215,-1614814043,-390540237 + ,2013776290,251722036,-1777751922,-519137256,2137656763,141376813,-1855689577,-429695999,1802195444 + ,476864866,-2056965928,-228458418,1812370925,453092731,-2113342271,-183516073,1706088902,314042704 + ,-1950435094,-54949764,1658658271,366619977,-1932296973,-69972891,1303535960,984961486,-1547960204 + ,-725929758,1256170817,1037604311,-1529756563,-740887301,1131014506,879679996,-1385723834,-631195440 + ,1141124467,855842277,-1442165665,-586318647,1342533948,654459306,-1106571248,-921952122,1466479909 + ,544179635,-1184443383,-832445281,1591671054,702138776,-1328506846,-942167884,1504918807,783551873 + ,-1212326853,-1061524307,-306674912,-1698712650,62317068,1957810842,-355121351,-1647151185,81470997 + ,1943803523,-480048366,-1805370492,225274430,2053790376,-468791541,-1828061283,167816743,2097651377 + ,-267414716,-2029476910,503444072,1762050814,-144550051,-2140837941,426522225,1852507879,-19653770 + ,-1982649376,282753626,1742555852,-105259153,-1900089351,397917763,1622183637,-690576408,-1580100738 + ,953729732,1340076626,-776247311,-1497606297,1068828381,1219638859,-670225446,-1358292148,906185462 + ,1090812512,-547295293,-1469587627,829329135,1181335161,-882789492,-1134132454,628085408,1382605366 + ,-871598187,-1156888829,570562233,1426400815,-977650754,-1296233688,733239954,1555261956,-1026031705 + ,-1244606671,752459403,1541320221,-1687895376,-328994266,1969922972,40735498,-1677130071,-351390145 + ,1913087877,83908371,-1782625662,-491226604,2075208622,213261112,-1831694693,-438977011,2094854071 + ,198958881,-2032938284,-237706686,1759359992,534414190,-2118248755,-155638181,1873836001,414664567 + ,-2012718362,-15766928,1711684554,285281116,-1889165569,-127750551,1634467795,376229701,-1609899400 + ,-686959890,1308918612,956543938,-1486412191,-799009033,1231636301,1047427035,-1362007478,-640263460 + ,1088359270,936918000,-1447252397,-558129467,1202900863,817233897,-1111625188,-893730166,1404277552 + ,615818150,-1160759803,-841546093,1423857449,601450431,-1285129682,-1000256840,1567103746,711928724 + ,-1274298825,-1022587231,1510334235,755167117 + }; +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/OurSyntaxDocument.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/OurSyntaxDocument.java new file mode 100644 index 00000000..f2b7c051 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/OurSyntaxDocument.java @@ -0,0 +1,259 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4; + +import java.awt.Color; +import java.awt.Font; +import java.awt.image.BufferedImage; +import java.util.ArrayList; +import java.util.List; +import javax.swing.text.AttributeSet; +import javax.swing.text.BadLocationException; +import javax.swing.text.DefaultEditorKit; +import javax.swing.text.DefaultStyledDocument; +import javax.swing.text.MutableAttributeSet; +import javax.swing.text.SimpleAttributeSet; +import javax.swing.text.StyleConstants; +import javax.swing.text.TabSet; +import javax.swing.text.TabStop; +import static edu.mit.csail.sdg.alloy4.OurConsole.style; + +/** Graphical syntax-highlighting StyledDocument. + * + *

Thread Safety: Can be called only by the AWT event thread + */ + +class OurSyntaxDocument extends DefaultStyledDocument { + + /** This ensures the class can be serialized reliably. */ + private static final long serialVersionUID = 0; + + /** The "comment mode" at the start of each line (0 = no comment) (1 = block comment) (2 = javadoc comment) (-1 = unknown) */ + private final List comments = new ArrayList(); + + /** Whether syntax highlighting is currently enabled or not. */ + private boolean enabled = true; + + /** The current font name is. */ + private String font = "Monospaced"; + + /** The current font size. */ + private int fontSize = 14; + + /** The current tab size. */ + private int tabSize = 4; + + /** The list of font+color styles (eg. regular text, symbols, keywords, comments, etc). */ + private final List all = new ArrayList(); + + /** The character style for regular text. */ + private final MutableAttributeSet styleNormal = style(font, fontSize, false, Color.BLACK, 0); { all.add(styleNormal); } + + /** The character style for symbols. */ + private final MutableAttributeSet styleSymbol = style(font, fontSize, true, Color.BLACK, 0); { all.add(styleSymbol); } + + /** The character style for integer constants. */ + private final MutableAttributeSet styleNumber = style(font, fontSize, true, new Color(0xA80A0A), 0); { all.add(styleNumber); } + + /** The character style for keywords. */ + private final MutableAttributeSet styleKeyword = style(font, fontSize, true, new Color(0x1E1EA8), 0); { all.add(styleKeyword); } + + /** The character style for string literals. */ + private final MutableAttributeSet styleString = style(font, fontSize, false, new Color(0xA80AA8), 0); { all.add(styleString); } + + /** The character style for up-to-end-of-line-style comment. */ + private final MutableAttributeSet styleComment = style(font, fontSize, false, new Color(0x0A940A), 0); { all.add(styleComment); } + + /** The character style for non-javadoc-style block comment. */ + private final MutableAttributeSet styleBlock = style(font, fontSize, false, new Color(0x0A940A), 0); { all.add(styleBlock); } + + /** The character style for javadoc-style block comment. */ + private final MutableAttributeSet styleJavadoc = style(font, fontSize, true, new Color(0x0A940A), 0); { all.add(styleJavadoc); } + + /** The paragraph style for indentation. */ + private final MutableAttributeSet tabset = new SimpleAttributeSet(); + + /** This stores the currently recognized set of reserved keywords. */ + private static final String[] keywords = new String[] {"abstract", "all", "and", "as", "assert", "but", "check", "disj", + "disjoint", "else", "enum", "exactly", "exh", "exhaustive", "expect", "extends", "fact", "for", "fun", "iden", + "iff", "implies", "in", "Int", "int", "let", "lone", "module", "no", "none", "not", "one", "open", "or", "part", + "partition", "pred", "private", "run", "seq", "set", "sig", "some", "String", "sum", "this", "univ" + }; + + /** Returns true if array[start .. start+len-1] matches one of the reserved keyword. */ + private static final boolean do_keyword(String array, int start, int len) { + if (len >= 2 && len <= 10) for(int i = keywords.length - 1; i >= 0; i--) { + String str = keywords[i]; + if (str.length()==len) for(int j=0; ;j++) if (j==len) return true; else if (str.charAt(j) != array.charAt(start+j)) break; + } + return false; + } + + /** Returns true if "c" can be in the start or middle or end of an identifier. */ + private static final boolean do_iden(char c) { + return (c>='A' && c<='Z') || (c>='a' && c<='z') || c=='$' || (c>='0' && c<='9') || c=='_' || c=='\'' || c=='\"'; + } + + /** Constructor. */ + public OurSyntaxDocument(String fontName, int fontSize) { + putProperty(DefaultEditorKit.EndOfLineStringProperty, "\n"); + tabSize++; + do_setFont(fontName, fontSize, tabSize - 1); // assigns the given font, and also forces recomputation of the tab size + } + + /** Enables or disables syntax highlighting. */ + public final void do_enableSyntax (boolean flag) { + if (enabled == flag) return; else { enabled = flag; comments.clear(); } + if (flag) do_reapplyAll(); else setCharacterAttributes(0, getLength(), styleNormal, false); + } + + /** Return the number of lines represented by the current text (where partial line counts as a line). + *

For example: count("")==1, count("x")==1, count("x\n")==2, and count("x\ny")==2 + */ + public final int do_getLineCount() { + String txt = toString(); + for(int n=txt.length(), ans=1, i=0; ; i++) if (i>=n) return ans; else if (txt.charAt(i)=='\n') ans++; + } + + /** Return the starting offset of the given line (If "line" argument is too large, it will return the last line's starting offset) + *

For example: given "ab\ncd\n", start(0)==0, start(1)==3, start(2...)==6. Same thing when given "ab\ncd\ne". + */ + public final int do_getLineStartOffset(int line) { + String txt = toString(); + for(int n=txt.length(), ans=0, i=0, y=0; ; i++) if (i>=n || y>=line) return ans; else if (txt.charAt(i)=='\n') {ans=i+1; y++;} + } + + /** Return the line number that the offset is in (If "offset" argument is too large, it will just return do_getLineCount()-1). + *

For example: given "ab\ncd\n", offset(0..2)==0, offset(3..5)==1, offset(6..)==2. Same thing when given "ab\ncd\ne". + */ + public final int do_getLineOfOffset(int offset) { + String txt = toString(); + for(int n=txt.length(), ans=0, i=0; ; i++) if (i>=n || i>=offset) return ans; else if (txt.charAt(i)=='\n') ans++; + } + + /** This method is called by Swing to insert a String into this document. + * We intentionally ignore "attr" and instead use our own coloring. + */ + @Override public void insertString(int offset, String string, AttributeSet attr) throws BadLocationException { + if (string.indexOf('\r')>=0) string = Util.convertLineBreak(string); // we don't want '\r' + if (!enabled) { super.insertString(offset, string, styleNormal); return; } + int startLine = do_getLineOfOffset(offset); + for(int i = 0; i < string.length(); i++) { // For each inserted '\n' we need to shift the values in "comments" array down + if (string.charAt(i)=='\n') { if (startLine < comments.size()-1) comments.add(startLine+1, -1); } + } + super.insertString(offset, string, styleNormal); + try { do_update(startLine); } catch(Exception ex) { comments.clear(); } + } + + /** This method is called by Swing to delete text from this document. */ + @Override public void remove(int offset, int length) throws BadLocationException { + if (!enabled) { super.remove(offset, length); return; } + int i = 0, startLine = do_getLineOfOffset(offset); + for(String oldText = toString(); i 0) this.remove(offset, length); + if (string != null && string.length() > 0) this.insertString(offset, string, styleNormal); + } + + /** Reapply styles assuming the given line has just been modified */ + private final void do_update(int line) throws BadLocationException { + String content = toString(); + int lineCount = do_getLineCount(); + while(line>0 && (line>=comments.size() || comments.get(line)<0)) line--; // "-1" in comments array are always contiguous + int comment = do_reapply(line==0 ? 0 : comments.get(line), content, line); + for (line++; line < lineCount; line++) { // update each subsequent line until it already starts with its expected comment mode + if (line < comments.size() && comments.get(line) == comment) break; else comment = do_reapply(comment, content, line); + } + } + + /** Re-color the given line assuming it starts with a given comment mode, then return the comment mode for start of next line. */ + private final int do_reapply(int comment, final String txt, final int line) { + while (line >= comments.size()) comments.add(-1); // enlarge array if needed + comments.set(line, comment); // record the fact that this line starts with the given comment mode + for(int n = txt.length(), i = do_getLineStartOffset(line); i < n;) { + final int oldi = i; + final char c = txt.charAt(i); + if (c=='\n') break; + if (comment==0 && c=='/' && i0) { + AttributeSet style = (comment==1 ? styleBlock : styleJavadoc); + while(i='0' && c<='9') ? styleNumber : (do_keyword(txt, oldi, i-oldi) ? styleKeyword : styleNormal); + setCharacterAttributes(oldi, i-oldi, style, false); + } else { + for(i++; i 100) tabSize = 100; + if (fontName.equals(this.font) && fontSize == this.fontSize && tabSize == this.tabSize) return; + this.font = fontName; + this.fontSize = fontSize; + this.tabSize = tabSize; + for(MutableAttributeSet s: all) { StyleConstants.setFontFamily(s, fontName); StyleConstants.setFontSize(s, fontSize); } + do_reapplyAll(); + BufferedImage im = new BufferedImage(10, 10, BufferedImage.TYPE_INT_RGB); // this is used to derive the tab width + int gap = tabSize * im.createGraphics().getFontMetrics(new Font(fontName, Font.PLAIN, fontSize)).charWidth('X'); + TabStop[] pos = new TabStop[100]; + for(int i=0; i<100; i++) { pos[i] = new TabStop(i*gap + gap); } + StyleConstants.setTabSet(tabset, new TabSet(pos)); + setParagraphAttributes(0, getLength(), tabset, false); + } + + /** Overriden to return the full text of the document. + * @return the entire text + */ + @Override public String toString() { + try { return getText(0, getLength()); } catch(BadLocationException ex) { return ""; } + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/OurSyntaxUndoableDocument.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/OurSyntaxUndoableDocument.java new file mode 100644 index 00000000..a489ae38 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/OurSyntaxUndoableDocument.java @@ -0,0 +1,139 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4; + +import javax.swing.text.AttributeSet; +import javax.swing.text.BadLocationException; +import javax.swing.text.SimpleAttributeSet; +import static java.lang.System.arraycopy; + +/** Graphical syntax-highlighting StyledDocument with undo+redo support. + * + *

Thread Safety: Can be called only by the AWT event thread + */ + +final class OurSyntaxUndoableDocument extends OurSyntaxDocument { + + /** This ensures the class can be serialized reliably. */ + private static final long serialVersionUID = 0; + + /** The maximum number of UNDO actions we want to keep. */ + private static final int MAXUNDO = 100; + + /** For each i = 0..now-1, insert[i] means whether the i-th operation was an insertion or not. */ + private boolean insert[] = new boolean[MAXUNDO]; + + /** For each i = 0..now-1, text[i] means the i-th operation was an insertion/deletion of that text. */ + private String text[] = new String[MAXUNDO]; + + /** For each i = 0..now-1, where[i] means the i-th operation was an insertion/deletion at that offset. */ + private int where[] = new int[MAXUNDO]; + + /** The number of undoable operations currently remembered in insert[], text[], and where[]. */ + private int now; + + /** The number of undoable opeartions that are currently "undone". */ + private int undone; + + /** Caches a default AttributeSet (just so that we don't pass null as a AttributeSet. */ + private final AttributeSet attr = new SimpleAttributeSet(); + + /** Constructor. */ + public OurSyntaxUndoableDocument(String fontName, int fontSize) { super(fontName, fontSize); } + + /** Clear the undo history. */ + public void do_clearUndo() { now = undone = 0; } + + /** Returns true if we can perform undo right now. */ + public boolean do_canUndo() { return undone < now; } + + /** Returns true if we can perform redo right now. */ + public boolean do_canRedo() { return undone > 0 ; } + + /** Perform undo then return where the new desired caret location should be (or return -1 if undo is not possible right now) */ + public int do_undo() { + if (undone >= now) return -1; else undone++; + boolean insert = this.insert[now - undone]; + String text = this.text[now - undone]; + int where = this.where[now - undone]; + try { + if (insert) { super.remove(where, text.length()); return where; } + else { super.insertString(where, text, attr); return where + text.length(); } + } catch(BadLocationException ex) { return -1; } + } + + /** Perform redo then return where the new desired caret location should be (or return -1 if redo is not possible right now) */ + public int do_redo() { + if (undone <= 0) return -1; + boolean insert = this.insert[now - undone]; + String text = this.text[now - undone]; + int where = this.where[now - undone]; + undone--; + try { + if (insert) { super.insertString(where, text, attr); return where + text.length(); } + else { super.remove(where, text.length()); return where; } + } catch(BadLocationException ex) { return -1; } + } + + /** This method is called by Swing to insert a String into this document. */ + @Override public void insertString(int offset, String string, AttributeSet attr) throws BadLocationException { + if (string.length()==0) return; else if (string.indexOf('\r')>=0) string = Util.convertLineBreak(string); // we don't want '\r' + if (undone > 0) { now = now - undone; undone = 0; } // clear the REDO entries + super.insertString(offset, string, attr); + if (now > 0 && insert[now-1]) { // merge with last edit if possible + if (where[now-1] == offset - text[now-1].length()) { text[now - 1] += string; return; } + } + if (now >= MAXUNDO) { + arraycopy(insert, 1, insert, 0, MAXUNDO-1); + arraycopy(text, 1, text, 0, MAXUNDO-1); + arraycopy(where, 1, where, 0, MAXUNDO-1); + now--; + } + insert[now]=true; text[now]=string; where[now]=offset; now++; + } + + /** This method is called by Swing to delete text from this document. */ + @Override public void remove(int offset, int length) throws BadLocationException { + if (length==0) return; + if (undone > 0) { now = now - undone; undone = 0; } // clear the REDO entries + String string = toString().substring(offset, offset+length); + super.remove(offset, length); + if (now > 0 && !insert[now-1]) { // merge with last edit if possible + if (where[now-1] == offset) { text[now-1] += string; return; } + if (where[now-1] == offset+length) { where[now-1] = offset; text[now-1] = string + text[now-1]; return; } + } + if (now >= MAXUNDO) { + arraycopy(insert, 1, insert, 0, MAXUNDO-1); + arraycopy(text, 1, text, 0, MAXUNDO-1); + arraycopy(where, 1, where, 0, MAXUNDO-1); + now--; + } + insert[now]=false; text[now]=string; where[now]=offset; now++; + } + + /** This method is called by Swing to replace text in this document. */ + @Override public void replace(int offset, int length, String string, AttributeSet attrs) throws BadLocationException { + if (length > 0) this.remove(offset, length); + if (string != null && string.length() > 0) this.insertString(offset, string, this.attr); + } + + /** Overriden to return the full text of the document. + * @return the entire text + */ + @Override public String toString() { + try { return getText(0, getLength()); } catch(BadLocationException ex) { return ""; } + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/OurSyntaxWidget.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/OurSyntaxWidget.java new file mode 100644 index 00000000..8dee1630 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/OurSyntaxWidget.java @@ -0,0 +1,299 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.event.ActionEvent; +import java.awt.event.FocusAdapter; +import java.awt.event.FocusEvent; +import java.awt.event.InputEvent; +import java.awt.event.KeyEvent; +import java.io.File; +import java.util.Collection; +import javax.swing.AbstractAction; +import javax.swing.JComponent; +import javax.swing.JScrollPane; +import javax.swing.JTextPane; +import javax.swing.KeyStroke; +import javax.swing.border.EmptyBorder; +import javax.swing.event.CaretEvent; +import javax.swing.event.CaretListener; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import javax.swing.text.AbstractDocument; +import javax.swing.text.BoxView; +import javax.swing.text.Document; +import javax.swing.text.Element; +import javax.swing.text.StyledEditorKit; +import javax.swing.text.View; +import javax.swing.text.ViewFactory; +import edu.mit.csail.sdg.alloy4.Listener.Event; + +/** Graphical syntax-highlighting editor. + * + *

Thread Safety: Can be called only by the AWT event thread + */ + +public final class OurSyntaxWidget { + + /** The current list of listeners; possible events are { STATUS_CHANGE, FOCUSED, CTRL_PAGE_UP, CTRL_PAGE_DOWN, CARET_MOVED }. */ + public final Listeners listeners = new Listeners(); + + /** The JScrollPane containing everything. */ + private final JScrollPane component = OurUtil.make(new JScrollPane(), new EmptyBorder(0, 0, 0, 0)); + + /** This is an optional JComponent annotation. */ + public final JComponent obj1; + + /** This is an optional JComponent annotation. */ + public final JComponent obj2; + + /** The underlying StyledDocument being displayed (changes will trigger the STATUS_CHANGE event) */ + private final OurSyntaxUndoableDocument doc = new OurSyntaxUndoableDocument("Monospaced", 14); + + /** The underlying JTextPane being displayed. */ + private final JTextPane pane = OurAntiAlias.pane(Color.BLACK, Color.WHITE, new EmptyBorder(1, 1, 1, 1)); + + /** The filename for this JTextPane (changes will trigger the STATUS_CHANGE event) */ + private String filename = ""; + + /** Whether this JTextPane has been modified since last load/save (changes will trigger the STATUS_CHANGE event) */ + private boolean modified; + + /** Whether this JTextPane corresponds to an existing disk file (changes will trigger the STATUS_CHANGE event) */ + private boolean isFile; + + /** Caches the most recent background painter if nonnull. */ + private OurHighlighter painter; + + /** Constructs a syntax-highlighting widget. */ + public OurSyntaxWidget() { this(true, "", "Monospaced", 14, 4, null, null); } + + /** Constructs a syntax-highlighting widget. */ + @SuppressWarnings("serial") + public OurSyntaxWidget + (boolean enableSyntax, String text, String fontName, int fontSize, int tabSize, JComponent obj1, JComponent obj2) { + this.obj1 = obj1; + this.obj2 = obj2; + final OurSyntaxWidget me = this; + final ViewFactory defaultFactory = (new StyledEditorKit()).getViewFactory(); + doc.do_enableSyntax(enableSyntax); + doc.do_setFont(fontName, fontSize, tabSize); + pane.setEditorKit(new StyledEditorKit() { // Prevents line-wrapping up to width=30000, and tells it to use our Document obj + @Override public Document createDefaultDocument() { return doc; } + @Override public ViewFactory getViewFactory() { + return new ViewFactory() { + public View create(Element x) { + if (!AbstractDocument.SectionElementName.equals(x.getName())) return defaultFactory.create(x); + return new BoxView(x, View.Y_AXIS) { // 30000 is a good width to use here; value > 32767 appears to cause errors + @Override public final float getMinimumSpan(int axis) { return super.getPreferredSpan(axis); } + @Override public final void layout(int w, int h) { try {super.layout(30000, h);} catch(Throwable ex) {} } + }; + } + }; + } + }); + if (text.length()>0) { pane.setText(text); pane.setCaretPosition(0); } + doc.do_clearUndo(); + pane.getActionMap().put("alloy_copy", new AbstractAction("alloy_copy") { + public void actionPerformed(ActionEvent e) { pane.copy(); } + }); + pane.getActionMap().put("alloy_cut", new AbstractAction("alloy_cut") { + public void actionPerformed(ActionEvent e) { pane.cut(); } + }); + pane.getActionMap().put("alloy_paste", new AbstractAction("alloy_paste") { + public void actionPerformed(ActionEvent e) { pane.paste(); } + }); + pane.getActionMap().put("alloy_ctrl_pageup", new AbstractAction("alloy_ctrl_pageup") { + public void actionPerformed(ActionEvent e) { listeners.fire(me, Event.CTRL_PAGE_UP); } + }); + pane.getActionMap().put("alloy_ctrl_pagedown", new AbstractAction("alloy_ctrl_pagedown") { + public void actionPerformed(ActionEvent e) { listeners.fire(me, Event.CTRL_PAGE_DOWN); } + }); + pane.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_C, InputEvent.CTRL_MASK), "alloy_copy"); + pane.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_X, InputEvent.CTRL_MASK), "alloy_cut"); + pane.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_V, InputEvent.CTRL_MASK), "alloy_paste"); + pane.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_INSERT, InputEvent.CTRL_MASK), "alloy_copy"); + pane.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_INSERT, InputEvent.SHIFT_MASK), "alloy_paste"); + pane.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, InputEvent.SHIFT_MASK), "alloy_cut"); + pane.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_UP, InputEvent.CTRL_MASK), "alloy_ctrl_pageup"); + pane.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_DOWN, InputEvent.CTRL_MASK), "alloy_ctrl_pagedown"); + doc.addDocumentListener(new DocumentListener() { + public void insertUpdate(DocumentEvent e) { modified=true; listeners.fire(me, Event.STATUS_CHANGE); } + public void removeUpdate(DocumentEvent e) { modified=true; listeners.fire(me, Event.STATUS_CHANGE); } + public void changedUpdate(DocumentEvent e) { } + }); + pane.addFocusListener(new FocusAdapter() { + @Override public void focusGained(FocusEvent e) { listeners.fire(me, Event.FOCUSED); } + }); + pane.addCaretListener(new CaretListener() { + public void caretUpdate(CaretEvent e) { listeners.fire(me, Event.CARET_MOVED); } + }); + component.addFocusListener(new FocusAdapter() { + @Override public void focusGained(FocusEvent e) { pane.requestFocusInWindow(); } + }); + component.setFocusable(false); + component.setMinimumSize(new Dimension(50, 50)); + component.setViewportView(pane); + modified = false; + } + + /** Add this object into the given container. */ + public void addTo(JComponent newParent, Object constraint) { newParent.add(component, constraint); } + + /** Returns true if this textbox is currently shaded. */ + boolean shaded() { return pane.getHighlighter().getHighlights().length > 0; } + + /** Remove all shading. */ + void clearShade() { pane.getHighlighter().removeAllHighlights(); } + + /** Shade the range of text from start (inclusive) to end (exclusive). */ + void shade(Color color, int start, int end) { + int c = color.getRGB() & 0xFFFFFF; + if (painter==null || (painter.color.getRGB() & 0xFFFFFF)!=c) painter = new OurHighlighter(color); + try { pane.getHighlighter().addHighlight(start, end, painter); } catch(Throwable ex) { } // exception is okay + } + + /** Returns the filename. */ + public String getFilename() { return filename; } + + /** Returns the modified-or-not flag. */ + public boolean modified() { return modified; } + + /** Returns whether this textarea is based on an actual disk file. */ + public boolean isFile() { return isFile; } + + /** Changes the font name, font size, and tab size for the document. */ + void setFont(String fontName, int fontSize, int tabSize) { if (doc!=null) doc.do_setFont(fontName, fontSize, tabSize); } + + /** Enables or disables syntax highlighting. */ + void enableSyntax(boolean flag) { if (doc!=null) doc.do_enableSyntax(flag); } + + /** Return the number of lines represented by the current text (where partial line counts as a line). + *

For example: count("")==1, count("x")==1, count("x\n")==2, and count("x\ny")==2 + */ + public int getLineCount() { return doc.do_getLineCount(); } + + /** Return the starting offset of the given line (If "line" argument is too large, it will return the last line's starting offset) + *

For example: given "ab\ncd\n", start(0)==0, start(1)==3, start(2...)==6. Same thing when given "ab\ncd\ne". + */ + public int getLineStartOffset(int line) { return doc.do_getLineStartOffset(line); } + + /** Return the line number that the offset is in (If "offset" argument is too large, it will just return do_getLineCount()-1). + *

For example: given "ab\ncd\n", offset(0..2)==0, offset(3..5)==1, offset(6..)==2. Same thing when given "ab\ncd\ne". + */ + public int getLineOfOffset(int offset) { return doc.do_getLineOfOffset(offset); } + + /** Returns true if we can perform undo right now. */ + public boolean canUndo() { return doc.do_canUndo(); } + + /** Returns true if we can perform redo right now. */ + public boolean canRedo() { return doc.do_canRedo(); } + + /** Perform undo if possible. */ + public void undo() { int i = doc.do_undo(); if (i>=0 && i<=pane.getText().length()) moveCaret(i, i); } + + /** Perform redo if possible. */ + public void redo() { int i = doc.do_redo(); if (i>=0 && i<=pane.getText().length()) moveCaret(i, i); } + + /** Clear the undo history. */ + public void clearUndo() { doc.do_clearUndo(); } + + /** Return the caret position. */ + public int getCaret() { return pane.getCaretPosition(); } + + /** Select the content between offset a and offset b, and move the caret to offset b. */ + public void moveCaret(int a, int b) { + try { pane.setCaretPosition(a); pane.moveCaretPosition(b); } catch(Exception ex) { if (a!=0 || b!=0) moveCaret(0, 0); } + } + + /** Return the entire text. */ + public String getText() { return pane.getText(); } + + /** Change the entire text to the given text (and sets the modified flag) */ + public void setText(String text) { pane.setText(text); } + + /** Copy the current selection into the clipboard. */ + public void copy() { pane.copy(); } + + /** Cut the current selection into the clipboard. */ + public void cut() { pane.cut(); } + + /** Paste the current clipboard content. */ + public void paste() { pane.paste(); } + + /** Discard all; if askUser is true, we'll ask the user whether to save it or not if the modified==true. + * @return true if this text buffer is now a fresh empty text buffer + */ + boolean discard(boolean askUser, Collection bannedNames) { + char ans = (!modified || !askUser) ? 'd' : OurDialog.askSaveDiscardCancel("The file \"" + filename + "\""); + if (ans=='c' || (ans=='s' && save(false, bannedNames)==false)) return false; + for(int i=1; ;i++) if (!bannedNames.contains(filename = Util.canon("Untitled " + i + ".als"))) break; + pane.setText(""); clearUndo(); modified=false; isFile=false; listeners.fire(this, Event.STATUS_CHANGE); + return true; + } + + /** Discard current content then read the given file; return true if the entire operation succeeds. */ + boolean load(String filename) { + String x; + try { + x = Util.readAll(filename); + } catch(Throwable ex) { OurDialog.alert("Error reading the file \"" + filename + "\""); return false; } + pane.setText(x); moveCaret(0,0); clearUndo(); modified=false; isFile=true; this.filename=filename; + listeners.fire(this, Event.STATUS_CHANGE); + return true; + } + + /** Discard (after confirming with the user) current content then reread from disk file. */ + void reload() { + if (!isFile) return; // "untitled" text buffer does not have a on-disk file to refresh from + if (modified && !OurDialog.yesno("You have unsaved changes to \"" + filename + "\"\nAre you sure you wish to discard " + + "your changes and reload it from disk?", "Discard your changes", "Cancel this operation")) return; + String t; + try { t=Util.readAll(filename); } catch(Throwable ex) { OurDialog.alert("Cannot read \""+filename+"\""); return; } + if (modified==false && t.equals(pane.getText())) return; // no text change nor status change + int c=pane.getCaretPosition(); pane.setText(t); moveCaret(c,c); modified=false; listeners.fire(this, Event.STATUS_CHANGE); + } + + /** Save the current tab content to the file system, and return true if successful. */ + boolean saveAs(String filename, Collection bannedNames) { + filename = Util.canon(filename); + if (bannedNames.contains(filename)) { + OurDialog.alert("The filename \""+filename+"\"\nis already open in another tab."); + return false; + } + try { Util.writeAll(filename, pane.getText()); } + catch (Throwable e) { OurDialog.alert("Error writing to the file \""+filename+"\""); return false; } + this.filename = Util.canon(filename); // a new file's canonical name may change + modified=false; isFile=true; listeners.fire(this, Event.STATUS_CHANGE); return true; + } + + /** Save the current tab content to the file system and return true if successful. + * @param alwaysPickNewName - if true, it will always pop up a File Selection dialog box to ask for the filename + */ + boolean save(boolean alwaysPickNewName, Collection bannedNames) { + String n = this.filename; + if (alwaysPickNewName || isFile==false || n.startsWith(Util.jarPrefix())) { + File f = OurDialog.askFile(false, null, ".als", ".als files"); if (f==null) return false; + n = Util.canon(f.getPath()); if (f.exists() && !OurDialog.askOverwrite(n)) return false; + } + if (saveAs(n, bannedNames)) {Util.setCurrentDirectory(new File(filename).getParentFile()); return true;} else return false; + } + + /** Transfer focus to this component. */ + public void requestFocusInWindow() { if (pane!=null) pane.requestFocusInWindow(); } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/OurTabbedSyntaxWidget.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/OurTabbedSyntaxWidget.java new file mode 100644 index 00000000..202887ca --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/OurTabbedSyntaxWidget.java @@ -0,0 +1,298 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Font; +import java.awt.Rectangle; +import java.awt.event.ComponentEvent; +import java.awt.event.ComponentListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.LinkedHashMap; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import edu.mit.csail.sdg.alloy4.Listener.Event; + +/** Graphical multi-tabbed syntax-highlighting editor. + * + *

Thread Safety: Can be called only by the AWT event thread. + * + *

Invariant: each tab has distinct file name. + */ + +public final class OurTabbedSyntaxWidget { + + /** The current list of listeners; possible events are { STATUS_CHANGE, FOCUSED, CARET_MOVED }. */ + public final Listeners listeners = new Listeners(); + + /** The JScrollPane containing everything. */ + private final JPanel component = OurUtil.make(new JPanel()); + + /** Background color for the list of tabs. */ + private static final Color GRAY = new Color(0.9f, 0.9f, 0.9f); + + /** Background color for an inactive tab. */ + private static final Color INACTIVE = new Color(0.8f, 0.8f, 0.8f); + + /** Background color for a inactive and highlighted tab. */ + private static final Color INACTIVE_HIGHLIGHTED = new Color(0.7f, 0.5f, 0.5f); + + /** Foreground color for a active and highlighted tab. */ + private static final Color ACTIVE_HIGHLIGHTED = new Color(0.5f, 0.2f, 0.2f); + + /** Default border color for each tab. */ + private static final Color BORDER = Color.LIGHT_GRAY; + + /** The font name to use in the JTextArea */ + private String fontName = "Monospaced"; + + /** The font size to use in the JTextArea */ + private int fontSize = 14; + + /** The tabsize to use in the JTextArea */ + private int tabSize = 4; + + /** Whether syntax highlighting is current enabled or not. */ + private boolean syntaxHighlighting; + + /** The list of clickable tabs. */ + private final JPanel tabBar; + + /** The scrollPane that wraps around this.tabbar */ + private final JScrollPane tabBarScroller; + + /** The list of tabs. */ + private final List tabs = new ArrayList(); + + /** The currently selected tab from 0 to list.size()-1 (This value must be 0 if there are no tabs) */ + private int me = 0; + + /** This object receives messages from sub-JTextPane objects. */ + private final Listener listener = new Listener() { + public Object do_action(Object sender, Event e) { + final OurTabbedSyntaxWidget me = OurTabbedSyntaxWidget.this; + if (sender instanceof OurSyntaxWidget) switch(e) { + case FOCUSED: listeners.fire(me, e); break; + case CARET_MOVED: listeners.fire(me, Event.STATUS_CHANGE); break; + case CTRL_PAGE_UP: prev(); break; + case CTRL_PAGE_DOWN: next(); break; + case STATUS_CHANGE: + clearShade(); + OurSyntaxWidget t = (OurSyntaxWidget)sender; + String n = t.getFilename(); + t.obj1.setToolTipText(n); + int i = n.lastIndexOf('/'); if (i >= 0) n = n.substring(i + 1); + i = n.lastIndexOf('\\'); if (i >= 0) n = n.substring(i + 1); + i = n.lastIndexOf('.'); if (i >= 0) n = n.substring(0, i); + if (n.length() > 14) { n = n.substring(0, 14) + "..."; } + if (t.obj1 instanceof JLabel) { ((JLabel)(t.obj1)).setText(" " + n + (t.modified() ? " * " : " ")); } + listeners.fire(me, Event.STATUS_CHANGE); + break; + } + return true; + } + public Object do_action(Object sender, Event e, Object arg) { return true; } + }; + + /** Constructs a tabbed editor pane. */ + public OurTabbedSyntaxWidget(String fontName, int fontSize, int tabSize) { + component.setBorder(null); + component.setLayout(new BorderLayout()); + JPanel glue = OurUtil.makeHB(new Object[]{null}); + glue.setBorder(new OurBorder(null, null, BORDER, null)); + tabBar = OurUtil.makeHB(glue); + if (!Util.onMac()) { tabBar.setOpaque(true); tabBar.setBackground(GRAY); } + tabBarScroller = new JScrollPane(tabBar, JScrollPane.VERTICAL_SCROLLBAR_NEVER, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); + tabBarScroller.setFocusable(false); + tabBarScroller.setBorder(null); + setFont(fontName, fontSize, tabSize); + newtab(null); + tabBarScroller.addComponentListener(new ComponentListener() { + public final void componentResized(ComponentEvent e) { select(me); } + public final void componentMoved(ComponentEvent e) { select(me); } + public final void componentShown(ComponentEvent e) { select(me); } + public final void componentHidden(ComponentEvent e) { } + }); + } + + /** Add this object into the given container. */ + public void addTo(JComponent newParent, Object constraint) { newParent.add(component, constraint); } + + /** Adjusts the background and foreground of all labels. */ + private void adjustLabelColor() { + for(int i=0; i= tabs.size()) return; else { me=i; component.revalidate(); adjustLabelColor(); component.removeAll(); } + if (tabs.size() > 1) component.add(tabBarScroller, BorderLayout.NORTH); + tabs.get(me).addTo(component, BorderLayout.CENTER); + component.repaint(); + tabs.get(me).requestFocusInWindow(); + tabBar.scrollRectToVisible(new Rectangle(0,0,0,0)); // Forces recalculation + tabBar.scrollRectToVisible(new Rectangle(tabs.get(me).obj2.getX(), 0, tabs.get(me).obj2.getWidth()+200, 1)); + listeners.fire(this, Event.STATUS_CHANGE); + } + + /** Refresh all tabs. */ + public void reloadAll() { for(OurSyntaxWidget t: tabs) t.reload(); } + + /** Return the list of all filenames except the filename in the i-th tab. */ + private List getAllNamesExcept(int i) { + ArrayList ans = new ArrayList(); + for(int x=0; ;x++) if (x == tabs.size()) return ans; else if (x != i) ans.add(tabs.get(x).getFilename()); + } + + /** Save the current tab content to the file system. + * @param alwaysPickNewName - if true, it will always pop up a File Selection dialog box to ask for the filename + * @return null if an error occurred (otherwise, return the filename) + */ + public String save(boolean alwaysPickNewName) { + if (me < 0 || me >= tabs.size() || !tabs.get(me).save(alwaysPickNewName, getAllNamesExcept(me))) return null; + return tabs.get(me).getFilename(); + } + + /** Close the i-th tab (if there are no more tabs afterwards, we'll create a new empty tab). + * + *

If the text editor content is not modified since the last save, then return true; otherwise, ask the user. + *

If the user says to save it, we will attempt to save it, then return true iff the save succeeded. + *

If the user says to discard it, this method returns true. + *

If the user says to cancel it, this method returns false (and the original tab and its contents will not be harmed). + */ + private boolean close(int i) { + clearShade(); + if (i<0 || i>=tabs.size()) return true; else if (!tabs.get(i).discard(true, getAllNamesExcept(i))) return false; + if (tabs.size() > 1) { tabBar.remove(i); tabs.remove(i); if (me >= tabs.size()) me = tabs.size() - 1; } + select(me); + return true; + } + + /** Close the current tab (then create a new empty tab if there were no tabs remaining) */ + public void close() { close(me); } + + /** Close every tab then create a new empty tab; returns true iff success. */ + public boolean closeAll() { + for(int i=tabs.size()-1; i>=0; i--) if (tabs.get(i).modified()==false) close(i); // first close all the unmodified files + for(int i=tabs.size()-1; i>=0; i--) if (close(i)==false) return false; // then attempt to close modified files one-by-one + return true; + } + + /** Returns the number of tabs. */ + public int count() { return tabs.size(); } + + /** Return a modifiable map from each filename to its text content (Note: changes to the map does NOT affect this object) */ + public Map takeSnapshot() { + Map map = new LinkedHashMap(); + for(OurSyntaxWidget t: tabs) { map.put(t.getFilename(), t.getText()); } + return map; + } + + /** Returns the list of filenames corresponding to each text buffer. */ + public List getFilenames() { return getAllNamesExcept(-1); } + + /** Changes the font name, font size, and tabsize of every text buffer. */ + public void setFont(String name, int size, int tabSize) { + fontName=name; fontSize=size; this.tabSize=tabSize; for(OurSyntaxWidget t: tabs) t.setFont(name, size, tabSize); + } + + /** Enables or disables syntax highlighting. */ + public void enableSyntax(boolean flag) { syntaxHighlighting=flag; for(OurSyntaxWidget t: tabs) t.enableSyntax(flag); } + + /** Returns the JTextArea of the current text buffer. */ + public OurSyntaxWidget get() { return (me>=0 && me=0 && i=2) select(me==0 ? tabs.size()-1 : (me-1)); } + + /** Switches to the next tab. */ + public void next() { if (tabs.size()>=2) select(me==tabs.size()-1 ? 0 : (me+1)); } + + /** Create a new tab with the given filename (if filename==null, we'll create a blank tab instead) + *

If a text buffer with that filename already exists, we will just switch to it; else we'll read that file into a new tab. + * @return false iff an error occurred + */ + public boolean newtab(String filename) { + if (filename!=null) { + filename = Util.canon(filename); + for(int i=0; i=0; i--) if (!tabs.get(i).isFile() && tabs.get(i).getText().length()==0) { + tabs.get(i).discard(false, getFilenames()); close(i); break; // Remove the rightmost untitled empty tab + } + } + select(tabs.size() - 1); // Must call this to switch to the new tab; and it will fire STATUS_CHANGE message which is important + return true; + } + + /** Highlights the text editor, based on the location information in the set of Pos objects. */ + public void shade(Iterable set, Color color, boolean clearOldHighlightsFirst) { + if (clearOldHighlightsFirst) clearShade(); + OurSyntaxWidget text = null; + int c = 0, d; + for(Pos p: set) if (p!=null && p.filename.length()>0 && p.y>0 && p.x>0 && newtab(p.filename)) { + text = get(); + c = text.getLineStartOffset(p.y-1) + p.x - 1; + d = text.getLineStartOffset(p.y2-1) + p.x2 - 1; + text.shade(color, c, d+1); + } + if (text!=null) { text.moveCaret(0, 0); text.moveCaret(c, c); } // Move to 0 ensures we'll scroll to the highlighted section + get().requestFocusInWindow(); + adjustLabelColor(); + listeners.fire(this, Event.STATUS_CHANGE); + } + + /** Highlights the text editor, based on the location information in the Pos object. */ + public void shade(Pos pos) { shade(Util.asList(pos), new Color(0.9f, 0.4f, 0.4f), true); } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/OurTree.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/OurTree.java new file mode 100644 index 00000000..26f959bb --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/OurTree.java @@ -0,0 +1,164 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4; + +import java.awt.Color; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.Graphics; +import java.util.IdentityHashMap; +import java.util.List; +import javax.swing.JLabel; +import javax.swing.JTree; +import javax.swing.UIManager; +import javax.swing.border.EmptyBorder; +import javax.swing.event.TreeModelListener; +import javax.swing.event.TreeSelectionEvent; +import javax.swing.event.TreeSelectionListener; +import javax.swing.tree.TreeCellRenderer; +import javax.swing.tree.TreeModel; +import javax.swing.tree.TreePath; +import javax.swing.tree.TreeSelectionModel; +import edu.mit.csail.sdg.alloy4.OurUtil; + +/** Graphical tree. + * + *

Thread Safety: Can be called only by the AWT event thread. + */ + +public abstract class OurTree extends JTree { + + /** The current list of listeners; whenever a node X is selected we'll send (Event.CLICK, X) to each listener. */ + public final Listeners listeners = new Listeners(); + + /** Custom TreeCellRenderer to print the tree nodes better. (The idea of using JLabel is inspired by DefaultTreeCellRenderer) */ + private final class OurTreeRenderer extends JLabel implements TreeCellRenderer { + /** This ensures the class can be serialized reliably. */ + private static final long serialVersionUID = 0; + /** This stores the height of one line of text. */ + private int height; + /** If preferredHeight > 0, then preferredWidth is the desired width for the current object being drawn. */ + private int preferredWidth = 0; + /** If preferredHeight > 0, then preferredHeight is the desired height for the current object being drawn. */ + private int preferredHeight = 0; + /** Whether the current object is selected or not. */ + private boolean isSelected; + /** Whether the current object is focused or not. */ + private boolean isFocused; + /** Constructs this renderer. */ + public OurTreeRenderer(int fontSize) { + super("Anything"); // This ensures that the height is calculated properly + setFont(OurUtil.getVizFont().deriveFont((float)fontSize)); + setVerticalAlignment(JLabel.BOTTOM); + setBorder(new EmptyBorder(0, 3, 0, 3)); + setText("Anything"); // So that we can derive the height + height = super.getPreferredSize().height; + } + /** This method is called by Swing to return an object to be drawn. */ + public Component getTreeCellRendererComponent + (JTree tree, Object value, boolean isSelected, boolean expanded, boolean isLeaf, int row, boolean isFocused) { + String string = tree.convertValueToText(value, isSelected, expanded, isLeaf, row, isFocused); + this.isFocused = isFocused; + this.isSelected = isSelected; + setText(string); + setForeground(UIManager.getColor(isSelected ? "Tree.selectionForeground" : "Tree.textForeground")); + preferredHeight = 0; // By default, don't override width/height + if (do_isDouble(value)) { Dimension d = super.getPreferredSize(); preferredWidth=d.width+3; preferredHeight=d.height*2; } + return this; + } + /** We override the getPreferredSize() method to return a custom size if do_isDouble() returns true for that value. */ + @Override public Dimension getPreferredSize() { + if (preferredHeight > 0) return new Dimension(preferredWidth, preferredHeight); + Dimension d = super.getPreferredSize(); + return new Dimension(d.width+3, d.height); + } + /** We override the paint() method to avoid drawing the box around the "extra space" (if height is double height) */ + @Override public void paint(Graphics g) { + int w=getWidth(), h=getHeight(), y=h-height; + Color background = isSelected ? UIManager.getColor("Tree.selectionBackground") : Color.WHITE; + Color border = isFocused ? UIManager.getColor("Tree.selectionBorderColor") : null; + if (background!=null) { g.setColor(background); g.fillRect(0, y, w, h-y); } + if (border!=null && isSelected) { g.setColor(border); g.drawRect(0, y, w-1, h-1-y); } + super.paint(g); + } + } + + /** This ensures the class can be serialized reliably. */ + private static final long serialVersionUID = 0; + + /** Subclass should implement this method to return the root of the tree. */ + public abstract Object do_root(); + + /** Subclass should implement this method to return the list of children nodes given a particular node. */ + public abstract List do_ask(Object parent); + + /** Subclass should override this method to return whether a given item should be double-height or not (default = no). */ + protected boolean do_isDouble(Object object) { return false; } + + /** Subclass should call this when all fields are initialized; we won't call do_root() and do_ask() until subclass calls this. */ + protected final void do_start() { + // Create a custom TreeModel that calls do_root() and do_ask() whenever the tree needs expansion + setModel(new TreeModel() { + // Cache the parent->child list so that we always get the exact same OBJECT REFERENCE when navigating the tree + private final IdentityHashMap> map = new IdentityHashMap>(); + public Object getChild(Object parent, int index) { + List ans = map.get(parent); + if (ans==null) { ans = do_ask(parent); map.put(parent, ans); } + return (index >= 0 && index < ans.size()) ? ans.get(index) : null; + } + public int getIndexOfChild(Object parent, Object child) { + getChild(parent, 0); + List ans = map.get(parent); + for(int i=0; ;i++) if (i==ans.size()) return -1; else if (ans.get(i)==child) return i; + } + public Object getRoot() { return do_root(); } + public int getChildCount(Object node) { getChild(node, 0); return map.get(node).size(); } + public boolean isLeaf(Object node) { getChild(node, 0); return map.get(node).isEmpty(); } + public void valueForPathChanged(TreePath path, Object newValue) { } + public void addTreeModelListener(TreeModelListener l) { } + public void removeTreeModelListener(TreeModelListener l) { } + }); + } + + /** This method is called by Swing to figure out what text should be displayed for each node in the tree. */ + @Override public abstract String convertValueToText(Object v, boolean select, boolean expand, boolean leaf, int i, boolean focus); + + /** Construct a Tree object with the given font size. */ + public OurTree(int fontSize) { + Font font = OurUtil.getVizFont().deriveFont((float)fontSize); + OurTreeRenderer renderer = new OurTreeRenderer(fontSize); + renderer.setFont(font); + renderer.invalidate(); + renderer.validate(); + setRowHeight(0); // To allow variable row height on Mac OS X + setCellRenderer(renderer); + setFont(font); + setBorder(new EmptyBorder(8, 8, 2, 2)); + getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); + putClientProperty("JTree.lineStyle", "Angled"); + setRootVisible(true); + setForeground(Color.BLACK); + setBackground(Color.WHITE); + setOpaque(true); + addTreeSelectionListener(new TreeSelectionListener() { + public void valueChanged(TreeSelectionEvent e) { + TreePath path = OurTree.this.getSelectionPath(); + if (path!=null) OurTree.this.listeners.fire(OurTree.this, Listener.Event.CLICK, path.getLastPathComponent()); + } + }); + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/OurUtil.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/OurUtil.java new file mode 100644 index 00000000..b41959ac --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/OurUtil.java @@ -0,0 +1,327 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4; + +import java.awt.Color; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.Toolkit; +import java.awt.event.ActionListener; +import java.awt.event.InputEvent; +import java.awt.event.KeyEvent; +import java.awt.image.BufferedImage; +import java.net.URL; +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.Icon; +import javax.swing.ImageIcon; +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JMenu; +import javax.swing.JMenuBar; +import javax.swing.JMenuItem; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTextArea; +import javax.swing.JTextField; +import javax.swing.JSplitPane; +import javax.swing.KeyStroke; +import javax.swing.border.Border; +import javax.swing.border.EmptyBorder; +import javax.swing.event.MenuEvent; +import javax.swing.event.MenuListener; +import javax.swing.plaf.basic.BasicSplitPaneUI; + +/** Graphical convenience methods. + * + *

Thread Safety: Can be called only by the AWT event thread. + */ + +public final class OurUtil { + + /** This constructor is private, since this utility class never needs to be instantiated. */ + private OurUtil() { } + + /** Assign the given attributes to the given JComponent, then return the JComponent again. + *

If Font x is given in the list, we call obj.setFont(x) + *

If String x is given in the list, we call obj.setToolTipText(x) + *

If Border x is given in the list, we call obj.setBorder(x) + *

If Dimension x is given in the list, we call obj.setPreferredSize(x) + *

If Color x is given in the list, and it's the first color, we call obj.setForeground(x) + *

If Color x is given in the list, and it's not the first color, we call obj.setBackground(x) then obj.setOpaque(true) + *

(If no Font is given, then after all these changes have been applied, we will call obj.setFont() will a default font) + */ + public static X make(X obj, Object... attributes) { + boolean hasFont = false, hasForeground = false; + if (attributes!=null) for(Object x: attributes) { + if (x instanceof Font) { obj.setFont((Font)x); hasFont=true; } + if (x instanceof String) { obj.setToolTipText((String)x); } + if (x instanceof Border) { obj.setBorder((Border)x); } + if (x instanceof Dimension) { obj.setPreferredSize((Dimension)x); } + if (x instanceof Color && !hasForeground) { obj.setForeground((Color)x); hasForeground=true; continue; } + if (x instanceof Color) { obj.setBackground((Color)x); obj.setOpaque(true); } + } + if (!hasFont) obj.setFont(getVizFont()); + return obj; + } + + /** Make a JLabel, then call Util.make() to apply a set of attributes to it. + * @param attributes - see {@link edu.mit.csail.sdg.alloy4.OurUtil#make OurUtil.make(component, attributes...)} + */ + public static JLabel label (String label, Object... attributes) { return make(new JLabel(label), attributes); } + + /** Make a JTextField with the given text and number of columns, then call Util.make() to apply a set of attributes to it. + * @param attributes - see {@link edu.mit.csail.sdg.alloy4.OurUtil#make OurUtil.make(component, attributes...)} + */ + public static JTextField textfield (String text, int columns, Object... attributes) { + return make(new JTextField(text, columns), attributes); + } + + /** Make a JTextArea with the given text and number of rows and columns, then call Util.make() to apply a set of attributes to it. + * @param attributes - see {@link edu.mit.csail.sdg.alloy4.OurUtil#make OurUtil.make(component, attributes...)} + */ + public static JTextArea textarea (String text, int rows, int columns, boolean editable, boolean wrap, Object... attributes) { + JTextArea ans = make(new JTextArea(text, rows, columns), Color.BLACK, Color.WHITE, new EmptyBorder(0,0,0,0)); + ans.setEditable(editable); + ans.setLineWrap(wrap); + ans.setWrapStyleWord(wrap); + return make(ans, attributes); + } + + /** Make a JScrollPane containing the given component (which can be null), then apply a set of attributes to it. + * @param attributes - see {@link edu.mit.csail.sdg.alloy4.OurUtil#make OurUtil.make(component, attributes...)} + */ + public static JScrollPane scrollpane (Component component, Object... attributes) { + JScrollPane ans = make(new JScrollPane(), new EmptyBorder(0,0,0,0)); + if (component!=null) ans.setViewportView(component); + ans.setMinimumSize(new Dimension(50, 50)); + return make(ans, attributes); + } + + /** Returns the recommended font to use in the visualizer, based on the OS. */ + public static Font getVizFont() { + return Util.onMac() ? new Font("Lucida Grande", Font.PLAIN, 11) : new Font("Dialog", Font.PLAIN, 12); + } + + /** Returns the screen width (in pixels). */ + public static int getScreenWidth() { return Toolkit.getDefaultToolkit().getScreenSize().width; } + + /** Returns the screen height (in pixels). */ + public static int getScreenHeight() { return Toolkit.getDefaultToolkit().getScreenSize().height; } + + /** Make a graphical button + * @param label - the text to show beneath the button + * @param tip - the tooltip to show when the mouse hovers over the button + * @param iconFileName - if nonnull, it's the filename of the icon to show (it will be loaded from an accompanying jar file) + * @param func - if nonnull, it's the function to call when the button is pressed + */ + public static JButton button (String label, String tip, String iconFileName, ActionListener func) { + JButton button = new JButton(label, (iconFileName!=null && iconFileName.length()>0) ? loadIcon(iconFileName) : null); + if (func != null) button.addActionListener(func); + button.setVerticalTextPosition(JButton.BOTTOM); + button.setHorizontalTextPosition(JButton.CENTER); + button.setBorderPainted(false); + button.setFocusable(false); + if (!Util.onMac()) button.setBackground(new Color(0.9f, 0.9f, 0.9f)); + button.setFont(button.getFont().deriveFont(10.0f)); + if (tip != null && tip.length() > 0) button.setToolTipText(tip); + return button; + } + + /** Load the given image file from an accompanying JAR file, and return it as an Icon object. */ + public static Icon loadIcon(String pathname) { + URL url = OurUtil.class.getClassLoader().getResource(pathname); + if (url!=null) return new ImageIcon(Toolkit.getDefaultToolkit().createImage(url)); + return new ImageIcon(new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB)); + } + + /** Make a JPanel with horizontal or vertical BoxLayout, then add the list of components to it (each aligned by xAlign and yAlign) + *
If a component is Color, it's the background of this JPanel and every component after it (until we see another Color) + *
If a component is Dimension, we will set it as the newly constructed JPanel's preferedSize and MaximumSize. + *
If a component is String, we will insert a JLabel with it as the label. + *
If a component is Integer, we will insert an "n*1" (or "1*n") rigid area instead. + *
If a component is null, we will insert a horizontal (or vertical) glue instead. + */ + private static JPanel makeBox(boolean horizontal, float xAlign, float yAlign, Object[] components) { + JPanel ans = new JPanel(); + ans.setLayout(new BoxLayout(ans, horizontal ? BoxLayout.X_AXIS : BoxLayout.Y_AXIS)); + ans.setAlignmentX(0.0f); + ans.setAlignmentY(0.0f); + Color color = null; + for(Object x: components) { + Component c = null; + if (x instanceof Color) { color = (Color)x; ans.setBackground(color); continue; } + if (x instanceof Dimension) { ans.setPreferredSize((Dimension)x); ans.setMaximumSize((Dimension)x); continue; } + if (x instanceof Component) { c = (Component)x; } + if (x instanceof String) { c = label((String)x, Color.BLACK); } + if (x instanceof Integer) { int i = (Integer)x; c = Box.createRigidArea(new Dimension(horizontal?i:1, horizontal?1:i)); } + if (x==null) { c = horizontal ? Box.createHorizontalGlue() : Box.createVerticalGlue(); } + if (c==null) continue; + if (color!=null) c.setBackground(color); + if (c instanceof JComponent) { ((JComponent)c).setAlignmentX(xAlign); ((JComponent)c).setAlignmentY(yAlign); } + ans.add(c); + } + return ans; + } + + /** Make a JPanel using horizontal BoxLayout, and add the components to it (each component will be center-aligned). + *
If a component is Color, it's the background of this JPanel and every component after it (until we see another Color) + *
If a component is Dimension, we will set it as the newly constructed JPanel's preferedSize and MaximumSize. + *
If a component is String, we will insert a JLabel with it as the label. + *
If a component is Integer, we will insert an "n*1" (or "1*n") rigid area instead. + *
If a component is null, we will insert a horizontal (or vertical) glue instead. + */ + public static JPanel makeH(Object... components) { return makeBox(true, 0.5f, 0.5f, components); } + + /** Make a JPanel using horizontal BoxLayout, and add the components to it (each component will be top-aligned). + *
If a component is Color, it's the background of this JPanel and every component after it (until we see another Color) + *
If a component is Dimension, we will set it as the newly constructed JPanel's preferedSize and MaximumSize. + *
If a component is String, we will insert a JLabel with it as the label. + *
If a component is Integer, we will insert an "n*1" (or "1*n") rigid area instead. + *
If a component is null, we will insert a horizontal (or vertical) glue instead. + */ + public static JPanel makeHT(Object... components) { return makeBox(true, 0.5f, 0.0f, components); } + + /** Make a JPanel using horizontal BoxLayout, and add the components to it (each component will be bottom-aligned). + *
If a component is Color, it's the background of this JPanel and every component after it (until we see another Color) + *
If a component is Dimension, we will set it as the newly constructed JPanel's preferedSize and MaximumSize. + *
If a component is String, we will insert a JLabel with it as the label. + *
If a component is Integer, we will insert an "n*1" (or "1*n") rigid area instead. + *
If a component is null, we will insert a horizontal (or vertical) glue instead. + */ + public static JPanel makeHB(Object... components) { return makeBox(true, 0.5f, 1.0f, components); } + + /** Make a JPanel using vertical BoxLayout, and add the components to it (each component will be left-aligned). + *
If a component is Color, it's the background of this JPanel and every component after it (until we see another Color) + *
If a component is Dimension, we will set it as the newly constructed JPanel's preferedSize and MaximumSize. + *
If a component is String, we will insert a JLabel with it as the label. + *
If a component is Integer, we will insert an "n*1" (or "1*n") rigid area instead. + *
If a component is null, we will insert a horizontal (or vertical) glue instead. + */ + public static JPanel makeVL(Object... components) { return makeBox(false, 0.0f, 0.5f, components); } + + /** Make a JPanel using vertical BoxLayout, and add the components to it (each component will be right-aligned). + *
If a component is Color, it's the background of this JPanel and every component after it (until we see another Color) + *
If a component is Dimension, we will set it as the newly constructed JPanel's preferedSize and MaximumSize. + *
If a component is String, we will insert a JLabel with it as the label. + *
If a component is Integer, we will insert an "n*1" (or "1*n") rigid area instead. + *
If a component is null, we will insert a horizontal (or vertical) glue instead. + */ + public static JPanel makeVR(Object... components) { return makeBox(false, 1.0f, 0.5f, components); } + + /** Constructs a new SplitPane containing the two components given as arguments + * @param orientation - the orientation (HORIZONTAL_SPLIT or VERTICAL_SPLIT) + * @param first - the left component (if horizontal) or top component (if vertical) + * @param second - the right component (if horizontal) or bottom component (if vertical) + * @param initialDividerLocation - the initial divider location (in pixels) + */ + public static JSplitPane splitpane (int orientation, Component first, Component second, int initialDividerLocation) { + JSplitPane x = make(new JSplitPane(orientation, first, second), new EmptyBorder(0,0,0,0)); + x.setContinuousLayout(true); + x.setDividerLocation(initialDividerLocation); + x.setOneTouchExpandable(false); + x.setResizeWeight(0.5); + if (Util.onMac() && (x.getUI() instanceof BasicSplitPaneUI)) { + boolean h = (orientation != JSplitPane.HORIZONTAL_SPLIT); + ((BasicSplitPaneUI)(x.getUI())).getDivider().setBorder(new OurBorder(h,h,h,h)); // Makes the border look nicer on Mac OS X + } + return x; + } + + /** Convenience method that recursively enables every JMenu and JMenuItem inside "menu". + * @param menu - the menu to start the recursive search + */ + public static void enableAll (JMenu menu) { + for(int i = 0; i < menu.getMenuComponentCount(); i++) { + Component x = menu.getMenuComponent(i); + if (x instanceof JMenuItem) ((JMenuItem)x).setEnabled(true); else if (x instanceof JMenu) enableAll((JMenu)x); + } + } + + /** Construct a new JMenu and add it to an existing JMenuBar. + *

Note: every time the user expands then collapses this JMenu, we automatically enable all JMenu and JMenuItem objects in it. + * + * @param parent - the JMenuBar to add this Menu into (or null if we don't want to add it to a JMenuBar yet) + * @param label - the label to show on screen (if it contains '&' followed by 'a'..'z', we'll remove '&' and use it as mnemonic) + * @param func - if nonnull we'll call its "run()" method right before expanding this menu + */ + public static JMenu menu (JMenuBar parent, String label, final Runnable func) { + final JMenu x = new JMenu(label.replace("&", ""), false); + if (!Util.onMac()) { + int i = label.indexOf('&'); + if (i>=0 && i+1='a' && i<='z') x.setMnemonic((i-'a')+'A'); else if (i>='A' && i<='Z') x.setMnemonic(i); + } + x.addMenuListener(new MenuListener() { + public void menuSelected (MenuEvent e) { if (func != null) func.run(); } + public void menuDeselected (MenuEvent e) { OurUtil.enableAll(x); } + public void menuCanceled (MenuEvent e) { OurUtil.enableAll(x); } + }); + if (parent!=null) parent.add(x); + return x; + } + + /** Construct a new JMenuItem then add it to an existing JMenu. + * @param parent - the JMenu to add this JMenuItem into (or null if you don't want to add it to any JMenu yet) + * @param label - the text to show on the menu + * @param attrs - a list of attributes to apply onto the new JMenuItem + *

If one positive number a is supplied, we call setMnemonic(a) + *

If two positive numbers a and b are supplied, and a!=VK_ALT, and a!=VK_SHIFT, we call setMnemoic(a) and setAccelerator(b) + *

If two positive numbers a and b are supplied, and a==VK_ALT or a==VK_SHIFT, we call setAccelerator(a | b) + *

If an ActionListener is supplied, we call addActionListener(x) + *

If an Boolean x is supplied, we call setEnabled(x) + *

If an Icon x is supplied, we call setIcon(x) + */ + public static JMenuItem menuItem (JMenu parent, String label, Object... attrs) { + JMenuItem m = new JMenuItem(label, null); + int accelMask = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(); + boolean hasMnemonic = false; + for(Object x: attrs) { + if (x instanceof Character || x instanceof Integer) { + int k = (x instanceof Character) ? ((int)((Character)x)) : ((Integer)x).intValue(); + if (k < 0) continue; + if (k==KeyEvent.VK_ALT) { hasMnemonic = true; accelMask = accelMask | InputEvent.ALT_MASK; continue; } + if (k==KeyEvent.VK_SHIFT) { hasMnemonic = true; accelMask = accelMask | InputEvent.SHIFT_MASK; continue; } + if (!hasMnemonic) { m.setMnemonic(k); hasMnemonic=true; } else m.setAccelerator(KeyStroke.getKeyStroke(k, accelMask)); + } + if (x instanceof ActionListener) m.addActionListener((ActionListener)x); + if (x instanceof Icon) m.setIcon((Icon)x); + if (x instanceof Boolean) m.setEnabled((Boolean)x); + } + if (parent!=null) parent.add(m); + return m; + } + + /** This method minimizes the window. */ + public static void minimize(JFrame frame) { frame.setExtendedState(JFrame.ICONIFIED); } + + /** This method alternatingly maximizes or restores the window. */ + public static void zoom(JFrame frame) { + int both = JFrame.MAXIMIZED_BOTH; + frame.setExtendedState((frame.getExtendedState() & both)!=both ? both : JFrame.NORMAL); + } + + /** Make the frame visible, non-iconized, and focused. */ + public static void show(JFrame frame) { + frame.setVisible(true); + frame.setExtendedState(frame.getExtendedState() & ~JFrame.ICONIFIED); + frame.requestFocus(); + frame.toFront(); + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/Pair.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/Pair.java new file mode 100644 index 00000000..d243eb5d --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/Pair.java @@ -0,0 +1,67 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4; + +import java.io.Serializable; + +/** Immutable; stores a pair of object references; Pair.equals() compares by calling equals() on both components. + * + *

Thread Safety: Safe (since objects of this class are immutable). + */ + +public final class Pair implements Serializable { + + /** This ensures the class can be serialized reliably. */ + private static final long serialVersionUID = 0; + + /** The first half of the pair. */ + public final A a; + + /** The second half of the pair. */ + public final B b; + + /** Constructs a new Pair object (a,b). */ + public Pair(A a, B b) { + this.a = a; + this.b = b; + } + + /** If "a" and "b" are both String, concatename them with a space; if only one is, return it; else call toString() on them. */ + @Override public String toString() { + if (a instanceof String) { if (b instanceof String) return a+" "+b; else return (String)a; } + if (b instanceof String) return (String)b; + if (a==null) { + return (b!=null) ? b.toString() : ""; + } else { + return (b!=null) ? (a+" "+b) : a.toString(); + } + } + + /** Returns a hashcode based on (a==null?0:a.hashCode()) and (b==null?0:b.hashCode()). */ + @Override public int hashCode() { + int i = (a==null) ? 0 : a.hashCode(); + int j = (b==null) ? 0 : b.hashCode(); + return i*173123 + j; + } + + /** Pairs (a1, b1) and (a2, b2) are equal iff (a1==null ? a2==null : a1.equals(a2)) and (b1==null ? b2==null : b1.equals(b2)) */ + @Override public boolean equals(Object that) { + if (this==that) return true; + if (!(that instanceof Pair)) return false; + Pair p = (Pair)that; + return (a==null ? p.a==null : a.equals(p.a)) && (b==null ? p.b==null : b.equals(p.b)); + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/Pos.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/Pos.java new file mode 100644 index 00000000..1e83c76d --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/Pos.java @@ -0,0 +1,138 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4; + +import java.io.Serializable; + +/** Immutable; stores the filename and line/column position. + * + *

Invariant: filename!=null && x>0 && y>0 && ((y2>y && x2>0) || (y2==y && x2>=x)) + * + *

Thread Safety: Safe (since objects of this class are immutable). + */ + +public final class Pos implements Serializable { + + /** To make sure the serialization form is stable. */ + private static final long serialVersionUID = 0; + + /** The filename (it can be an empty string if unknown) */ + public final String filename; + + /** The starting column position (from 1..) */ + public final int x; + + /** The starting row position (from 1..) */ + public final int y; + + /** The ending column position (from 1..) */ + public final int x2; + + /** The ending row position (from 1..) */ + public final int y2; + + /** The default "unknown" location. */ + public static final Pos UNKNOWN = new Pos("",1,1); + + /** Constructs a new Pos object. + * @param filename - the filename (it can be an empty string if unknown) + * @param x - the column position (from 1..) + * @param y - the row position (from 1..) + */ + public Pos(String filename, int x, int y) { + this.filename = (filename==null ? "" : filename); + this.x = (x>0 ? x : 1); + this.y = (y>0 ? y : 1); + this.x2 = this.x; + this.y2 = this.y; + } + + /** Constructs a new Pos object. + * @param filename - the filename (it can be an empty string if unknown) + * @param x - the starting column position (from 1..) + * @param y - the starting row position (from 1..) + * @param x2 - the ending column position (from 1..) + * @param y2 - the ending row position (from 1..) + */ + public Pos(String filename, int x, int y, int x2, int y2) { + this.filename = (filename==null ? "" : filename); + this.x = (x>0 ? x : 1); + this.y = (y>0 ? y : 1); + if (y2<(this.y)) y2=this.y; + if (y2==this.y) { + if (x2<(this.x)) x2=this.x; + } else { + if (x2<1) x2=1; + } + this.x2 = x2; + this.y2 = y2; + } + + /** Return a new position that merges this and that (it is assumed that the two Pos objects have same filename) + * @param that - the other position object + */ + public Pos merge(Pos that) { + if (that==null || that==UNKNOWN || that==this) return this; + if (this==UNKNOWN) return that; + int x=this.x, y=this.y, x2=that.x2, y2=that.y2; + if (that.yy2 || (this.y2==y2 && this.x2>x2)) { + x2=this.x2; + y2=this.y2; + } + if (x==this.x && y==this.y && x2==this.x2 && y2==this.y2) return this; // avoid creating unnecessary new object + if (x==that.x && y==that.y && x2==that.x2 && y2==that.y2) return that; // avoid creating unnecessary new object + return new Pos(filename, x, y, x2, y2); + } + + /** Returns true if neither argument is null nor UNKNOWN, + * and that the ending position of "a" is before the starting position of "b". + */ + public static boolean before(Pos a, Pos b) { + if (a==null || a==Pos.UNKNOWN || b==null || b==Pos.UNKNOWN || !a.filename.equals(b.filename)) return false; + return a.y2=0) f=f.substring(a+1); + if (f.length()==0) return "line "+y+", column "+x; else return "line "+y+", column "+x+", filename="+f; + } + + /** Returns a String representation of this position value. */ + @Override public String toString() { + if (filename.length()==0) return "line "+y+", column "+x; else return "line "+y+", column "+x+", filename="+filename; + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/Runner.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/Runner.java new file mode 100644 index 00000000..eed69b79 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/Runner.java @@ -0,0 +1,96 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4; + +import java.awt.Window; +import java.awt.event.ActionEvent; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; +import java.awt.event.WindowListener; +import java.awt.event.WindowEvent; +import javax.swing.AbstractAction; +import javax.swing.event.CaretEvent; +import javax.swing.event.CaretListener; +import javax.swing.event.MenuEvent; +import javax.swing.event.MenuListener; + +/** This class converts a Runnable into an AbstractAction, WindowListener, CaretListener, and MenuListener also. */ + +public abstract class Runner extends AbstractAction implements Runnable, WindowListener, MenuListener, CaretListener, FocusListener { + + /** This ensures the class can be serialized reliably. */ + private static final long serialVersionUID = 0; + + /** Constructs a new runner; you should override the run() and run(arg) method to customize it. */ + public Runner() { } + + /** This method should be overriden to provide the default action that this Runner would perform. */ + public abstract void run(); + + /** This method should be overriden to provide the default action that this Runner would perform given an argument. */ + public abstract void run(Object arg); + + /** This method is defined in java.awt.event.ActionListener; (this implementation calls this.run()) */ + public final void actionPerformed(ActionEvent e) { run(); } + + /** This method is defined in javax.swing.event.MenuListener; (this implementation calls this.run()) */ + public final void menuSelected(MenuEvent e) { run(); } + + /** This method is defined in javax.swing.event.MenuListener; (this implementation does nothing) */ + public final void menuDeselected(MenuEvent e) { } + + /** This method is defined in javax.swing.event.MenuListener; (this implementation does nothing) */ + public final void menuCanceled(MenuEvent e) { } + + /** This method is defined in java.awt.event.CaretListener; (this implementation calls this.run()) */ + public final void caretUpdate(CaretEvent e) { run(); } + + /** This method is defined in java.awt.event.FocusListener; (this implementation calls this.run()) */ + public final void focusGained(FocusEvent e) { run(); } + + /** This method is defined in java.awt.event.FocusListener; (this implementation does nothing) */ + public final void focusLost(FocusEvent e) { } + + /** This method is defined in java.awt.event.WindowListener; (this implementation calls this.run()) */ + public final void windowClosing(WindowEvent e) { run(); } + + /** This method is defined in java.awt.event.WindowListener; (this implementation does nothing) */ + public final void windowClosed(WindowEvent e) { } + + /** This method is defined in java.awt.event.WindowListener; (this implementation does nothing) */ + public final void windowOpened(WindowEvent e) { } + + /** This method is defined in java.awt.event.WindowListener; (this implementation does nothing) */ + public final void windowIconified(WindowEvent e) { } + + /** This method is defined in java.awt.event.WindowListener; (this implementation does nothing) */ + public final void windowDeiconified(WindowEvent e) { } + + /** This method is defined in java.awt.event.WindowListener; (this implementation does nothing) */ + public final void windowActivated(WindowEvent e) { } + + /** This method is defined in java.awt.event.WindowListener; (this implementation does nothing) */ + public final void windowDeactivated(WindowEvent e) { } + + /** This helper method returns a Runnable whose run() method will call window.dispose() */ + public static final Runner createDispose(final Window window) { + return new Runner() { + private static final long serialVersionUID = 0; + public final void run() { window.dispose(); } + public final void run(Object arg) { window.dispose(); } + }; + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/SafeList.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/SafeList.java new file mode 100644 index 00000000..1584e1ff --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/SafeList.java @@ -0,0 +1,221 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; +import edu.mit.csail.sdg.alloy4.ConstList.TempList; + +/** This list allows add() but disallows remove() and set(); null values are allowed. + * + *

+ * By making this sacrifice, we are able to provide a very cheap "duplicate method" + * that simulates making a copy without actually making a copy. + * + *

+ * Furthermore, this class's iterator allows concurrent insertion and iteration + * (that is, we can iterate over the list while adding elements to the list at the same time). + * The iterator is guaranteed to iterate over exactly the elements + * that existed at the time that the iterator was created. + * + *

Thread Safety: Safe. + * + * @param - the type of element + */ + +public final class SafeList implements Serializable, Iterable { + + /** This ensures the class can be serialized reliably. */ + private static final long serialVersionUID = 0; + + /** The actual list of elements; it will be shared by an original SafeList and all its unmodifiable copies. */ + private final List list; + + /** If negative, that means this instance is mutable; otherwise, it is the list size at the time of the copy. */ + private final int max; + + /** Constructs a modifiable empty list. */ + public SafeList() { + list = new ArrayList(); + max = (-1); + } + + /** Constructs a modifiable empty list with the initial capacity. */ + public SafeList(int initialCapacity) { + list = new ArrayList(initialCapacity); + max = (-1); + } + + /** Constructs a modifiable list containing the elements from the given collection. */ + public SafeList(Collection initialValue) { + list = new ArrayList(initialValue); + max = (-1); + } + + /** Constructs a modifiable list containing the elements from the given iterable. */ + public SafeList(Iterable initialValue) { + list = new ArrayList(); + max = (-1); + for(T obj: initialValue) list.add(obj); + } + + /** Private constructor for assigning exact values to "list" and "max". */ + private SafeList(List list, int max) { + this.list = list; + this.max = max; + } + + /** Constructs an unmodifiable copy of an existing SafeList. */ + public SafeList dup() { + synchronized(SafeList.class) { return new SafeList(list, size()); } + } + + /** Constructs a modifiable ArrayList containing the same elements as this list. */ + public List makeCopy() { + synchronized(SafeList.class) { + int n = size(); + ArrayList ans = new ArrayList(n); + for(int i=0; i makeConstList() { + synchronized(SafeList.class) { + int n = size(); + TempList ans = new TempList(n); + for(int i=0; i b; + if (that instanceof List) { n=((List)that).size(); if (n!=size()) return false; b=((List)that).iterator(); } + else if (that instanceof SafeList) { n=((SafeList)that).size(); if (n!=size()) return false; b=((SafeList)that).iterator(); } + else return false; + Iterator a=iterator(); + for(int i=0; i=0) throw new UnsupportedOperationException(); else return list.add(item); + } + } + + /** Add a collection of elements into the list. */ + public void addAll(Collection items) { + synchronized(SafeList.class) { + if (max>=0) throw new UnsupportedOperationException(); + list.addAll(items); + } + } + + /** Get an element from the list. */ + public T get(int i) { + synchronized(SafeList.class) { + if (max>=0 && i>=max) throw new IndexOutOfBoundsException(); else return list.get(i); + } + } + + /** Returns the size of the list. */ + public int size() { + synchronized(SafeList.class) { + if (max>=0) return max; else return list.size(); + } + } + + /** Returns true if the list is empty. */ + public boolean isEmpty() { + return size()==0; + } + + /** Returns an iterator that iterates over elements in this list + * (in the order that they were inserted). + * + *

Note: This iterator's remove() method always throws UnsupportedOperationException. + * + *

Note: This iterator always returns exactly the list of elements that existed + * at the time that the iterator was created (even if the list is modified after that point). + */ + public Iterator iterator() { + synchronized(SafeList.class) { + return new Iterator() { + private final int imax = (max>=0 ? max : list.size()); + private int now = 0; + public final T next() { + if (now >= imax) throw new NoSuchElementException(); + synchronized(SafeList.class) { + T answer = list.get(now); + now++; + return answer; + } + } + public final boolean hasNext() { return now < imax; } + public final void remove() { throw new UnsupportedOperationException(); } + }; + } + } + + /** Returns a String representation of this list. */ + @Override public String toString() { + StringBuilder sb = new StringBuilder("["); + boolean first = true; + for(Object x: this) { + if (first) first=false; else sb.append(", "); + if (x==this) sb.append("(this collection)"); else sb.append(x); + } + return sb.append(']').toString(); + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/Subprocess.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/Subprocess.java new file mode 100644 index 00000000..f0b600e0 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/Subprocess.java @@ -0,0 +1,94 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4; + +import java.io.InputStream; +import java.util.Timer; +import java.util.TimerTask; + +/** This provides a convenience wrapper around a Process object. + * + *

To launch a subprocess, simply write Subprocess.exec(timeout, args); + * + *

Thread Safety: Safe. + */ + +public final class Subprocess { + + /** Timer used to schedule a timeout for the process. */ + private static final Timer stopper = new Timer(); + + /** This field will store the program output (or "" if an error occurred) */ + private String stdout = null; + + /** This field will store an error message (or "" if no error occurred) */ + private String stderr = null; + + /** Constructor is private since we only allow the static method exec() to construct objects of this class. */ + private Subprocess() { } + + /** Executes the given command line, wait for its completion, then return its output. + * @param timeLimit - we will attempt to terminate the process after that many milliseconds have passed + * @param p - preconstructed Process object + */ + private static String exec (final long timeLimit, final Process p) throws Exception { + final Subprocess pro = new Subprocess(); + final InputStream f1 = p.getInputStream(), f2 = p.getErrorStream(); + TimerTask stoptask = new TimerTask() { + public void run() { + synchronized(pro) { if (pro.stdout!=null && pro.stderr!=null) return; pro.stdout=""; pro.stderr="Error: timeout"; } + p.destroy(); + } + }; + synchronized(Subprocess.class) { stopper.schedule(stoptask, timeLimit); } + new Thread(new Runnable() { + public void run() { + String err = null; + try { if (f2.read()>=0) err="Error: stderr"; } catch(Throwable ex) { err="Error: "+ex; } + synchronized(pro) { if (err!=null) {pro.stdout=""; pro.stderr=err;} else if (pro.stderr==null) pro.stderr=""; } + } + }).start(); + p.getOutputStream().close(); + StringBuilder output = new StringBuilder(); + byte[] buf = new byte[8192]; + while(true) { + int n = f1.read(buf); + if (n<0) break; else for(int i=0; iThread Safety: Safe. + */ + +public final class UniqueNameGenerator { + + /** This stores the set of names we've generated so far. */ + private final Set names = new HashSet(); + + /** Construct a UniqueNameGenerator with a blank history. */ + public UniqueNameGenerator() { } + + /** Regard the provided name as "seen". + *

For convenience, it returns the argument as the return value. + */ + public synchronized String seen(String name) { names.add(name); return name; } + + /** Queries whether the provided name has been "seen" or not. */ + public synchronized boolean hasSeen(String name) { return names.contains(name); } + + /** Clear the history of previously generated names. */ + public synchronized void clear() { names.clear(); } + + /** Generate a unique name based on the input name. + * + *

Specifically: if the name has not been generated/seen already by this generator, + * then it is returned as is. Otherwise, we append ' to it until the name becomes unique. + */ + public synchronized String make(String name) { + while(!names.add(name)) name=name+"'"; + return name; + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/Util.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/Util.java new file mode 100644 index 00000000..c621bdcd --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/Util.java @@ -0,0 +1,601 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4; + +import java.io.Closeable; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintStream; +import java.io.PrintWriter; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; +import java.nio.charset.CharacterCodingException; +import java.nio.charset.Charset; +import java.nio.charset.CodingErrorAction; +import java.util.Comparator; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Locale; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.prefs.Preferences; +import edu.mit.csail.sdg.alloy4.ConstList.TempList; + +/** This provides useful static methods for I/O and XML operations. + * + *

Thread Safety: Safe. + */ + +public final class Util { + + /** This constructor is private, since this utility class never needs to be instantiated. */ + private Util() { } + + /** This reads and writes String-valued Java persistent preferences. + *

Thread Safety: Safe. + */ + public static final class StringPref { + /** The id associated with this preference. */ + private final String id; + /** The default value for this preference. */ + private final String defaultValue; + /** Constructs a new StringPref object with the given id. */ + public StringPref (String id) {this.id=id; this.defaultValue="";} + /** Constructs a new StringPref object with the given id and the given default value. */ + public StringPref (String id, String defaultValue) {this.id=id; this.defaultValue=defaultValue;} + /** Sets the value for this preference. */ + public void set (String value) { Preferences.userNodeForPackage(Util.class).put(id, value); } + /** Reads the value for this preference; if not set or is empty, we return the default value. */ + public String get () { + String ans=Preferences.userNodeForPackage(Util.class).get(id, ""); + return (ans==null || ans.length()==0) ? defaultValue : ans; + } + } + + /** This reads and writes boolean-valued Java persistent preferences. + *

Thread Safety: Safe. + */ + public static final class BooleanPref { + /** The id associated with this preference. */ + private final String id; + /** Constructurs a new BooleanPref object with the given id. */ + public BooleanPref (String id) { this.id=id; } + /** Sets the value for this preference. */ + public void set (boolean value) { Preferences.userNodeForPackage(Util.class).put(id, value ? "y" : ""); } + /** Reads the value for this preference; if not set, we return false. */ + public boolean get () { return "y".equals(Preferences.userNodeForPackage(Util.class).get(id, "")); } + } + + /** This reads and writes integer-valued Java persistent preferences. + *

Thread Safety: Safe. + */ + public static final class IntPref { + /** The id associated with this preference. */ + private final String id; + /** The minimum value for this preference. */ + private final int min; + /** The maximum value for this preference. */ + private final int max; + /** The default value for this preference. */ + private final int def; + /** If min>n, we return min; else if n>max, we return max; otherwise we return n. */ + private int bound (int n) { return nmax? max : n); } + /** Make a new IntPref object with the given id; you must ensure max >= min, but def does not have to be between min..max */ + public IntPref (String id, int min, int def, int max) {this.id=id; this.min=min; this.def=def; this.max=max;} + /** Sets the value for this preference. */ + public void set (int value) { Preferences.userNodeForPackage(Util.class).putInt(id, bound(value)); } + /** Reads the value for this preference; if not set, we return the default value. */ + public int get () { + int n; + String t = Preferences.userNodeForPackage(Util.class).get(id, ""); + if (t==null || t.length()==0) return def; + try { n=Integer.parseInt(t); } catch(NumberFormatException ex) { return def; } + return bound(n); + } + } + + /** Copy the input list, append "element" to it, then return the result as an unmodifiable list. */ + public static ConstList append(List list, T element) { + TempList ans = new TempList(list.size()+1); + ans.addAll(list).add(element); + return ans.makeConst(); + } + + /** Copy the input array, append "element" to it, then return the result as a new array. */ + @SuppressWarnings("unchecked") + public static T[] append(T[] list, T element) { + T[] ans = (T[]) java.lang.reflect.Array.newInstance(list.getClass().getComponentType(), list.length+1); + System.arraycopy(list, 0, ans, 0, list.length); + ans[ans.length-1] = element; + return ans; + } + + /** Copy the input list, prepend "element" to it, then return the result as an unmodifiable list. */ + public static ConstList prepend(List list, T element) { + TempList ans = new TempList(list.size()+1); + ans.add(element).addAll(list); + return ans.makeConst(); + } + + /** Returns an unmodifiable List with same elements as the array. */ + public static ConstList asList(T... array) { + return (new TempList(array)).makeConst(); + } + + /** Returns a newly created LinkedHashSet containing the given elements in the given order. */ + public static LinkedHashSet asSet(V... values) { + LinkedHashSet ans = new LinkedHashSet(); + for(int i=0; i LinkedHashMap asMap(K[] keys, V... values) { + LinkedHashMap ans = new LinkedHashMap(); + for(int i=0; i Iterable fastJoin(final Iterable collection1, final Iterable collection2) { + return new Iterable() { + public Iterator iterator() { + return new Iterator () { + private Iterator a=collection1.iterator(), b=collection2.iterator(); + public boolean hasNext() { + if (a!=null) { if (a.hasNext()) return true; a=null; } + if (b!=null) { if (b.hasNext()) return true; b=null; } + return false; + } + public E next() { + if (a!=null) { if (a.hasNext()) return a.next(); a=null; } + if (b!=null) { if (b.hasNext()) return b.next(); b=null; } + throw new NoSuchElementException(); + } + public void remove() { throw new UnsupportedOperationException(); } + }; + } + }; + } + + /** Converts Windows/Mac/Unix linebreaks into '\n', and replace non-tab non-linebreak control characters into space. */ + public static String convertLineBreak(String input) { + return input.replace("\r\n","\n").replace('\r','\n').replaceAll("[\000-\010\013\014\016-\037]"," "); + } + + /** Attempt to close the file/stream/reader/writer and return true if and only if we successfully closed it. + * (If object==null, we return true right away) + */ + public static boolean close(Closeable object) { + if (object==null) return true; + boolean ans=true; + try { + if (object instanceof PrintStream && ((PrintStream)object).checkError()) ans=false; + if (object instanceof PrintWriter && ((PrintWriter)object).checkError()) ans=false; + object.close(); + return ans; + } catch(Throwable ex) { + return false; + } + } + + /** This synchronized field stores the current "default directory" which is used by the FileOpen and FileSave dialogs. */ + private static String currentDirectory = null; + + /** Modifies the current "default directory" which is used by the FileOpen and FileSave dialogs. */ + public synchronized static void setCurrentDirectory(File newDirectory) { + if (newDirectory==null) // this can actually happen + currentDirectory = canon(System.getProperty("user.home")); + else + currentDirectory = canon(newDirectory.getAbsolutePath()); + } + + /** Returns the current "default directory" which is used by the FileOpen and FileSave dialogs. */ + public synchronized static String getCurrentDirectory() { + if (currentDirectory == null) currentDirectory = canon(System.getProperty("user.home")); + return currentDirectory; + } + + /** This returns the constant prefix to denote whether Util.readAll() should read from a JAR or read from the file system. + * (The reason we made this into a "method" rather than a constant String is that it is used + * by Util.canon() which is called by many static initializer blocks... so if we made this into a static field + * of Util, then it may not be initialized yet when we need it!) + */ + public static String jarPrefix() { return File.separator + "$alloy4$" + File.separator; } + + /** Read everything into a String; throws IOException if an error occurred. + * (If filename begins with Util.jarPrefix() then we read from the JAR instead) + */ + public static String readAll(String filename) throws FileNotFoundException, IOException { + String JAR = jarPrefix(); + boolean fromJar=false; + if (filename.startsWith(JAR)) { fromJar=true; filename=filename.substring(JAR.length()).replace('\\', '/'); } + InputStream fis=null; + int now=0, max=4096; + if (!fromJar) { + long maxL = new File(filename).length(); + max = (int)maxL; + if (max != maxL) throw new IOException("File too big to fit in memory"); + } + byte[] buf; + try { + buf = new byte[max]; + fis = fromJar ? Util.class.getClassLoader().getResourceAsStream(filename) : new FileInputStream(filename); + if (fis==null) throw new FileNotFoundException("File \""+filename+"\" cannot be found"); + while(true) { + if (now >= max) { + max = now + 4096; + if (max0) System.arraycopy(buf, 0, buf2, 0, now); + buf = buf2; + } + int r = fis.read(buf, now, max-now); + if (r<0) break; + now = now + r; + } + } catch(OutOfMemoryError ex) { + System.gc(); + throw new IOException("There is insufficient memory."); + } finally { + close(fis); + } + CodingErrorAction r = CodingErrorAction.REPORT; + CodingErrorAction i = CodingErrorAction.IGNORE; + ByteBuffer bbuf; + String ans = ""; + try { + // We first try UTF-8; + bbuf=ByteBuffer.wrap(buf, 0, now); + ans=Charset.forName("UTF-8").newDecoder().onMalformedInput(r).onUnmappableCharacter(r).decode(bbuf).toString(); + } catch(CharacterCodingException ex) { + try { + // if that fails, we try using the platform's default charset + bbuf=ByteBuffer.wrap(buf, 0, now); + ans=Charset.defaultCharset().newDecoder().onMalformedInput(r).onUnmappableCharacter(r).decode(bbuf).toString(); + } catch(CharacterCodingException ex2) { + // if that also fails, we try using "ISO-8859-1" which should always succeed but may map some characters wrong + bbuf=ByteBuffer.wrap(buf, 0, now); + ans=Charset.forName("ISO-8859-1").newDecoder().onMalformedInput(i).onUnmappableCharacter(i).decode(bbuf).toString(); + } + } + return convertLineBreak(ans); + } + + /** Open then overwrite the file with the given content; throws Err if an error occurred. */ + public static long writeAll(String filename, String content) throws Err { + final FileOutputStream fos; + try { + fos=new FileOutputStream(filename); + } catch(IOException ex) { + throw new ErrorFatal("Cannot write to the file "+filename); + } + // Convert the line break into the UNIX line break, and remove ^L, ^F... and other characters that confuse JTextArea + content = convertLineBreak(content); + // If the last line does not have a LINEBREAK, add it + if (content.length()>0 && content.charAt(content.length()-1)!='\n') content=content+"\n"; + // Now, convert the line break into the local platform's line break, then write it to the file + try { + final String NL = System.getProperty("line.separator"); + byte[] array = content.replace("\n",NL).getBytes("UTF-8"); + fos.write(array); + fos.close(); + return array.length; + } catch(IOException ex) { + close(fos); + throw new ErrorFatal("Cannot write to the file "+filename, ex); + } + } + + /** Returns the canonical absolute path for a file. + * If an IO error occurred, or if the file doesn't exist yet, + * we will at least return a noncanonical but absolute path for it. + *

Note: if filename=="", we return "". + */ + public static final String canon(String filename) { + if (filename==null || filename.length()==0) return ""; + if (filename.startsWith(jarPrefix())) { + char sep = File.separatorChar, other = (sep=='/' ? '\\' : '/'); + return filename.replace(other, sep); + } + File file = new File(filename); + try { return file.getCanonicalPath(); } catch(IOException ex) { return file.getAbsolutePath(); } + } + + /** Sorts two strings for optimum module order; we guarantee slashComparator(a,b)==0 iff a.equals(b). + *
(1) First of all, the builtin names "extend" and "in" are sorted ahead of other names + *
(2) Else, if one string starts with "this/", then it is considered smaller + *
(3) Else, if one string has fewer '/' than the other, then it is considered smaller. + *
(4) Else, we compare them lexically without case-sensitivity. + *
(5) Finally, we compare them lexically with case-sensitivity. + */ + public static final Comparator slashComparator = new Comparator() { + public final int compare(String a, String b) { + if (a==null) return (b==null)?0:-1; else if (b==null) return 1; else if (a.equals(b)) return 0; + if (a.equals("extends")) return -1; else if (b.equals("extends")) return 1; + if (a.equals("in")) return -1; else if (b.equals("in")) return 1; + if (a.startsWith("this/")) { + if (!b.startsWith("this/")) return -1; + } else if (b.startsWith("this/")) { + return 1; + } + int acount=0, bcount=0; + for(int i=0; i0) return false; + boolean result = true; + InputStream in = null; + FileOutputStream out = null; + try { + in = Util.class.getClassLoader().getResourceAsStream(sourcename); + if (in==null) return false; // This means the file is not relevant for this setup, so we don't pop up a fatal dialog + out = new FileOutputStream(destname); + byte[] b = new byte[16384]; + while(true) { + int numRead = in.read(b); + if (numRead < 0) break; + if (numRead > 0) out.write(b, 0, numRead); + } + } catch (IOException e) { + result=false; + } + if (!close(out)) result=false; + if (!close(in)) result=false; + if (!result) OurDialog.fatal("Error occurred in creating the file \""+destname+"\""); + return true; + } + + /** Copy the list of files from JAR into the destination directory, + * then set the correct permissions on them if possible. + * + * @param executable - if true, we will attempt to set the file's "executable" permission (failure to do this is ignored) + * @param keepPath - if true, the full path will be created for the destination file + * @param destdir - the destination directory + * @param names - the files to copy from the JAR + */ + public static void copy(boolean executable, boolean keepPath, String destdir, String... names) { + String[] args = new String[names.length+2]; + args[0] = "/bin/chmod"; // This does not work on Windows, but the "executable" bit is not needed on Windows anyway. + args[1] = (executable ? "700" : "600"); // 700 means read+write+executable; 600 means read+write. + int j=2; + for(int i=0; i=0) destname=destname.substring(ii+1); } + destname=(destdir+'/'+destname).replace('/', File.separatorChar); + int last=destname.lastIndexOf(File.separatorChar); + new File(destname.substring(0,last+1)).mkdirs(); // Error will be caught later by the file copy + if (copy(name, destname)) { args[j]=destname; j++; } + } + if (onWindows() || j<=2) return; + String[] realargs = new String[j]; + for(int i=0; i If (from > to), this means we simply delete the portion of the file beginning at "to" and up to but excluding "from". + *

If (from < to), this means we insert (to-from) number of ARBITRARY bytes into the "from" location + * and shift the original file content accordingly. + *

Note: after this operation, the file's current position will be moved to the start of the file. + * @throws IOException if (from < 0) || (to < 0) || (from >= file.length()) + */ + public static void shift (RandomAccessFile file, long from, long to) throws IOException { + long total = file.length(); + if (from<0 || from>=total || to<0) throw new IOException(); else if (from==to) {file.seek(0); return;} + final byte buf[] = new byte[4096]; + int res; + if (from>to) { + while(true) { + file.seek(from); + if ((res=file.read(buf)) <= 0) { file.setLength(to); file.seek(0); return; } + file.seek(to); file.write(buf, 0, res); from=from+res; to=to+res; + } + } else { + file.seek(total); + for(long todo=to-from; todo>0;) { + if (todo >= buf.length) {file.write(buf); todo = todo - buf.length;} else {file.write(buf, 0, (int)todo); break;} + } + for(long todo=total-from; todo>0; total=total-res, todo=todo-res) { + if (todo > buf.length) res=buf.length; else res=(int)todo; + file.seek(total - res); + for(int done=0; done + * In particular, it changes LESS THAN, GREATER THAN, AMPERSAND, SINGLE QUOTE, and DOUBLE QUOTE + * into "&lt;" "&gt;" "&amp;" "&apos;" and "&quot;" and turns any characters + * outside of the 32..126 range into the "&#xHHHH;" encoding + * (where HHHH is the 4 digit lowercase hexadecimal representation of the character value). + * + * @param out - the PrintWriter to write into + * @param str - the String to write out + */ + public static void encodeXML(PrintWriter out, String str) { + int n=str.length(); + for(int i=0; i') { out.write(">"); continue; } + if (c=='&') { out.write("&"); continue; } + if (c=='\'') { out.write("'"); continue; } + if (c=='\"') { out.write("""); continue; } + if (c>=32 && c<=126) { out.write(c); continue; } + out.write("&#x"); + String v=Integer.toString(c, 16); + for(int j=v.length(); j<4; j++) out.write('0'); + out.write(v); + out.write(';'); + } + } + + /** Write a String into a StringBuilder, and encode special characters using XML-specific encoding. + * + *

+ * In particular, it changes LESS THAN, GREATER THAN, AMPERSAND, SINGLE QUOTE, and DOUBLE QUOTE + * into "&lt;" "&gt;" "&amp;" "&apos;" and "&quot;" and turns any characters + * outside of the 32..126 range into the "&#xHHHH;" encoding + * (where HHHH is the 4 digit lowercase hexadecimal representation of the character value). + * + * @param out - the StringBuilder to write into + * @param str - the String to write out + */ + public static void encodeXML(StringBuilder out, String str) { + int n=str.length(); + for(int i=0; i') { out.append(">"); continue; } + if (c=='&') { out.append("&"); continue; } + if (c=='\'') { out.append("'"); continue; } + if (c=='\"') { out.append("""); continue; } + if (c>=32 && c<=126) { out.append(c); continue; } + out.append("&#x"); + String v=Integer.toString(c, 16); + for(int j=v.length(); j<4; j++) out.append('0'); + out.append(v).append(';'); + } + } + + /** Encode special characters of a String using XML/HTML encoding. + * + *

+ * In particular, it changes LESS THAN, GREATER THAN, AMPERSAND, SINGLE QUOTE, and DOUBLE QUOTE + * into "&lt;" "&gt;" "&amp;" "&apos;" and "&quot;" and turns any characters + * outside of the 32..126 range into the "&#xHHHH;" encoding + * (where HHHH is the 4 digit lowercase hexadecimal representation of the character value). + */ + public static String encode(String str) { + if (str.length() == 0) return str; + StringBuilder sb = new StringBuilder(); + encodeXML(sb, str); + return sb.toString(); + } + + /** Write a list of Strings into a PrintWriter, where strs[2n] are written as-is, and strs[2n+1] are XML-encoded. + * + *

For example, if you call encodeXML(out, A, B, C, D, E), it is equivalent to the following: + *
out.print(A); + *
encodeXML(out, B); + *
out.print(C); + *
encodeXML(out, D); + *
out.print(E); + *
In other words, it writes the even entries as-is, and writes the odd entries using XML encoding. + * + * @param out - the PrintWriter to write into + * @param strs - the list of Strings to write out + */ + public static void encodeXMLs(PrintWriter out, String... strs) { + for(int i=0; i For example, if you call encodeXML(out, A, B, C, D, E), it is equivalent to the following: + *
out.append(A); + *
encodeXML(out, B); + *
out.append(C); + *
encodeXML(out, D); + *
out.append(E); + *
In other words, it writes the even entries as-is, and writes the odd entries using XML encoding. + * + * @param out - the StringBuilder to write into + * @param strs - the list of Strings to write out + */ + public static void encodeXMLs(StringBuilder out, String... strs) { + for(int i=0; ismall within big. + * @param big - the String that we want to perform the search on + * @param small - the pattern we are looking forward + * @param start - the offset within "big" to start (for example: 0 means to start from the beginning of "big") + * @param forward - true if the search should go forward; false if it should go backwards + * @param caseSensitive - true if the search should be done in a case-sensitive manner + * + * @return 0 or greater if found, -1 if not found (Note: if small=="", then we always return -1) + */ + public static int indexOf(String big, String small, int start, boolean forward, boolean caseSensitive) { + int len=big.length(), slen=small.length(); + if (slen==0) return -1; + while(start>=0 && start=slen) return start; + if (start+i>=len) break; + int b=big.charAt(start+i), s=small.charAt(i); + if (!caseSensitive && b>='A' && b<='Z') b=(b-'A')+'a'; + if (!caseSensitive && s>='A' && s<='Z') s=(s-'A')+'a'; + if (b!=s) break; + } + if (forward) start++; else start--; + } + return -1; + } + + /** Returns true iff running on Windows **/ + public static boolean onWindows() { + return System.getProperty("os.name").toLowerCase(Locale.US).startsWith("windows"); + }; + + /** Returns true iff running on Mac OS X. **/ + public static boolean onMac() { + return System.getProperty("mrj.version")!=null || System.getProperty("os.name").toLowerCase(Locale.US).startsWith("mac "); + } + + /** Returns the substring after the last "/" */ + public static String tail(String string) { int i=string.lastIndexOf('/'); return (i<0) ? string : string.substring(i+1); } + + /** Returns the largest allowed integer, or -1 if no integers are allowed (bitwidth < 1). */ + public static int max(int bitwidth) { return bitwidth < 1 ? -1 : (1<<(bitwidth-1))-1; } + + /** Returns the smallest allowed integer, or 0 if no integers are allowed (bitwidth < 1)*/ + public static int min(int bitwidth) { return bitwidth < 1 ? 0 : 0-(1<<(bitwidth-1)); } + + /** Returns a mask of the form 000..0011..11 where the number of 1s is equal to the number of significant bits of the highest integer withing the given bitwidth */ + public static int shiftmask(int bitwidth) { return bitwidth < 1 ? 0 : (1 << (32 - Integer.numberOfLeadingZeros(bitwidth-1))) - 1; } + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/Version.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/Version.java new file mode 100644 index 00000000..1ba1f34b --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/Version.java @@ -0,0 +1,42 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4; + +/** This holds the buildDate String. + * + * The release build script will generate a customized Version.java with the correct buildnumber and date. + * + *

Thread Safety: Safe. + */ + +public final class Version { + + /** The constructor is private, since this class never needs to be instantiated. */ + private Version() { } + + /** This is true if this is an experimental version rather than a release version. */ + public static final boolean experimental = true; + + /** Returns the build number. */ + public static int buildNumber() { return Integer.MAX_VALUE; } + + /** Returns the version string. */ + public static String version() { return "4.2.?"; } + + /** Returns the build date. */ + public static String buildDate() { return "unknown"; } + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/WorkerEngine.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/WorkerEngine.java new file mode 100644 index 00000000..90a5c679 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/WorkerEngine.java @@ -0,0 +1,296 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4; + +import java.io.File; +import java.io.FileDescriptor; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.OutputStream; +import java.io.PrintStream; +import java.io.Serializable; +import java.lang.Thread.UncaughtExceptionHandler; + +/** This class allows you to execute tasks in a subprocess, and receive its outputs via callback. + * + *

By executing the task in a subprocess, we can always terminate a runaway task explicitly by calling stop(), + * and we can control how much memory and stack space to give to the subprocess. + * + *

Only one task may execute concurrently at any given time; if you try to issue a new task + * when the previous task hasn't finished, then you will get an IOException. + * + *

As long as the subprocess hasn't terminated either due to crashing or due to user calling stop(), + * then the same subprocess is reused to execute each subsequent task; however, if the subprocess crashed, + * the crash will be reported to the parent process via callback, and if we try to execute another task, + * then a new subprocess will be spawned automatically. + */ + +public final class WorkerEngine { + + /** This defines an interface for performing tasks in a subprocess. */ + public interface WorkerTask extends Serializable { + /** The task should send zero or more non-null Objects to out.callback(msg) to report progress to the parent process. */ + public void run(WorkerCallback out) throws Exception; + } + + /** This defines an interface for receiving results from a subprocess. */ + public interface WorkerCallback { + /** The task would send zero or more non-null Objects to this handler + * (the objects will be serialized by the sub JVM and deserialized in the parent JVM). */ + public void callback(Object msg); + /** If the task completed successfully, this method will be called. */ + public void done(); + /** If the task terminated with an error, this method will be called. */ + public void fail(); + } + + /** This wraps the given InputStream such that the resulting object's "close()" method does nothing; + * if stream==null, we get an InputStream that always returns EOF. */ + private static InputStream wrap(final InputStream stream) { + return new InputStream() { + public int read(byte b[], int off, int len) throws IOException { + if (len==0) return 0; else if (stream==null) return -1; else return stream.read(b, off, len); + } + public int read() throws IOException { if (stream==null) return -1; else return stream.read(); } + public long skip(long n) throws IOException { if (stream==null) return 0; else return stream.skip(n); } + }; + } + + /** This wraps the given OutputStream such that the resulting object's "close()" method simply calls "flush()"; + * if stream==null, we get an OutputStream that ignores all writes. */ + private static OutputStream wrap(final OutputStream stream) { + return new OutputStream() { + public void write(int b) throws IOException { if (stream!=null) stream.write(b); } + public void write(byte b[], int off, int len) throws IOException { if (stream!=null) stream.write(b, off, len); } + public void flush() throws IOException { if (stream!=null) stream.flush(); } + public void close() throws IOException { if (stream!=null) stream.flush(); } + // The close() method above INTENTIONALLY does not actually close the file + }; + } + + /** If nonnull, it is the latest sub JVM. */ + private static Process latest_sub = null; + + /** If nonnull, it is the latest worker thread talking to the sub JVM. + * (If latest_sub==null, then we guarantee latest_manager is also null) */ + private static Thread latest_manager = null; + + /** Constructor is private since this class does not need to be instantiated. */ + private WorkerEngine() { } + + /** This terminates the subprocess, and prevent any further results from reaching the parent's callback handler. */ + public static void stop() { + synchronized(WorkerEngine.class) { + try { if (latest_sub!=null) latest_sub.destroy(); } finally { latest_manager=null; latest_sub=null; } + } + } + + /** This returns true iff the subprocess is still busy processing the last task. */ + public static boolean isBusy() { + synchronized(WorkerEngine.class) { return latest_manager!=null && latest_manager.isAlive(); } + } + + /** This executes a task using the current thread. + * @param task - the task that we want to execute + * @param callback - the handler that will receive outputs from the task + * @throws IOException - if a previous task is still busy executing + */ + public static void runLocally(final WorkerTask task, final WorkerCallback callback) throws Exception { + synchronized(WorkerEngine.class) { + if (latest_manager!=null && latest_manager.isAlive()) throw new IOException("Subprocess still performing the last task."); + try { task.run(callback); callback.done(); } catch(Throwable ex) { callback.callback(ex); callback.fail(); } + } + } + + /** This issues a new task to the subprocess; + * if subprocess hasn't been constructed yet or has terminated abnormally, this method will launch a new subprocess. + * @param task - the task that we want the subprocess to execute + * @param newmem - the amount of memory (in megabytes) we want the subprocess to have + * (if the subproces has not terminated, then this parameter is ignored) + * @param newstack - the amount of stack (in kilobytes) we want the subprocess to have + * (if the subproces has not terminated, then this parameter is ignored) + * @param jniPath - if nonnull and nonempty, then it specifies the subprocess's default JNI library location + * @param classPath - if nonnull and nonempty, then it specifies the subprocess's default CLASSPATH, + * else we'll use System.getProperty("java.class.path") + * @param callback - the handler that will receive outputs from the task + * @throws IOException - if a previous task is still busy executing + * @throws IOException - if an error occurred in launching a sub JVM or talking to it + */ + public static void run + (final WorkerTask task, int newmem, int newstack, String jniPath, String classPath, final WorkerCallback callback) + throws IOException { + if (classPath==null || classPath.length()==0) classPath = System.getProperty("java.class.path"); + synchronized(WorkerEngine.class) { + final Process sub; + if (latest_manager!=null && latest_manager.isAlive()) throw new IOException("Subprocess still performing the last task."); + try { + if (latest_sub!=null) latest_sub.exitValue(); latest_manager=null; latest_sub=null; + } catch(IllegalThreadStateException ex) { } + if (latest_sub==null) { + String java = "java", javahome = System.getProperty("java.home"); + if (javahome!=null && javahome.length()>0) { + // First try "[JAVAHOME]/bin/java" + File f = new File(javahome + File.separatorChar + "bin" + File.separatorChar + "java"); + // Then try "[JAVAHOME]/java" + if (!f.isFile()) f = new File(javahome + File.separatorChar + "java"); + // All else, try "java" (and let the Operating System search the program path...) + if (f.isFile()) java = f.getAbsolutePath(); + } + String debug = "yes".equals(System.getProperty("debug")) ? "yes" : "no"; + if (jniPath!=null && jniPath.length()>0) + sub = Runtime.getRuntime().exec(new String[] { + java, + "-Xmx" + newmem + "m", + "-Xss" + newstack + "k", + "-Djava.library.path=" + jniPath, + "-Ddebug=" + debug, + "-cp", classPath, WorkerEngine.class.getName(), + Version.buildDate(), ""+Version.buildNumber() + }); + else + sub = Runtime.getRuntime().exec(new String[] { + java, + "-Xmx" + newmem + "m", + "-Xss" + newstack + "k", + "-Ddebug=" + debug, + "-cp", classPath, WorkerEngine.class.getName(), + Version.buildDate(), ""+Version.buildNumber() + }); + latest_sub = sub; + } else { + sub = latest_sub; + } + latest_manager = new Thread(new Runnable() { + public void run() { + ObjectInputStream sub2main = null; + ObjectOutputStream main2sub = null; + try { + main2sub = new ObjectOutputStream(wrap(sub.getOutputStream())); main2sub.writeObject(task); main2sub.close(); + sub2main = new ObjectInputStream(wrap(sub.getInputStream())); + } catch(Throwable ex) { + sub.destroy(); Util.close(main2sub); Util.close(sub2main); + synchronized(WorkerEngine.class) { if (latest_sub != sub) return; callback.fail(); return; } + } + while(true) { + synchronized(WorkerEngine.class) { if (latest_sub != sub) return; } + Object x; + try { + x = sub2main.readObject(); + } catch(Throwable ex) { + sub.destroy(); Util.close(sub2main); + synchronized(WorkerEngine.class) { if (latest_sub != sub) return; callback.fail(); return; } + } + synchronized(WorkerEngine.class) { + if (latest_sub != sub) return; if (x==null) {callback.done(); return;} else callback.callback(x); + } + } + } + }); + latest_manager.start(); + } + } + + /** This is the entry point for the sub JVM. + * + *

Behavior is very simple: it reads a WorkerTask object from System.in, then execute it, then read another... + * If any error occurred, or if it's disconnected from the parent process's pipe, it then terminates itself + * (since we assume the parent process will notice it and react accordingly) + */ + public static void main(String[] args) { + // To prevent people from accidentally invoking this class, or invoking it from an incompatible version, + // we add a simple sanity check on the command line arguments + if (args.length!=2) halt("#args should be 2 but instead is "+args.length, 1); + if (!args[0].equals(Version.buildDate())) halt("BuildDate mismatch: "+args[0]+" != "+Version.buildDate(), 1); + if (!args[1].equals("" + Version.buildNumber())) halt("BuildNumber mismatch: "+args[1]+" != "+Version.buildNumber(), 1); + // To prevent a zombie process, we set a default handler to terminate itself if something does slip through our detection + Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandler() { + public void uncaughtException(Thread t, Throwable e) { halt("UncaughtException: "+e, 1); } + }); + // Redirect System.in, System.out, System.err to no-op (so that if a task tries to read/write to System.in/out/err, + // those reads and writes won't mess up the ObjectInputStream/ObjectOutputStream) + System.setIn(wrap((InputStream)null)); + System.setOut(new PrintStream(wrap((OutputStream)null))); + System.setErr(new PrintStream(wrap((OutputStream)null))); + final FileInputStream in = new FileInputStream(FileDescriptor.in); + final FileOutputStream out = new FileOutputStream(FileDescriptor.out); + // Preload these 3 libraries; on MS Windows with JDK 1.6 this seems to prevent freezes + try { System.loadLibrary("minisat"); } catch(Throwable ex) { } + try { System.loadLibrary("minisatprover"); } catch(Throwable ex) { } + try { System.loadLibrary("zchaff"); } catch(Throwable ex) { } + // Now we repeat the following read-then-execute loop + Thread t = null; + while(true) { + final WorkerTask task; + try { + System.gc(); // while we're waiting for the next task, we might as well encourage garbage collection + ObjectInputStream oin = new ObjectInputStream(wrap(in)); + task = (WorkerTask) oin.readObject(); + oin.close(); + } catch(Throwable ex) { + halt("Can't read task: "+ex, 1); + return; + } + // Our main thread has a loop that keeps "attempting" to read bytes from System.in, + // and delegate the actual task to a separate "worker thread". + // This way, if the parent process terminates, then this subprocess should see it almost immediately + // (since the inter-process pipe will be broken) and will terminate (regardless of the status of the worker thread) + if (t!=null && t.isAlive()) { + // We only get here if the previous subtask has informed the parent that the job is done, and that the parent + // then issued another job. So we wait up to 5 seconds for the worker thread to confirm its termination. + // If 5 seconds is up, then we assume something terrible has happened. + try {t.join(5000); if (t.isAlive()) halt("Timeout", 1);} catch (Throwable ex) {halt("Timeout: "+ex, 1);} + } + t = new Thread(new Runnable() { + public void run() { + ObjectOutputStream x = null; + Throwable e = null; + try { + x = new ObjectOutputStream(wrap(out)); + final ObjectOutputStream xx = x; + WorkerCallback y = new WorkerCallback() { + public void callback(Object x) { try {xx.writeObject(x);} catch(IOException ex) {halt("Callback: "+ex, 1);} } + public void done() { } + public void fail() { } + }; + task.run(y); + x.writeObject(null); + x.flush(); + } catch(Throwable ex) { + e=ex; + } + for(Throwable t=e; t!=null; t=t.getCause()) if (t instanceof OutOfMemoryError || t instanceof StackOverflowError) { + try { System.gc(); x.writeObject(t); x.flush(); } catch(Throwable ex2) { } finally { halt("Error: "+e, 2); } + } + if (e instanceof Err) { + try { System.gc(); x.writeObject(e); x.writeObject(null); x.flush(); } catch(Throwable t) { halt("Error: "+e, 1); } + } + if (e!=null) { + try { System.gc(); x.writeObject(e); x.flush(); } catch(Throwable t) { } finally { halt("Error: "+e, 1); } + } + Util.close(x); // avoid memory leaks + } + }); + t.start(); + } + } + + /** This method terminates the caller's process. */ + private static void halt(String reason, int exitCode) { Runtime.getRuntime().halt(exitCode); } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/XMLNode.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/XMLNode.java new file mode 100644 index 00000000..11b2398b --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/XMLNode.java @@ -0,0 +1,479 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.Map.Entry; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; + +/** Immutable; this class represents an XML element node. */ + +public final class XMLNode implements Iterable { + + /** The type of the element; never null. */ + private String type = ""; + + /** If type is text, this is the text. */ + private String text = ""; + + /** The set of (key,value) pairs; never null. */ + private final Map map = new LinkedHashMap(); + + /** The list of direct children nodes. */ + private final List sub = new ArrayList(); + + /** Constructs an empty XMLNode object. */ + private XMLNode() { } + + /** Returns the number of direct subnodes. */ + public int count() { return sub.size(); } + + /** Returns an unmodifiable view of the attributes. */ + public Set> attributes() { return Collections.unmodifiableMap(map).entrySet(); } + + /** Dump the content to a String. */ + @Override public String toString() { + StringBuilder sb = new StringBuilder(); + toString(sb, 0); + return sb.toString(); + } + + /** Dump the content to a StringBuilder. */ + public void toString(StringBuilder sb, int indent) { + for(int i=0; i0) { Util.encodeXML(sb, text); sb.append('\n'); return; } + Util.encodeXMLs(sb, "<", type); + for(Map.Entry e: map.entrySet()) { + Util.encodeXMLs(sb, " ", e.getKey(), "=\"", e.getValue(), "\""); + } + if (sub.size()==0) { sb.append("/>\n"); return; } + sb.append(">\n"); + for(XMLNode x:sub) x.toString(sb, indent+2); + for(int i=0; i\n"); + } + + /** Simple parser based on XML Specification 1.0 taking into account XML Specification Errata up to 2008/Jan/18. */ + private static final class XMLParser { + + /** True if we want to read text data also. */ + private final boolean wantText; + + /** The reader for the input XML file. */ + private final Reader reader; + + /** The current x position in the file. */ + private int x = 1; + + /** The current y position in the file. */ + private int y = 1; + + /** The current "readahead" character; -2 if the readahead cache is empty; -1 if EOF is detected; otherwise it is one char. */ + private int read = (-2); + + /** Constructor is private, since we want only XMLNode to be able to construct an instance of this class. */ + private XMLParser(Reader reader, boolean wantText) { + this.wantText = wantText; + if (reader instanceof BufferedReader) this.reader = reader; else this.reader = new BufferedReader(reader); + } + + /** Throws an IOException with the given msg, and associate with it the current line and column location. */ + private void malform(String msg) throws IOException { throw new IOException("Error at line "+y+" column "+x+": "+msg); } + + /** Read the next character. + * @throws IOException if end-of-file is reached. + * @throws IOException if an I/O error occurred. + */ + private int read() throws IOException { + if (read<(-1)) read=reader.read(); + if (read<0) { malform("Unexpected end of file."); } else if (read=='\n') { x=1; y++; } else { x++; } + int ans = read; + read = -2; + return ans; + } + + /** Peek without consuming the next character, or return -1 if end-of-file is reached. + * @throws IOException if an I/O error occurred. + */ + private int peek() throws IOException { + if (read<(-1)) read=reader.read(); + return read; + } + + /** Consume up to and including the consecutive characters "char1" and "char2". + * @throws IOException if we reached end-of-file without seeing the pattern. + * @throws IOException if an I/O error occurred. + */ + private void skipUntil(int char1, int char2) throws IOException { + while(true) { + int ch = read(); + if (ch==char1 && peek()==char2) { read=(-2); return; } + } + } + + /** If the next N characters match the given string (where N == length of string), then consume them, else throw IOException. + * @throws IOException if the next N characters do not match the given string. + * @throws IOException if an I/O error occurred. + */ + private void expect(String string) throws IOException { + int saveX=x, saveY=y; + for(int i=0; i' + * AttlistDecl ::= '' + * elementdecl ::= '' + * EntityDecl ::= '' + * PI ::= '' + * Comment ::= '' + * Misc ::= Comment | PI | S + * doctypedecl ::= '' + * intSubset ::= (elementdecl | AttlistDecl | EntityDecl | NotationDecl | PI | Comment | PEReference | S)* + * + * SkipNondata(false) will skip zero or more instance of the below, and thus it will consume (Misc | doctypedecl)* + * SPACE TAB CR LF + * + * + * '' + * + * SkipNondata(true) will skip zero or more instances of the below, and thus it will consume intSubset* + * SPACE TAB CR LF + * + * + * '' + * '[' followed by SkipNondata(true) followed by ']' + * '...' + * "..." + * any char that is not '<' nor '>' nor '[' nor ']' nor ''' nor '"' + */ + + /** Skip as much nondata as possible, then return the first character after that (or -1 if we end up at end-of-file). + *

Specifically, skipNondata(false) consumes (Misc | doctypedecl)* from XML specification + *

Likewise, skipNondata(true) consumes (intSubset)* from XML specification + * @throws IOException if the XML input is malformed. + * @throws IOException if an I/O error occurred. + */ + private int skipNondata(boolean inner) throws IOException { + while(true) { + int ch = peek(); + if (ch<0) return -1; + read = -2; + if (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n') continue; + if (ch == '<') { + ch = read(); + if (ch == '?') { skipUntil('?', '>'); continue; } + if (ch != '!') { read = ch ; return '<'; } + if (peek() == '-') { + read = -2; + if (read()!='-') malform("Expects start of "); + skipUntil('-', '-'); + if (read()!='>') malform("Expects end of "); + continue; + } + if (skipNondata(true)!='>') malform("Expects end of "); + } + else if (!inner || ch == ']' || ch=='>') { return ch; } + else if (ch == '[') { if (skipNondata(true)!=']') malform("Expects end of [...]"); } + else if (ch == '\'' || ch == '\"') { while(read()!=ch) { } } + } + } + + /** Parse an element name or attribute name. + * @throws IOException if the XML input is malformed. + * @throws IOException if an I/O error occurred. + */ + private String parseName() throws IOException { + StringBuilder sb = new StringBuilder(); + while(true) { + int ch = read(); + if (ch==' ' || ch=='\t' || ch=='\r' || ch=='\n' || ch=='=' || ch=='/' || ch=='<' || ch=='>' || ch=='[' || ch==']') { + read=ch; + return sb.toString(); + } + sb.append((char)ch); + } + } + + /** Parse a value up to delim (which is always either ' or "), assuming the initial ' or " has already been consumed. + * @throws IOException if the XML input is malformed. + * @throws IOException if an I/O error occurred. + */ + private String parseValue(int delim) throws IOException { + StringBuilder sb = new StringBuilder(), sb2 = null; + while(true) { + int ch=read(); + if (ch==delim) return sb.toString(); + if (ch=='&') { + if (sb2==null) sb2=new StringBuilder(); else sb2.setLength(0); + while((ch=read()) != ';') sb2.append((char)ch); + if (sb2.length()>2 && sb2.charAt(0)=='#' && sb2.charAt(1)=='x') { + try { ch=Integer.parseInt(sb2.substring(2), 16); } catch(NumberFormatException ex) { ch=(-1); } + } else if (sb2.length()>1 && sb2.charAt(0)=='#'){ + try { ch=Integer.parseInt(sb2.substring(1)); } catch(NumberFormatException ex) { ch=(-1); } + } else { + String name = sb2.toString(); + if (name.equals("amp")) ch='&'; + else if (name.equals("quot")) ch='"'; + else if (name.equals("apos")) ch='\''; + else if (name.equals("lt")) ch='<'; + else if (name.equals("gt")) ch='>'; + else ch=(-1); + } + if (ch<0) malform("The entity \"&"+sb2.toString()+";\" is unknown."); + } + sb.append((char)ch); + } + } + + /* + * Below are the grammar rules for "element": + * ========================================== + * + * element ::= '<' Name (S Name S? '=' S? AttValue)* S? '/>' + * | '<' Name (S Name S? '=' S? AttValue)* S? '>' content '' + * + * content ::= CharData? ((element | Reference | CDSect | PI | Comment) CharData?)* + * CDSect ::= '' Char*)) ']]>' + * CharData ::= [^<&]* - ([^<&]* ']]>' [^<&]*) + */ + + /** Parse an element (and all its subelements), assuming the initial "less than" sign has already been consumed. + * @throws IOException if the XML input is malformed. + * @throws IOException if an I/O error occurred. + */ + private void parseElement(XMLNode target) throws IOException { + target.type = parseName(); + while(true) { + boolean space = false; + int ch = read(); + if (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n') { space=true; ch=skipSpace(); } + if (ch == '=') malform("Unexpected '='"); + if (ch == '/') { + if (read()!='>') malform("Expects '/>'"); + break; + } + if (ch == '>') { + parseContent(target); + if (!target.type.equals(parseName())) malform("Start tag and end tag must have matching types."); + if (skipSpace()!='>') malform("Expects ''"); + break; + } + if (!space) malform("Whitespace needed before a (key,value) pair."); + read = ch; + String key = parseName(); + if (key.length()==0) malform("Attribute name cannot be empty."); + if (skipSpace()!='=') malform("Expects = after the attribute name."); + ch = skipSpace(); + if (ch != '\'' && ch != '\"') malform("Expects \' or \" as the start of the attribute value."); + String value = parseValue(ch); + target.map.put(key, value); + } + } + + /** Parses the content until the rightful closing "LESS THAN SIGN followed by FORWARD SLASH" are both consumed. + * @throws IOException if the XML input is malformed. + * @throws IOException if an I/O error occurred. + */ + private void parseContent(XMLNode parent) throws IOException { + StringBuilder sb = wantText ? new StringBuilder() : null; + again: + while(true) { + if (sb==null) { + while(read()!='<') {} + } else { + sb.append(parseValue('<').replace('\r',' ').replace('\n', ' ')); + parent.addText(sb); + } + int ch=read(); + if (ch=='/') return; + if (ch=='?') { skipUntil('?', '>'); continue; } + if (ch=='!') { + ch=read(); + if (ch=='-') { + if (read()!='-') malform("Expects start of "); + skipUntil('-', '-'); + if (read()!='>') malform("Expects end of "); + continue; + } + if (ch!='[') malform("Expects "); + expect("CDATA["); + for(int ah=0,bh=0; ;) { + ch=read(); + if (ah==']' && bh==']' && ch=='>') { + parent.addText(sb); + continue again; + } else { + if (ah>0 && sb!=null) sb.append((char)ah); + ah=bh; bh=ch; + } + } + } + read = ch; + XMLNode newElem = new XMLNode(); + parseElement(newElem); + parent.sub.add(newElem); + } + } + } + + /** Add a text node by removing all contents from the given StringBuilder and clearing that StringBuilder. */ + private void addText(StringBuilder stringBuilder) { + if (stringBuilder==null || stringBuilder.length()==0) return; + XMLNode x = new XMLNode(); + x.text = stringBuilder.toString(); + stringBuilder.setLength(0); + sub.add(x); + } + + /** Constructs the root XMLNode by parsing an entire XML document, then close the reader afterwards. */ + public XMLNode(Reader reader, boolean parseText) throws IOException { + try { + // document ::= Misc* doctypedecl? Misc* element Misc* + XMLParser parser = new XMLParser(reader, parseText); + if (parser.skipNondata(false)!='<') parser.malform("Expects start of root element."); + parser.parseElement(this); + if (parser.skipNondata(false)!=(-1)) parser.malform("Expects end of file."); + } finally { + Util.close(reader); + } + } + + /** Constructs the root XMLNode by parsing an entire XML document, then close the reader afterwards. */ + public XMLNode(Reader reader) throws IOException { + try { + // document ::= Misc* doctypedecl? Misc* element Misc* + XMLParser parser = new XMLParser(reader, false); + if (parser.skipNondata(false)!='<') parser.malform("Expects start of root element."); + parser.parseElement(this); + if (parser.skipNondata(false)!=(-1)) parser.malform("Expects end of file."); + } finally { + Util.close(reader); + } + } + + /** Constructs the root XMLNode by parsing an entire XML document. */ + public XMLNode(File file) throws IOException { + FileInputStream fis = null; + InputStreamReader reader = null; + try { + // document ::= Misc* doctypedecl? Misc* element Misc* + fis = new FileInputStream(file); + reader = new InputStreamReader(fis, "UTF-8"); + XMLParser parser = new XMLParser(reader, false); + if (parser.skipNondata(false)!='<') parser.malform("Expects start of root element."); + parser.parseElement(this); + if (parser.skipNondata(false)!=(-1)) parser.malform("Expects end of file."); + } finally { + Util.close(reader); + Util.close(fis); + } + } + + /** Returns the type of the element. */ + public String getType() { return type; } + + /** Returns the text if this is a text node, returns "" otherwise. */ + public String getText() { return text; } + + /** Returns true if the type of this element is equal to the given type. */ + public boolean is(String type) { return this.type.equals(type); } + + /** Returns a read-only iterator over the immediate subelements. */ + public Iterator iterator() { return Collections.unmodifiableList(sub).iterator(); } + + /** Returns a read-only iteration of the immediate subelements whose type is equal to the given type. */ + public Iterable getChildren(final String type) { + return new Iterable() { + public Iterator iterator() { + return new Iterator() { + private final Iterator it = sub.iterator(); + private XMLNode peek = null; + public boolean hasNext() { + while(true) { + if (peek!=null && peek.type.equals(type)) return true; + if (!it.hasNext()) return false; else peek=it.next(); + } + } + public XMLNode next() { if (!hasNext()) throw new NoSuchElementException(); XMLNode ans=peek; peek=null; return ans; } + public void remove() { throw new UnsupportedOperationException(); } + }; + } + }; + } + + /** Returns the value associated with the given attribute name; if the attribute doesn't exist, return "". */ + public String getAttribute(String name) { + String ans = map.get(name); + return (ans==null) ? "" : ans; + } + + /** Returns the value associated with the given attribute name; if the attribute doesn't exist, return the defaultValue. */ + public String getAttribute(String name, String defaultValue) { + String ans = map.get(name); + return (ans==null) ? defaultValue : ans; + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/package.html b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/package.html new file mode 100644 index 00000000..2087435e --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4/package.html @@ -0,0 +1,5 @@ + + +This package contains general data structures and helper classes. + + diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/ast/Attr.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/ast/Attr.java new file mode 100644 index 00000000..0b576d74 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/ast/Attr.java @@ -0,0 +1,133 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4compiler.ast; + +import edu.mit.csail.sdg.alloy4.Pos; + +/** Immutable; represents attributes that can be associated with Signatures and some other AST objects. */ + +public final class Attr { + + /** This class contains all possible attribute types. */ + public enum AttrType { + + /** WHERE; if a Sig has a WHERE attribute, it defines where the sig was declared in the user model. */ + WHERE("where"), + + /** ABSTRACT; if a PrimSig is abstract, it is equal to the union of its subsigs. */ + ABSTRACT("abstract"), + + /** SOME; if a Sig is some, it has at least one atom. */ + SOME("some"), + + /** ONE; if a Sig is one, it has exactly one atom. */ + ONE("one"), + + /** LONE; if a Sig is lone, it has at most one atom. */ + LONE("lone"), + + /** EXACT; if a SubsetSig is exact, it is equal to the union of its parents. */ + EXACT("exact"), + + /** SUBSIG; every PrimSig (including the builtin sigs) has the SUBSIG attribute set, and the SUBSET attribute unset. */ + SUBSIG("subsig"), + + /** SUBSET; every SubsetSig has the SUBSET attribute set, and the SUBSIG attribute unset. */ + SUBSET("subset"), + + /** META; if a Sig has the META attribute, it means it is a META atom corresponding to some real signature or field. */ + META("meta"), + + /** PRIVATE; if a Sig has the PRIVATE attribute, it means its label is private within the same module. */ + PRIVATE("private"), + + /** BUILTIN; every builtin Sig has the BUILTIN attribute, and every non-builtin Sig does not. */ + BUILTIN("builtin"), + + /** ENUM; if a PrimSig has the ENUM attribute, it is toplevel and abstract and has only singleton children. */ + ENUM("enum"); + + /** The label for this attribute type. */ + private final String label; + + /** Constructor for this attribute type. */ + private AttrType(String label) { this.label = label; } + + /** Construct an attribute of this type with this position; if pos==null, it is treated as Pos.UNKNOWN. */ + public final Attr make(Pos pos) { return new Attr(this, pos); } + + /** Construct an attribute of this type with this position; if pos==null, this method returns null. */ + public final Attr makenull(Pos pos) { return pos==null ? null : new Attr(this, pos); } + + /** Returns the combined position for all Attribute of this type in the given array; null entries in the collection are ignored; if none are found we return null. */ + public Pos find(Attr... attributes) { + Pos p = null; + if (attributes!=null) for(Attr a: attributes) if (a!=null && a.type==this) p = a.pos.merge(p); + return p; + } + + /** {@inheritDoc} */ + @Override public final String toString() { return label; } + } + + /** The type of this attribute. */ + public final AttrType type; + + /** The position associated with this attribute. */ + public final Pos pos; + + /** ABSTRACT; if a PrimSig is abstract, it is equal to the union of its subsigs. */ + public static final Attr ABSTRACT = new Attr(AttrType.ABSTRACT, null); + + /** SOME; if a Sig is some, it has at least one atom. */ + public static final Attr SOME = new Attr(AttrType.SOME, null); + + /** ONE; if a Sig is one, it has exactly one atom. */ + public static final Attr ONE = new Attr(AttrType.ONE, null); + + /** LONE; if a Sig is lone, it has at most one atom. */ + public static final Attr LONE = new Attr(AttrType.LONE, null); + + /** EXACT; if a SubsetSig is exact, it is equal to the union of its parents. */ + public static final Attr EXACT = new Attr(AttrType.EXACT, null); + + /** SUBSIG; every PrimSig (including the builtin sigs) has the SUBSIG attribute set, and the SUBSET attribute unset. */ + public static final Attr SUBSIG = new Attr(AttrType.SUBSIG, null); + + /** SUBSET; every SubsetSig has the SUBSET attribute set, and the SUBSIG attribute unset. */ + public static final Attr SUBSET = new Attr(AttrType.SUBSET, null); + + /** META; if a Sig has the META attribute, it means it is a META atom corresponding to some real signature or field. */ + public static final Attr META = new Attr(AttrType.META, null); + + /** PRIVATE; if a Sig has the PRIVATE attribute, it means its label is private within the same module. */ + public static final Attr PRIVATE = new Attr(AttrType.PRIVATE, null); + + /** BUILTIN; every builtin Sig has the BUILTIN attribute, and every non-builtin Sig does not. */ + public static final Attr BUILTIN = new Attr(AttrType.BUILTIN, null); + + /** ENUM; if a PrimSig has the ENUM attribute, it is toplevel and abstract and has only singleton children. */ + public static final Attr ENUM = new Attr(AttrType.ENUM, null); + + /** Construct an attribute of the given type with the given position; if pos==null, it is treated as Pos.UNKNOWN. */ + private Attr(AttrType type, Pos pos) { + this.type = type; + this.pos = (pos==null ? Pos.UNKNOWN : pos); + } + + /** {@inheritDoc} */ + @Override public final String toString() { return type.label; } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/ast/Browsable.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/ast/Browsable.java new file mode 100644 index 00000000..a95b7a57 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/ast/Browsable.java @@ -0,0 +1,107 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4compiler.ast; + +import java.awt.BorderLayout; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; +import java.util.ArrayList; +import java.util.List; +import javax.swing.JFrame; +import javax.swing.JScrollPane; +import javax.swing.border.EmptyBorder; +import edu.mit.csail.sdg.alloy4.ConstList; +import edu.mit.csail.sdg.alloy4.Listener; +import edu.mit.csail.sdg.alloy4.OurTree; +import edu.mit.csail.sdg.alloy4.Pos; +import edu.mit.csail.sdg.alloy4.Util; + +/** This abstract class represents a node that can be browsed in the graphical parse tree viewer. */ + +public abstract class Browsable { + + /** Returns a Pos object representing the position of this Expr. */ + public Pos pos() { return Pos.UNKNOWN; } + + /** Returns a Pos object representing the entire span of this Expr and all its subexpressions. */ + public Pos span() { return pos(); } + + /** Returns the description (as HTML) to show for this node. */ + public abstract String getHTML(); + + /** Returns a list of subnodes for this node. */ + public abstract List getSubnodes(); + + /** Construct a Browsable node with the given HTML description and the given single subnode. */ + public static final Browsable make(final Pos pos, final Pos span, final String html, Browsable subnode) { + return make(pos, span, html, Util.asList(subnode)); + } + + /** Construct a Browsable node with the given HTML description and the given single subnode. */ + public static final Browsable make(final String html, Browsable subnode) { + return make(Pos.UNKNOWN, Pos.UNKNOWN, html, Util.asList(subnode)); + } + + /** Construct a Browsable node with the given HTML description and the given 0 or more subnodes. */ + public static final Browsable make(final String html, final List subnodes) { + return make(Pos.UNKNOWN, Pos.UNKNOWN, html, subnodes); + } + + /** Construct a Browsable node with the given HTML description and the given 0 or more subnodes. */ + public static final Browsable make(final Pos pos, final Pos span, final String html, final List subnodes) { + final ConstList constlist = ConstList.make(subnodes); + return new Browsable() { + @Override public Pos pos() { return pos; } + @Override public Pos span() { return span; } + @Override public String getHTML() { return html; } + @Override public List getSubnodes() { return constlist; } + }; + } + + /** Display this node and its subnodes as a tree; if listener!=null, it will receive OurTree.Event.SELECT events when nodes are selected. */ + public final JFrame showAsTree(Listener listener) { + final OurTree tree = new OurTree(12) { + private static final long serialVersionUID = 0; + private final boolean onWindows = Util.onWindows(); + {do_start();} + @Override public String convertValueToText(Object val, boolean selected, boolean expanded, boolean leaf, int row, boolean focus) { + String c = ">"; + String x = (val instanceof Browsable) ? ((Browsable)val).getHTML() : Util.encode(String.valueOf(val)); + if (onWindows) c = selected ? " style=\"color:#ffffff;\">" : " style=\"color:#000000;\">"; + return ""; + } + @Override public List do_ask(Object parent) { + if (parent instanceof Browsable) return ((Browsable)parent).getSubnodes(); else return new ArrayList(); + } + @Override public Object do_root() { return Browsable.this; } + }; + tree.setBorder(new EmptyBorder(3, 3, 3, 3)); + final JScrollPane scr = new JScrollPane(tree, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); + scr.addFocusListener(new FocusListener() { + public void focusGained(FocusEvent e) { tree.requestFocusInWindow(); } + public void focusLost(FocusEvent e) { } + }); + final JFrame x = new JFrame("Parse Tree"); + x.setLayout(new BorderLayout()); + x.add(scr, BorderLayout.CENTER); + x.pack(); + x.setSize(500, 500); + x.setLocationRelativeTo(null); + x.setVisible(true); + if (listener!=null) tree.listeners.add(listener); + return x; + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/ast/Command.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/ast/Command.java new file mode 100644 index 00000000..f6a92038 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/ast/Command.java @@ -0,0 +1,207 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4compiler.ast; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import edu.mit.csail.sdg.alloy4.Err; +import edu.mit.csail.sdg.alloy4.Pos; +import edu.mit.csail.sdg.alloy4.ConstList; +import edu.mit.csail.sdg.alloy4.Util; +import edu.mit.csail.sdg.alloy4.ConstList.TempList; +import edu.mit.csail.sdg.alloy4.ErrorSyntax; + +/** Immutable; reresents a "run" or "check" command. + * + *

Invariant: expects == -1, 0, or 1 + *

Invariant: overall >= -1 + *

Invariant: bitwidth >= -1 + *

Invariant: maxseq >= -1 + *

Invariant: maxstring >= -1 + */ + +public final class Command extends Browsable { + + /** If nonnull, it means this command depends on this parent command. */ + public final Command parent; + + /** The position in the original file where this command was declared; never null. */ + public final Pos pos; + + /** The label for the command; it is just for pretty-printing and does not have to be unique. */ + public final String label; + + /** true if this is a "check"; false if this is a "run". */ + public final boolean check; + + /** The overall scope (0 or higher) (Or -1 if there is no overall scope). */ + public final int overall; + + /** The integer bitwidth (0 or higher) (Or -1 if it was not specified). */ + public final int bitwidth; + + /** The maximum sequence length (0 or higher) (Or -1 if it was not specified). */ + public final int maxseq; + + /** The number of String atoms to allocate (0 or higher) (Or -1 if it was not specified). */ + public final int maxstring; + + /** The expected answer (either 0 or 1) (Or -1 if there is no expected answer). */ + public final int expects; + + /** The formula associated with this command. */ + public final Expr formula; + + /** The list of scopes. */ + public final ConstList scope; + + /** This stores a list of Sig whose scope shall be considered "exact", but we may or may not know what its scope is yet. */ + public final ConstList additionalExactScopes; + + /** Returns a human-readable string that summarizes this Run or Check command. */ + @Override public final String toString() { + if (parent!=null) { Command p=parent; while(p.parent!=null) p=p.parent; return p.toString(); } + boolean first=true; + StringBuilder sb=new StringBuilder(check?"Check ":"Run ").append(label); + if (overall>=0 && (bitwidth>=0 || maxseq>=0 || scope.size()>0)) + sb.append(" for ").append(overall).append(" but"); + else if (overall>=0) + sb.append(" for ").append(overall); + else if (bitwidth>=0 || maxseq>=0 || scope.size()>0) + sb.append(" for"); + if (bitwidth>=0) { sb.append(" ").append(bitwidth).append(" int"); first=false; } + if (maxseq>=0) { sb.append(first?" ":", ").append(maxseq).append(" seq"); first=false; } + for(CommandScope e:scope) { + sb.append(first?" ":", ").append(e); + first=false; + } + if (expects>=0) sb.append(" expect ").append(expects); + return sb.toString(); + } + + /** Constructs a new Command object. + * + * @param check - true if this is a "check"; false if this is a "run" + * @param overall - the overall scope (0 or higher) (-1 if no overall scope was specified) + * @param bitwidth - the integer bitwidth (0 or higher) (-1 if it was not specified) + * @param maxseq - the maximum sequence length (0 or higher) (-1 if it was not specified) + * @param formula - the formula that must be satisfied by this command + */ + public Command(boolean check, int overall, int bitwidth, int maxseq, Expr formula) throws ErrorSyntax { + this(null, "", check, overall, bitwidth, maxseq, -1, null, null, formula, null); + } + + /** Constructs a new Command object. + * + * @param pos - the original position in the file (must not be null) + * @param label - the label for this command (it is only for pretty-printing and does not have to be unique) + * @param check - true if this is a "check"; false if this is a "run" + * @param overall - the overall scope (0 or higher) (-1 if no overall scope was specified) + * @param bitwidth - the integer bitwidth (0 or higher) (-1 if it was not specified) + * @param maxseq - the maximum sequence length (0 or higher) (-1 if it was not specified) + * @param expects - the expected value (0 or 1) (-1 if no expectation was specified) + * @param scope - a list of scopes (can be null if we want to use default) + * @param additionalExactSig - a list of sigs whose scope shall be considered exact though we may or may not know what the scope is yet + * @param formula - the formula that must be satisfied by this command + */ + public Command(Pos pos, String label, boolean check, int overall, int bitwidth, int maxseq, int expects, Iterable scope, Iterable additionalExactSig, Expr formula, Command parent) { + if (pos==null) pos = Pos.UNKNOWN; + this.formula = formula; + this.pos = pos; + this.label = (label==null ? "" : label); + this.check = check; + this.overall = (overall<0 ? -1 : overall); + this.bitwidth = (bitwidth<0 ? -1 : bitwidth); + this.maxseq = (maxseq<0 ? -1 : maxseq); + this.maxstring = (-1); + this.expects = (expects<0 ? -1 : (expects>0 ? 1 : 0)); + this.scope = ConstList.make(scope); + this.additionalExactScopes = ConstList.make(additionalExactSig); + this.parent = parent; + } + + /** Constructs a new Command object where it is the same as the current object, except with a different formula. */ + public Command change(Expr newFormula) { + return new Command(pos, label, check, overall, bitwidth, maxseq, expects, scope, additionalExactScopes, newFormula, parent); + } + + /** Constructs a new Command object where it is the same as the current object, except with a different scope. */ + public Command change(ConstList scope) { + return new Command(pos, label, check, overall, bitwidth, maxseq, expects, scope, additionalExactScopes, formula, parent); + } + + /** Constructs a new Command object where it is the same as the current object, except with a different list of "additional exact sigs". */ + public Command change(Sig... additionalExactScopes) { + return new Command(pos, label, check, overall, bitwidth, maxseq, expects, scope, Util.asList(additionalExactScopes), formula, parent); + } + + /** Constructs a new Command object where it is the same as the current object, except with a different scope for the given sig. */ + public Command change(Sig sig, boolean isExact, int newScope) throws ErrorSyntax { return change(sig, isExact, newScope, newScope, 1); } + + /** Constructs a new Command object where it is the same as the current object, except with a different scope for the given sig. */ + public Command change(Sig sig, boolean isExact, int startingScope, int endingScope, int increment) throws ErrorSyntax { + for(int i=0; i(scope).set(i, sc).makeConst()); + } + CommandScope sc = new CommandScope(Pos.UNKNOWN, sig, isExact, startingScope, endingScope, increment); + return change(Util.append(scope, sc)); + } + + /** Helper method that returns the scope corresponding to a given sig (or return null if the sig isn't named in this command) */ + public CommandScope getScope(Sig sig) { + for(int i=0; i getGrowableSigs() { + TempList answer = new TempList(); + for(CommandScope sc: scope) if (sc.startingScope != sc.endingScope) answer.add(sc.sig); + return answer.makeConst(); + } + + /** Return a modifiable copy of the set of all String constants used in this command or in any facts embedded in this command. */ + public Set getAllStringConstants(Iterable sigs) throws Err { + final Set set = new HashSet(); + final VisitQuery findString = new VisitQuery() { + @Override public final Object visit(ExprConstant x) throws Err { + if (x.op==ExprConstant.Op.STRING) set.add(x.string); + return null; + } + }; + for(Command c=this; c!=null; c=c.parent) c.formula.accept(findString); + for(Sig s: sigs) { + for(Expr e: s.getFacts()) e.accept(findString); + for(Decl d: s.getFieldDecls()) d.expr.accept(findString); + } + return set; + } + + /** {@inheritDoc} */ + @Override public final Pos pos() { return pos; } + + /** {@inheritDoc} */ + @Override public final Pos span() { return pos; } + + /** {@inheritDoc} */ + @Override public String getHTML() { return (check?"check ":"run ") + label; } + + /** {@inheritDoc} */ + @Override public List getSubnodes() { return formula==null ? (new ArrayList(0)) : Util.asList(formula); } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/ast/CommandScope.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/ast/CommandScope.java new file mode 100644 index 00000000..5ab2cf84 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/ast/CommandScope.java @@ -0,0 +1,93 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4compiler.ast; + +import edu.mit.csail.sdg.alloy4.ErrorSyntax; +import edu.mit.csail.sdg.alloy4.Pos; +import edu.mit.csail.sdg.alloy4.Util; + +/** Immutable; reresents a scope in a "run" or "check" command. + * + *

Invariant: sig != null + *

Invariant: endingScope >= startingScope >= 0 + *

Invariant: increment > 0 + */ + +public class CommandScope { + + /** The position in the original source file where this scope was declared; can be Pos.UNKNOWN if unknown. */ + public final Pos pos; + + /** The sig whose scope is being given by this CommandScope object. */ + public final Sig sig; + + /** True iff the scope is an exact scope. */ + public final boolean isExact; + + /** The starting scope. */ + public final int startingScope; + + /** The ending scope; if this sig is not a growing sig, then this.startingScope==this.endingScope. */ + public final int endingScope; + + /** The scope increment; if this sig is not a growing sig, then this.increment is ignored. */ + public final int increment; + + /** Construct a new CommandScope object. + * @param sig - the sig for this scope + * @param isExact - true iff the scope is intended to be exact + * @param scope - the scope + * @throws ErrorSyntax if scope is less than zero + */ + public CommandScope(Sig sig, boolean isExact, int scope) throws ErrorSyntax { this(null, sig, isExact, scope, scope, 1); } + + /** Construct a new CommandScope object. + * @param pos - the position where this scope is given + * @param sig - the sig for this scope + * @param isExact - true iff the scope is intended to be exact + * @param startingScope - the starting scope + * @param endingScope - the ending scope (if this sig is not intended to be growable, then startingScope should equal endingScope) + * @param increment - the scope increment (if this sig is not intended to be growable, then this field is ignored) + * @throws ErrorSyntax if startingScope is less than zero + * @throws ErrorSyntax if endingScope is less than startingScope + * @throws ErrorSyntax if increment is less than one + */ + public CommandScope(Pos pos, Sig sig, boolean isExact, int startingScope, int endingScope, int increment) throws ErrorSyntax { + if (pos == null) pos = Pos.UNKNOWN; + if (sig == null) throw new NullPointerException(); + if (startingScope < 0) throw new ErrorSyntax(pos, "Sig "+sig+" cannot have a negative starting scope ("+startingScope+")"); + if (endingScope < 0) throw new ErrorSyntax(pos, "Sig "+sig+" cannot have a negative ending scope ("+endingScope+")"); + if (endingScope < startingScope) throw new ErrorSyntax(pos, "Sig "+sig+" cannot have an ending scope ("+endingScope+") smaller than its starting scope ("+startingScope+")"); + if (startingScope == endingScope) increment = 1; + if (increment < 1) throw new ErrorSyntax(pos, "Sig "+sig+"'s increment value cannot be "+increment+".\nThe increment must be 1 or greater."); + this.pos = pos; + this.sig = sig; + this.isExact = isExact; + this.startingScope = startingScope; + this.endingScope = endingScope; + this.increment = increment; + } + + /** {@inheritDoc} */ + @Override public String toString() { + return (isExact ? "exactly " : "") + + startingScope + + (endingScope!=startingScope ? (".."+endingScope) : "") + + (increment > 1 ? (":"+increment) : "") + + " " + + Util.tail(sig.label); + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/ast/Decl.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/ast/Decl.java new file mode 100644 index 00000000..46a6df10 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/ast/Decl.java @@ -0,0 +1,88 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4compiler.ast; + +import java.util.List; +import edu.mit.csail.sdg.alloy4.Pos; +import edu.mit.csail.sdg.alloy4.ConstList; + +/** Immutable; this declaration binds a list of names to an expression. */ + +public final class Decl { + + /** If nonnull, then this decl is private (and this.isPrivate is the location of the "private" keyword) */ + public final Pos isPrivate; + + /** If nonnull, then each name is disjoint (and this.disjoint is the location of the "disjoint" keyword) */ + public final Pos disjoint; + + /** If nonnull, then each atom of this sig has disjoint value for this field (and this.disjoint2 is the location of the "disjoint" keyword) */ + public final Pos disjoint2; + + /** The list of names. */ + public final ConstList names; + + /** The value that the list of names are bound to. */ + public final Expr expr; + + /** Caches the span() result. */ + private Pos span; + + /** Returns a Pos object representing the entire span of this expression and all its subexpressions. */ + public Pos span() { + Pos p = span; + if (p == null) { + p = expr.span().merge(disjoint).merge(disjoint2); + for(ExprHasName n: names) p = p.merge(n.span()); + span = p; + } + return p; + } + + /** This constructs a declaration; the list of names must not be empty. */ + public Decl(Pos isPrivate, Pos disjoint, Pos disjoint2, List names, Expr expr) { + if (names.size()==0) throw new NullPointerException(); + this.isPrivate = isPrivate; + this.disjoint = (names.size()>1 ? disjoint : null); + this.disjoint2 = disjoint2; + this.names = ConstList.make(names); + this.expr = expr; + } + + /** Return the first variable in this declaration. */ + public ExprHasName get() { + return names.get(0); + } + + /** If the list of declaration contains a duplicate name, return one such duplicate name, else return null. */ + public static ExprHasName findDuplicateName (List list) { + for(int i=0; i Invariant: pos!=null + *
Invariant: type!=null + *
Invariant: type==EMPTY iff errors.size()>0 + *
Invariant: mult==0 || mult==1 || mult==2 + *
Invariant: weight>0 + */ + +public abstract class Expr extends Browsable { + + /** The filename, line, and column position in the original Alloy model file (cannot be null). */ + public final Pos pos; + + /** The filename, line, and column position in the original Alloy model file of the closing bracket). */ + public final Pos closingBracket; + + /** The type for this node; EMPTY if it is not well-typed. */ + final Type type; + + /** Return the type for this node; EMPTY if it is not well-typed. */ + public final Type type() { return type; } + + /** The list of errors on this node; nonempty iff this.type==EMPTY */ + public final JoinableList errors; + + /** This field records whether the node is a multiplicity constraint. + * + *
If it's 2, that means it is an arrow multiplicity constraint (X ?->? X), + * or has the form (A -> B) where A and/or B is an arrow multiplicity constraint. + * + *
If it's 1, that means it is a multiplicity constraint of the form (? X) + * + *
If it's 0, that means it does not have either form. + */ + public final int mult; + + /** Each leaf Expr has a weight value (which can be 0 or higher); + * by default, each nonleaf Expr's weight is the sum of its children's weights. + */ + public final long weight; + + /** True if this expression is not fully resolved. */ + public final boolean ambiguous; + + /** This is an unmodifiable empty list of Err objects. */ + static final JoinableList emptyListOfErrors = new JoinableList(); + + //================================================================================================================// + + /** Constructs a new expression node + * + * @param pos - the original position in the file (can be null if unknown) + * + * @param closingBracket - the original position of the closing bracket (can be null if unknown) + * + * @param ambiguous - true if this node is ExprChoice or it contains an ExprChoice subnode + * + * @param type - the type + * + * @param mult - the multiplicity (which must be 0, 1, or 2) + *
If it's 2, that means it is a multiplicity constraint (X ?->? X), + * or has the form (A -> B) where A and/or B is a multiplicity constraint. + *
If it's 1, that means it is a multiplicity constraint of the form (? X) + *
If it's 0, that means it does not have either form. + * + * @param weight - the weight of this node and all subnodes + * + * @param errors - the list of errors associated with this Expr node (can be null if there are none) + */ + Expr (Pos pos, Pos closingBracket, boolean ambiguous, Type type, int mult, long weight, JoinableList errors) { + this.pos = (pos==null ? Pos.UNKNOWN : pos); + this.closingBracket = (closingBracket==null ? Pos.UNKNOWN : closingBracket); + this.ambiguous = ambiguous; + if (errors==null) errors = emptyListOfErrors; + if (type==EMPTY && errors.size()==0) + errors = errors.make(new ErrorType(pos, "This expression failed to be typechecked")); + this.mult = (mult<0 || mult>2) ? 0 : mult; + this.type = (errors.size()>0 || type==null) ? EMPTY : type; + this.weight = (weight>0) ? weight : 0; + this.errors = errors; + } + + /** This must only be called by Sig's constructor. */ + Expr (Pos pos, Type type) { + this.closingBracket = Pos.UNKNOWN; + this.ambiguous = false; + this.errors = emptyListOfErrors; + this.pos = (pos==null ? Pos.UNKNOWN : pos); + this.type = (type==null || type==EMPTY) ? Type.make((PrimSig)this) : type; + this.mult = 0; + this.weight = 0; + } + + /** {@inheritDoc} */ + @Override public final Pos pos() { return pos; } + + /** Print a textual description of it and all subnodes to a StringBuilder, with the given level of indentation. + * (If indent<0, it will be printed in one line without line break) + */ + public abstract void toString(StringBuilder out, int indent); + + /** Print a brief text description of it and all subnodes. */ + @Override public String toString() { StringBuilder sb = new StringBuilder(); toString(sb,-1); return sb.toString(); } + + /** {@inheritDoc} */ + @Override public final int hashCode() { return super.hashCode(); } + + /** {@inheritDoc} */ + @Override public final boolean equals(Object other) { return super.equals(other); } + + //================================================================================================================// + + /** Accepts the return visitor. */ + public abstract T accept(VisitReturn visitor) throws Err; + + /** Converts this into a "formula" if possible; otherwise, returns an Expr with a nonempty error list */ + public final Expr typecheck_as_formula() { + if (!errors.isEmpty() || type.is_bool) return this; + String msg = "This must be a formula expression.\nInstead, it has the following possible type(s):\n" + type; + return NOOP.make(null, this, new ErrorType(span(), msg), 0); + } + + /** Converts this into an "integer expression" if possible; otherwise, returns an Expr with a nonempty error list */ + public final Expr typecheck_as_int() { + if (!errors.isEmpty()) + return this; + if (type.is_small_int()) + return this; + if (type.is_int()) { + return cast2int(); + } + // else: error + String msg = "This must be an integer expression.\nInstead, it has the following possible type(s):\n"+type; + return NOOP.make(null, this, new ErrorType(span(), msg), 0); + } + + /** Converts this into a "set or relation" if possible; otherwise, returns an Expr with a nonempty error list */ + public final Expr typecheck_as_set() { + if (!errors.isEmpty()) + return this; + if (type.is_small_int()) + return cast2sigint(); + if (type.is_int()) + return this; + if (type.size()>0) + return this; + // else: error + String msg = "This must be a set or relation.\nInstead, it has the following possible type(s):\n"+type; + return NOOP.make(null, this, new ErrorType(span(), msg), 0); + } + + //================================================================================================================// + + /** Resolves this expression if ambiguous. + * (And if t.size()>0, it represents the set of tuples whose presence/absence is relevent to the parent expression) + * (Note: it's possible for t to be EMPTY, or even ambiguous!) + * + *

On success: the return value will be well-typed and unambiguous + *

On failure: the return value's "errors" list will be nonempty + * + *

If we detect any type warnings, we will add the type warnings to the "warnings" collection. + * + * @param warnings - the list that will receive any warning we generate; can be null if we wish to ignore warnings + */ + public abstract Expr resolve(Type t, Collection warnings); + + /** Converts this into a "formula" if possible, then resolves it if ambiguous. + * + *

On success: the return value will be a well-typed unambiguous formula expression + *

On failure: the return value's "errors" list will be nonempty + * + *

If we detect any type warnings, we will add the type warnings to the "warnings" collection. + * + * @param warnings - the list that will receive any warning we generate; can be null if we wish to ignore warnings + */ + public final Expr resolve_as_formula(Collection warnings) { + return typecheck_as_formula().resolve(Type.FORMULA, warnings).typecheck_as_formula(); + } + + /** Converts this into an "integer expression" if possible, then resolves it if ambiguous. + * + *

On success: the return value will be a well-typed unambiguous integer expression + *

On failure: the return value's "errors" list will be nonempty + * + *

If we detect any type warnings, we will add the type warnings to the "warnings" collection. + * + * @param warnings - the list that will receive any warning we generate; can be null if we wish to ignore warnings + */ + public final Expr resolve_as_int(Collection warnings) { + return typecheck_as_int().resolve(Type.smallIntType(), warnings).typecheck_as_int(); + } + + /** Converts this into a "set or relation" if possible, then resolves it if ambiguous. + * + *

On success: the return value will be a well-typed unambiguous set or relation expression + *

On failure: the return value's "errors" list will be nonempty + * + *

If we detect any type warnings, we will add the type warnings to the "warnings" collection. + * + * @param warnings - the list that will receive any warning we generate; can be null if we wish to ignore warnings + */ + public final Expr resolve_as_set(Collection warnings) { + Expr x = typecheck_as_set(); + Type t = x.type; + return x.resolve(Type.removesBoolAndInt(t), warnings).typecheck_as_set(); + } + + //================================================================================================================// + + /** Returns true if we can determine the two expressions are equivalent; may sometimes return false. */ + public boolean isSame(Expr obj) { + while(obj instanceof ExprUnary && ((ExprUnary)obj).op==ExprUnary.Op.NOOP) obj=((ExprUnary)obj).sub; + return obj==this; + } + + /** Remove the "NOP" in front of an expression (if any). */ + public final Expr deNOP() { + Expr x = this; + while(x instanceof ExprUnary && ((ExprUnary)x).op==ExprUnary.Op.NOOP) x=((ExprUnary)x).sub; + return x; + } + + /** If this is loneOf/oneOf/someOf/exactlyOf expression, return loneOf/oneOf/someOf/exactlyOf, otherwise returns setOf. */ + public final ExprUnary.Op mult() { + Expr x = this; + while(x instanceof ExprUnary) { + ExprUnary.Op op = ((ExprUnary)x).op; + if (op==ExprUnary.Op.NOOP) { x = ((ExprUnary)x).sub; continue; } + if (op==ExprUnary.Op.ONEOF || op==ExprUnary.Op.LONEOF || op==ExprUnary.Op.SOMEOF || op==ExprUnary.Op.EXACTLYOF) return op; + break; + } + return ExprUnary.Op.SETOF; + } + + /** A return visitor that determines whether the node (or a subnode) contains a predicate/function call. */ + private static final VisitQuery hasCall = new VisitQuery() { + @Override public final Object visit(ExprCall x) { return this; } + }; + + /** Returns true if the node is well-typed, unambiguous, and contains a predicate/function call. */ + final boolean hasCall() { + boolean ans = !ambiguous && errors.isEmpty(); + if (ans) { try { ans=accept(hasCall)!=null; } catch(Err ex) { ans=false; } } // This exception should not occur + return ans; + } + + /** Returns true if the node is well-typed, unambiguous, and contains the given variable. */ + public final boolean hasVar(final ExprVar goal) { + if (ambiguous || !errors.isEmpty()) return false; + VisitQuery hasVar = new VisitQuery() { + @Override public final Object visit(ExprVar x) throws Err { + if (x==goal) return this; else return null; + } + }; + boolean ans; + try { ans=accept(hasVar)!=null; } catch(Err ex) { return false; } // This exception should not occur + return ans; + } + + /** Transitively returns a set that contains all predicates/functions that this expression calls directly or indirectly. */ + public final Iterable findAllFunctions() { + final LinkedHashSet seen = new LinkedHashSet(); + final List todo = new ArrayList(); + final VisitQuery q = new VisitQuery() { + @Override public final Object visit(ExprCall x) { if (seen.add(x.fun)) todo.add(x.fun); return null; } + }; + try { + q.visitThis(this); + while(!todo.isEmpty()) { q.visitThis(todo.remove(todo.size()-1).getBody()); } + } catch(Err ex) { } // Exception should not occur + return seen; + } + + /** Returns the height of the abstract syntax tree starting from this node. */ + public abstract int getDepth(); + + //================================================================================// + // Below are convenience methods for building up expressions from subexpressions. // + //================================================================================// + + /** Returns the formula (this and x) + *

this and x must both be formulas + *

Note: as a special guarantee, if x==null, then the method will return this Expr object as-is. + */ + public final Expr and(Expr x) { return (x==null) ? this : ExprBinary.Op.AND.make(span().merge(x.span()), null, this, x); } + + /** Returns the formula (this or x) + *

this and x must both be formulas + *

Note: as a special guarantee, if x==null, then the method will return this Expr object as-is. + */ + public final Expr or(Expr x) { return (x==null) ? this : ExprBinary.Op.OR.make(span().merge(x.span()), null, this, x); } + + /** Returns the formula (this iff x) + *

this and x must both be formulas + */ + public final Expr iff(Expr x) { return ExprBinary.Op.IFF.make(span().merge(x.span()), null, this, x); } + + /** Returns the formula (this implies x) + *

this and x must both be formulas + */ + public final Expr implies(Expr x) { return ExprBinary.Op.IMPLIES.make(span().merge(x.span()), null, this, x); } + + /** Returns the expression (this.x) + *

1. this must be a set or relation + *

2. x must be a set or relation + *

3. at most one of them can be a unary set + */ + public final Expr join(Expr x) { return ExprBinary.Op.JOIN.make(span().merge(x.span()), null, this, x); } + + /** Returns the expression (this <: x) + *

this must be a unary set + *

x must be a set or relation + */ + public final Expr domain(Expr x) { return ExprBinary.Op.DOMAIN.make(span().merge(x.span()), null, this, x); } + + /** Returns the expression (this :> x) + *

this must be a set or relation + *

x must be a unary set + */ + public final Expr range(Expr x) { return ExprBinary.Op.RANGE.make(span().merge(x.span()), null, this, x); } + + /** Returns the expression (this intersects x) + *

this and x must be expressions with the same arity + */ + public final Expr intersect(Expr x) { return ExprBinary.Op.INTERSECT.make(span().merge(x.span()), null, this, x); } + + /** Returns the expression (this++x) + *

this and x must be expressions with the same arity + */ + public final Expr override(Expr x) { return ExprBinary.Op.PLUSPLUS.make(span().merge(x.span()), null, this, x); } + + /** Returns the expression (this+x) + *

this and x must be expressions with the same arity, or both be integer expressions + *

Note: as a special guarantee, if x==null, then the method will return this Expr object as-is. + */ + public final Expr plus(Expr x) { return (x==null) ? this : ExprBinary.Op.PLUS.make(span().merge(x.span()), null, this, x); } + public final Expr iplus(Expr x) { return (x==null) ? this : ExprBinary.Op.IPLUS.make(span().merge(x.span()), null, this, x); } + + /** Returns the expression (this-x) + *

this and x must be expressions with the same arity, or both be integer expressions + */ + public final Expr minus(Expr x) { return ExprBinary.Op.MINUS.make(span().merge(x.span()), null, this, x); } + public final Expr iminus(Expr x) { return ExprBinary.Op.IMINUS.make(span().merge(x.span()), null, this, x); } + + /** Returns the formula "this.mul[x]" (the result of multiplying this by x) + *

this and x must both be integer expressions + */ + public final Expr mul(Expr x) { return ExprBinary.Op.MUL.make(span().merge(x.span()), null, this, x); } + + /** Returns the formula "this.div[x]" (the quotient of dividing this by x) + *

this and x must both be integer expressions + */ + public final Expr div(Expr x) { return ExprBinary.Op.DIV.make(span().merge(x.span()), null, this, x); } + + /** Returns the formula "this.rem[x]" (the remainder of dividing this by x) + *

this and x must both be integer expressions + */ + public final Expr rem(Expr x) { return ExprBinary.Op.REM.make(span().merge(x.span()), null, this, x); } + + /** Returns the formula (this==x) + *

this and x must be expressions with the same arity, or both be integer expressions + */ + public final Expr equal(Expr x) { return ExprBinary.Op.EQUALS.make(span().merge(x.span()), null, this, x); } + + /** Returns the formula (this < x) + *

this and x must both be integer expressions + */ + public final Expr lt(Expr x) { return ExprBinary.Op.LT.make(span().merge(x.span()), null, this, x); } + + /** Returns the formula (this <= x) + *

this and x must both be integer expressions + */ + public final Expr lte(Expr x) { return ExprBinary.Op.LTE.make(span().merge(x.span()), null, this, x); } + + /** Returns the formula (this > x) + *

this and x must both be integer expressions + */ + public final Expr gt(Expr x) { return ExprBinary.Op.GT.make(span().merge(x.span()), null, this, x); } + + /** Returns the formula (this >= x) + *

this and x must both be integer expressions + */ + public final Expr gte(Expr x) { return ExprBinary.Op.GTE.make(span().merge(x.span()), null, this, x); } + + /** Returns the integer expression (this << x) + *

this and x must both be integer expressions + */ + public final Expr shl(Expr x) { return ExprBinary.Op.SHL.make(span().merge(x.span()), null, this, x); } + + /** Returns the integer expression (this >>> x) + *

this and x must both be integer expressions + */ + public final Expr shr(Expr x) { return ExprBinary.Op.SHR.make(span().merge(x.span()), null, this, x); } + + /** Returns the integer expression (this >> x) + *

this and x must both be integer expressions + */ + public final Expr sha(Expr x) { return ExprBinary.Op.SHA.make(span().merge(x.span()), null, this, x); } + + /** Returns the formula (this in x) + *

this must be a set or relation + *

x must be a set or relation or multiplicity constraint + *

this and x must have the same arity + */ + public final Expr in(Expr x) { return ExprBinary.Op.IN.make(span().merge(x.span()), null, this, x); } + + /** Returns the expression (this -> x) which can also be regarded as a multiplicity constraint (this set->set x) + *

this must be a set or relation + *

x must be a set or relation + */ + public final Expr product(Expr x) { return ExprBinary.Op.ARROW.make(span().merge(x.span()), null, this, x); } + + /** Returns the multiplicity constraint (this set->some x) + *

this must be a set or relation + *

x must be a set or relation + */ + public final Expr any_arrow_some(Expr x) { return ExprBinary.Op.ANY_ARROW_SOME.make(span().merge(x.span()), null, this, x); } + + /** Returns the multiplicity constraint (this set->one x) + *

this must be a set or relation + *

x must be a set or relation + */ + public final Expr any_arrow_one(Expr x) { return ExprBinary.Op.ANY_ARROW_ONE.make(span().merge(x.span()), null, this, x); } + + /** Returns the multiplicity constraint (this set->lone x) + *

this must be a set or relation + *

x must be a set or relation + */ + public final Expr any_arrow_lone(Expr x) { return ExprBinary.Op.ANY_ARROW_LONE.make(span().merge(x.span()), null, this, x); } + + /** Returns the multiplicity constraint (this some->set x) + *

this must be a set or relation + *

x must be a set or relation + */ + public final Expr some_arrow_any(Expr x) { return ExprBinary.Op.SOME_ARROW_ANY.make(span().merge(x.span()), null, this, x); } + + /** Returns the multiplicity constraint (this some->some x) + *

this must be a set or relation + *

x must be a set or relation + */ + public final Expr some_arrow_some(Expr x) { return ExprBinary.Op.SOME_ARROW_SOME.make(span().merge(x.span()), null, this, x); } + + /** Returns the multiplicity constraint (this some->one x) + *

this must be a set or relation + *

x must be a set or relation + */ + public final Expr some_arrow_one(Expr x) { return ExprBinary.Op.SOME_ARROW_ONE.make(span().merge(x.span()), null, this, x); } + + /** Returns the multiplicity constraint (this some->lone x) + *

this must be a set or relation + *

x must be a set or relation + */ + public final Expr some_arrow_lone(Expr x) { return ExprBinary.Op.SOME_ARROW_LONE.make(span().merge(x.span()), null, this, x); } + + /** Returns the multiplicity constraint (this one->set x) + *

this must be a set or relation + *

x must be a set or relation + */ + public final Expr one_arrow_any(Expr x) { return ExprBinary.Op.ONE_ARROW_ANY.make(span().merge(x.span()), null, this, x); } + + /** Returns the multiplicity constraint (this one->some x) + *

this must be a set or relation + *

x must be a set or relation + */ + public final Expr one_arrow_some(Expr x) { return ExprBinary.Op.ONE_ARROW_SOME.make(span().merge(x.span()), null, this, x); } + + /** Returns the multiplicity constraint (this one->one x) + *

this must be a set or relation + *

x must be a set or relation + */ + public final Expr one_arrow_one(Expr x) { return ExprBinary.Op.ONE_ARROW_ONE.make(span().merge(x.span()), null, this, x); } + + /** Returns the multiplicity constraint (this one->lone x) + *

this must be a set or relation + *

x must be a set or relation + */ + public final Expr one_arrow_lone(Expr x) { return ExprBinary.Op.ONE_ARROW_LONE.make(span().merge(x.span()), null, this, x); } + + /** Returns the multiplicity constraint (this lone->set x) + *

this must be a set or relation + *

x must be a set or relation + */ + public final Expr lone_arrow_any(Expr x) { return ExprBinary.Op.LONE_ARROW_ANY.make(span().merge(x.span()), null, this, x); } + + /** Returns the multiplicity constraint (this lone->some x) + *

this must be a set or relation + *

x must be a set or relation + */ + public final Expr lone_arrow_some(Expr x) { return ExprBinary.Op.LONE_ARROW_SOME.make(span().merge(x.span()), null, this, x); } + + /** Returns the multiplicity constraint (this lone->one x) + *

this must be a set or relation + *

x must be a set or relation + */ + public final Expr lone_arrow_one(Expr x) { return ExprBinary.Op.LONE_ARROW_ONE.make(span().merge(x.span()), null, this, x); } + + /** Returns the multiplicity constraint (this lone->lone x) + *

this must be a set or relation + *

x must be a set or relation + */ + public final Expr lone_arrow_lone(Expr x) { return ExprBinary.Op.LONE_ARROW_LONE.make(span().merge(x.span()), null, this, x); } + + /** Returns the multiplicity constraint (this isSeq->lone x) + *

this must be a set or relation + *

x must be a set or relation + */ + public final Expr isSeq_arrow_lone(Expr x) { return ExprBinary.Op.ISSEQ_ARROW_LONE.make(span().merge(x.span()), null, this, x); } + + /** Returns the expression/integer/formula (this => x else y) + *

this must be a formula + *

x and y must both be expressions of the same arity, or both be integer expressions, or both be formulas + */ + public final Expr ite(Expr x, Expr y) { return ExprITE.make(Pos.UNKNOWN, this, x, y); } + + /** Returns the formula (all...| this) + *

this must be a formula + */ + public final Expr forAll(Decl firstDecl, Decl... moreDecls) throws Err { + Pos p = firstDecl.span(); + for(Decl v: moreDecls) p=p.merge(v.span()); + return ExprQt.Op.ALL.make(p, null, Util.prepend(Util.asList(moreDecls), firstDecl), this); + } + + /** Returns the formula (no...| this) + *

this must be a formula + */ + public final Expr forNo(Decl firstDecl, Decl... moreDecls) throws Err { + Pos p = firstDecl.span(); + for(Decl v: moreDecls) p=p.merge(v.span()); + return ExprQt.Op.NO.make(p, null, Util.prepend(Util.asList(moreDecls), firstDecl), this); + } + + /** Returns the formula (lone...| this) + *

this must be a formula + */ + public final Expr forLone(Decl firstDecl, Decl... moreDecls) throws Err { + Pos p = firstDecl.span(); + for(Decl v: moreDecls) p=p.merge(v.span()); + return ExprQt.Op.LONE.make(p, null, Util.prepend(Util.asList(moreDecls), firstDecl), this); + } + + /** Returns the formula (one ...| this) + *

this must be a formula + */ + public final Expr forOne(Decl firstDecl, Decl... moreDecls) throws Err { + Pos p = firstDecl.span(); + for(Decl v: moreDecls) p=p.merge(v.span()); + return ExprQt.Op.ONE.make(p, null, Util.prepend(Util.asList(moreDecls), firstDecl), this); + } + + /** Returns the formula (some...| this) + *

this must be a formula + */ + public final Expr forSome(Decl firstDecl, Decl... moreDecls) throws Err { + Pos p = firstDecl.span(); + for(Decl v: moreDecls) p=p.merge(v.span()); + return ExprQt.Op.SOME.make(p, null, Util.prepend(Util.asList(moreDecls), firstDecl), this); + } + + /** Returns the comprehension expression {...|this} + *

this must be a formula + *

each declaration must be a "one-of" quantification over a unary set + */ + public final Expr comprehensionOver(Decl firstDecl, Decl... moreDecls) throws Err { + Pos p = firstDecl.span(); + for(Decl v: moreDecls) p=p.merge(v.span()); + return ExprQt.Op.COMPREHENSION.make(p, null, Util.prepend(Util.asList(moreDecls), firstDecl), this); + } + + /** Returns the integer (sum...| this) + *

this must be an integer expression + *

each declaration must be a "one-of" quantification over a unary set + */ + public final Expr sumOver(Decl firstDecl, Decl... moreDecls) throws Err { + Pos p = firstDecl.span(); + for(Decl v: moreDecls) p=p.merge(v.span()); + return ExprQt.Op.SUM.make(p, null, Util.prepend(Util.asList(moreDecls), firstDecl), this); + } + + /** Return the multiplicity expression "some this" + *

this must be already fully typechecked, and must be a unary set + */ + public final Expr someOf() { + return ExprUnary.Op.SOMEOF.make(span(), this); + } + + /** Return a new declaration "v: some this" + *

this must be already fully typechecked, and must be a unary set + *

the label is only used for pretty-printing, and does not have to be unique + */ + public final Decl someOf(String label) throws Err { + Expr x = ExprUnary.Op.SOMEOF.make(span(), this); + ExprVar v = ExprVar.make(x.span(), label, type); + return new Decl(null, null, null, Arrays.asList(v), x); + } + + /** Return the multiplicity expression "lone this" + *

this must be already fully typechecked, and must be a unary set + */ + public final Expr loneOf() { + return ExprUnary.Op.LONEOF.make(span(), this); + } + + /** Return a new declaration "v: lone this" + *

this must be already fully typechecked, and must be a unary set + *

the label is only used for pretty-printing, and does not have to be unique + */ + public final Decl loneOf(String label) throws Err { + Expr x = ExprUnary.Op.LONEOF.make(span(), this); + ExprVar v = ExprVar.make(x.span(), label, type); + return new Decl(null, null, null, Arrays.asList(v), x); + } + + /** Return the multiplicity expression "one this" + *

this must be already fully typechecked, and must be a unary set + */ + public final Expr oneOf() { + return ExprUnary.Op.ONEOF.make(span(), this); + } + + /** Return a new declaration "v: one this" + *

this must be already fully typechecked, and must be a unary set + *

the label is only used for pretty-printing, and does not have to be unique + */ + public final Decl oneOf(String label) throws Err { + Expr x = ExprUnary.Op.ONEOF.make(span(), this); + ExprVar v = ExprVar.make(x.span(), label, type); + return new Decl(null, null, null, Arrays.asList(v), x); + } + + /** Return the multiplicity expression "set this" + *

this must be already fully typechecked, and must be a set or relation + */ + public final Expr setOf() { + return ExprUnary.Op.SETOF.make(span(), this); + } + + /** Return a new declaration "v: set this" + *

this must be already fully typechecked, and must be a set or relation + *

the label is only used for pretty-printing, and does not have to be unique + */ + public final Decl setOf(String label) throws Err { + Expr x = ExprUnary.Op.SETOF.make(span(), this); + ExprVar v = ExprVar.make(x.span(), label, type); + return new Decl(null, null, null, Arrays.asList(v), x); + } + + /** Returns the formula (not this) + *

this must be a formula + */ + public final Expr not() { return ExprUnary.Op.NOT.make(span(), this); } + + /** Returns the formula (no this) + *

this must be a set or a relation + */ + public final Expr no() { return ExprUnary.Op.NO.make(span(), this); } + + /** Returns the formula (some this) + *

this must be a set or a relation + */ + public final Expr some() { return ExprUnary.Op.SOME.make(span(), this); } + + /** Returns the formula (lone this) + *

this must be a set or a relation + */ + public final Expr lone() { return ExprUnary.Op.LONE.make(span(), this); } + + /** Returns the formula (one this) + *

this must be a set or a relation + */ + public final Expr one() { return ExprUnary.Op.ONE.make(span(), this); } + + /** Returns the expression (~this) + *

this must be a binary relation + */ + public final Expr transpose() { return ExprUnary.Op.TRANSPOSE.make(span(), this); } + + /** Returns the expression (*this) + *

this must be a binary relation + */ + public final Expr reflexiveClosure() { return ExprUnary.Op.RCLOSURE.make(span(), this); } + + /** Returns the expression (^this) + *

this must be a binary relation + */ + public final Expr closure() { return ExprUnary.Op.CLOSURE.make(span(), this); } + + /** Returns the integer expression (#this) truncated to the current integer bitwidth. + *

this must be a set or a relation + */ + public final Expr cardinality() { return ExprUnary.Op.CARDINALITY.make(span(), this); } + + /** Returns the integer expression "int[this]" + *

this must be a unary set + */ + public final Expr cast2int() { return ExprUnary.Op.CAST2INT.make(span(), this); } + + /** Returns the singleton set "Int[this]" + *

this must be an integer expression + */ + public final Expr cast2sigint() { return ExprUnary.Op.CAST2SIGINT.make(span(), this); } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/ast/ExprBad.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/ast/ExprBad.java new file mode 100644 index 00000000..670b09d5 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/ast/ExprBad.java @@ -0,0 +1,70 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4compiler.ast; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import edu.mit.csail.sdg.alloy4.Pos; +import edu.mit.csail.sdg.alloy4.Err; +import edu.mit.csail.sdg.alloy4.ErrorWarning; +import edu.mit.csail.sdg.alloy4.JoinableList; +import static edu.mit.csail.sdg.alloy4compiler.ast.Type.EMPTY; + +/** Immutable; represents an illegal node. + * + *

Invariant: this.type==EMPTY && this.errors.size()==1 + */ + +public final class ExprBad extends Expr { + + /** The original source text that caused the error. */ + private final String originalText; + + /** {@inheritDoc} */ + @Override public Pos span() { return pos; } + + /** {@inheritDoc} */ + @Override public void toString(StringBuilder out, int indent) { + if (indent<0) { + out.append(originalText); + } else { + for(int i=0; i(error)); + this.originalText = originalText; + } + + /** {@inheritDoc} */ + public int getDepth() { return 1; } + + /** {@inheritDoc} */ + @Override public Expr resolve(Type t, Collection warns) { return this; } + + /** {@inheritDoc} */ + @Override public final T accept(VisitReturn visitor) throws Err { return visitor.visit(this); } + + /** {@inheritDoc} */ + @Override public String getHTML() { return "error (parser or typechecker failed)"; } + + /** {@inheritDoc} */ + @Override public List getSubnodes() { return new ArrayList(0); } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/ast/ExprBadCall.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/ast/ExprBadCall.java new file mode 100644 index 00000000..21e1e767 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/ast/ExprBadCall.java @@ -0,0 +1,131 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4compiler.ast; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import edu.mit.csail.sdg.alloy4.Pos; +import edu.mit.csail.sdg.alloy4.ConstList; +import edu.mit.csail.sdg.alloy4.JoinableList; +import edu.mit.csail.sdg.alloy4.Err; +import edu.mit.csail.sdg.alloy4.ErrorType; +import edu.mit.csail.sdg.alloy4.ErrorWarning; +import static edu.mit.csail.sdg.alloy4compiler.ast.Type.EMPTY; + +/** Immutable; represents an illegal pred/fun call. + * + *

Invariant: this.type==EMPTY && this.errors.size()>0 + */ + +public final class ExprBadCall extends Expr { + + /** The function. */ + public final Func fun; + + /** The unmodifiable list of arguments. */ + public final ConstList args; + + /** The extra weight added to this node on top of the combined weights of the arguments. */ + public final long extraWeight; + + /** Caches the span() result. */ + private Pos span=null; + + /** {@inheritDoc} */ + @Override public Pos span() { + Pos p=span; + if (p==null) { + p=pos.merge(closingBracket); + for(Expr a:args) p=p.merge(a.span()); + span=p; + } + return p; + } + + /** {@inheritDoc} */ + @Override public void toString(StringBuilder out, int indent) { + if (indent<0) { + out.append(fun.label).append('['); + for(int i=0; i0) out.append(", "); args.get(i).toString(out,-1); } + out.append(']'); + } else { + for(int i=0; i args, JoinableList errors, long extraWeight, long weight) { + super(pos, closingBracket, ambiguous, EMPTY, 0, weight, errors); + this.fun=fun; + this.args=args; + this.extraWeight=extraWeight; + } + + /** Constructs an ExprBadCall object. */ + public static Expr make(final Pos pos, final Pos closingBracket, final Func fun, ConstList args, long extraPenalty) { + if (extraPenalty<0) extraPenalty=0; + if (args==null) args=ConstList.make(); + long weight = extraPenalty; + boolean ambiguous = false; + JoinableList errors = emptyListOfErrors; + for(Expr x: args) { + weight = weight + x.weight; + ambiguous = ambiguous || x.ambiguous; + errors = errors.make(x.errors); + } + if (errors.isEmpty()) { + StringBuilder sb=new StringBuilder("This cannot be a correct call to "); + sb.append(fun.isPred?"pred ":"fun "); + sb.append(fun.label); + sb.append(fun.count()==0 ? ".\nIt has no parameters,\n" : ".\nThe parameters are\n"); + for(ExprVar v:fun.params()) { + sb.append(" ").append(v.label).append(": ").append(v.type).append('\n'); + } + sb.append(args.size()==0 || fun.count()==0 ? "so the arguments cannot be empty.\n" : "so the arguments cannot be\n"); + int n = fun.count(); + for(Expr v: args) { + if (n==0) break; else n--; + sb.append(" "); + v.toString(sb, -1); + sb.append(" (type = ").append(v.type).append(")\n"); + } + errors = errors.make(new ErrorType(pos, sb.toString())); + } + return new ExprBadCall(pos, closingBracket, ambiguous, fun, args, errors, extraPenalty, weight); + } + + /** {@inheritDoc} */ + public int getDepth() { + int max = 1; + for(Expr x: args) { int tmp=x.getDepth(); if (max warns) { return this; } + + /** {@inheritDoc} */ + @Override public final T accept(VisitReturn visitor) throws Err { return visitor.visit(this); } + + /** {@inheritDoc} */ + @Override public String getHTML() { return "error (parser or typechecker failed)"; } + + /** {@inheritDoc} */ + @Override public List getSubnodes() { return new ArrayList(0); } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/ast/ExprBadJoin.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/ast/ExprBadJoin.java new file mode 100644 index 00000000..5284b4e0 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/ast/ExprBadJoin.java @@ -0,0 +1,103 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4compiler.ast; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import edu.mit.csail.sdg.alloy4.Pos; +import edu.mit.csail.sdg.alloy4.Err; +import edu.mit.csail.sdg.alloy4.ErrorType; +import edu.mit.csail.sdg.alloy4.ErrorWarning; +import edu.mit.csail.sdg.alloy4.JoinableList; +import static edu.mit.csail.sdg.alloy4compiler.ast.Type.EMPTY; + +/** Immutable; represents an illegal relation join. + * + *

Invariant: this.type==EMPTY && this.errors.size()>0 + */ + +public final class ExprBadJoin extends Expr { + + /** The left-hand-side expression. */ + public final Expr left; + + /** The right-hand-side expression. */ + public final Expr right; + + /** Caches the span() result. */ + private Pos span=null; + + /** {@inheritDoc} */ + @Override public Pos span() { + Pos p=span; + if (p==null) span = (p = pos.merge(closingBracket).merge(right.span()).merge(left.span())); + return p; + } + + /** {@inheritDoc} */ + @Override public void toString(StringBuilder out, int indent) { + if (indent<0) { + left.toString(out,-1); + out.append('.'); + right.toString(out,-1); + } else { + for(int i=0; i errors) { + super(pos, closingBracket, (left.ambiguous || right.ambiguous), EMPTY, 0, 0, errors); + this.left=left; + this.right=right; + } + + /** Constructs an ExprBadJoin node. */ + public static Expr make(Pos pos, Pos closingBracket, Expr left, Expr right) { + JoinableList errors = left.errors.make(right.errors); + if (errors.isEmpty()) { + StringBuilder sb=new StringBuilder("This cannot be a legal relational join where\nleft hand side is "); + left.toString(sb,-1); + sb.append(" (type = ").append(left.type).append(")\nright hand side is "); + right.toString(sb,-1); + sb.append(" (type = ").append(right.type).append(")\n"); + errors = errors.make(new ErrorType(pos, sb.toString())); + } + return new ExprBadJoin(pos, closingBracket, left, right, errors); + } + + /** {@inheritDoc} */ + public int getDepth() { + int a=left.getDepth(), b=right.getDepth(); + if (a>=b) return 1+a; else return 1+b; + } + + /** {@inheritDoc} */ + @Override public Expr resolve(Type t, Collection warns) { return this; } + + /** {@inheritDoc} */ + @Override public final T accept(VisitReturn visitor) throws Err { return visitor.visit(this); } + + /** {@inheritDoc} */ + @Override public String getHTML() { return "error (parser or typechecker failed)"; } + + /** {@inheritDoc} */ + @Override public List getSubnodes() { return new ArrayList(0); } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/ast/ExprBinary.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/ast/ExprBinary.java new file mode 100644 index 00000000..0a2ad703 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/ast/ExprBinary.java @@ -0,0 +1,521 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4compiler.ast; + +import static edu.mit.csail.sdg.alloy4compiler.ast.Type.EMPTY; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import edu.mit.csail.sdg.alloy4.Err; +import edu.mit.csail.sdg.alloy4.ErrorSyntax; +import edu.mit.csail.sdg.alloy4.ErrorType; +import edu.mit.csail.sdg.alloy4.ErrorWarning; +import edu.mit.csail.sdg.alloy4.JoinableList; +import edu.mit.csail.sdg.alloy4.Pos; +import edu.mit.csail.sdg.alloy4.Util; +import edu.mit.csail.sdg.alloy4compiler.ast.Sig.Field; +import edu.mit.csail.sdg.alloy4compiler.ast.Sig.PrimSig; +import edu.mit.csail.sdg.alloy4compiler.ast.Type.ProductType; + +/** Immutable; represents an expression of the form (x OP y). + * + *

Invariant: type!=EMPTY => (this.mult!=1) + *

Invariant: type!=EMPTY => (this.mult==2 => this.op is one of the 17 arrow operators) + *

Invariant: type!=EMPTY => (left.mult!=1) + *

Invariant: type!=EMPTY => (left.mult==2 => this.op is one of the 17 arrow operators) + *

Invariant: type!=EMPTY => (right.mult==1 => this.op==IN) + *

Invariant: type!=EMPTY => (right.mult==2 => (this.op==IN || this.op is one of the 17 arrow operators)) + */ + +public final class ExprBinary extends Expr { + + /** The binary operator. */ + public final Op op; + + /** The left-hand-side expression. */ + public final Expr left; + + /** The right-hand-side expression. */ + public final Expr right; + + /** Caches the span() result. */ + private Pos span = null; + + //============================================================================================================// + + /** Constructs a new ExprBinary node. */ + private ExprBinary(Pos pos, Pos closingBracket, Op op, Expr left, Expr right, Type type, JoinableList errors) { + super(pos, + closingBracket, + left.ambiguous || right.ambiguous, + type, + (op.isArrow && (left.mult==2 || right.mult==2 || op!=Op.ARROW))?2:0, + left.weight + right.weight, + errors); + this.op = op; + this.left = left; + this.right = right; + } + + //============================================================================================================// + + /** Returns true if we can determine the two expressions are equivalent; may sometimes return false. */ + @Override public boolean isSame(Expr obj) { + while(obj instanceof ExprUnary && ((ExprUnary)obj).op==ExprUnary.Op.NOOP) obj=((ExprUnary)obj).sub; + if (obj==this) return true; + if (!(obj instanceof ExprBinary)) return false; + ExprBinary x = (ExprBinary)obj; + return op==x.op && left.isSame(x.left) && right.isSame(x.right); + } + + //============================================================================================================// + + /** Convenience method that generates a type error with "msg" as the message, + * and includes the left and right bounding types in the message. + */ + private static ErrorType error(Pos pos, String msg, Expr left, Expr right) { + return new ErrorType(pos, msg+"\nLeft type = "+left.type+"\nRight type = "+right.type); + } + + //============================================================================================================// + + /** Convenience method that generates a type warning with "msg" as the message, + * and includes the left and right bounding types in the message. + */ + private ErrorWarning warn(String msg) { + return new ErrorWarning(pos, msg + +"\nLeft type = " + Type.removesBoolAndInt(left.type) + +"\nRight type = " + Type.removesBoolAndInt(right.type)); + } + + //============================================================================================================// + + /** Convenience method that generates a type warning with "msg" as the message, + * and includes the parent's relevance type, as well as the left and right bounding types in the message. + */ + private ErrorWarning warn(String msg, Type parent) { + return new ErrorWarning(pos, msg + + "\nParent's relevant type = " + Type.removesBoolAndInt(parent) + + "\nLeft type = " + Type.removesBoolAndInt(left.type) + + "\nRight type = " + Type.removesBoolAndInt(right.type)); + } + + //============================================================================================================// + + /** {@inheritDoc} */ + @Override public Pos span() { + Pos p = span; + if (p==null) span = (p = pos.merge(closingBracket).merge(right.span()).merge(left.span())); + return p; + } + + //============================================================================================================// + + /** {@inheritDoc} */ + @Override public void toString(StringBuilder out, int indent) { + if (indent<0) { + if (op==Op.ISSEQ_ARROW_LONE) out.append("seq "); else { left.toString(out,-1); out.append(' ').append(op).append(' '); } + right.toString(out,-1); + } else { + for(int i=0; i",true), + /** ->some */ ANY_ARROW_SOME("->some",true), + /** ->one */ ANY_ARROW_ONE("->one",true), + /** ->lone */ ANY_ARROW_LONE("->lone",true), + /** some-> */ SOME_ARROW_ANY("some->",true), + /** some->some */ SOME_ARROW_SOME("some->some",true), + /** some->one */ SOME_ARROW_ONE("some->one",true), + /** some->lone */ SOME_ARROW_LONE("some->lone",true), + /** one-> */ ONE_ARROW_ANY("one->",true), + /** one->some */ ONE_ARROW_SOME("one->some",true), + /** one->one */ ONE_ARROW_ONE("one->one",true), + /** one->lone */ ONE_ARROW_LONE("one->lone",true), + /** lone-> */ LONE_ARROW_ANY("lone->",true), + /** lone->some */ LONE_ARROW_SOME("lone->some",true), + /** lone->one */ LONE_ARROW_ONE("lone->one",true), + /** lone->lone */ LONE_ARROW_LONE("lone->lone",true), + /** isSeq->lone */ ISSEQ_ARROW_LONE("isSeq->lone",true), + /** . */ JOIN(".",false), + /** <: */ DOMAIN("<:",false), + /** :> */ RANGE(":>",false), + /** & */ INTERSECT("&",false), + /** ++ */ PLUSPLUS("++",false), + /** set union */ PLUS("+",false), + /** int + */ IPLUS("@+",false), + /** set diff */ MINUS("-",false), + /** int - */ IMINUS("@-",false), + /** multiply */ MUL("*",false), + /** divide */ DIV("/",false), + /** remainder */ REM("%",false), + /** = */ EQUALS("=",false), + /** != */ NOT_EQUALS("!=",false), + /** => */ IMPLIES("=>",false), + /** < */ LT("<",false), + /** =< */ LTE("<=",false), + /** > */ GT(">",false), + /** >= */ GTE(">=",false), + /** !< */ NOT_LT("!<",false), + /** !=< */ NOT_LTE("!<=",false), + /** !> */ NOT_GT("!>",false), + /** !>= */ NOT_GTE("!>=",false), + /** << */ SHL("<<",false), + /** >> */ SHA(">>",false), + /** >>> */ SHR(">>>",false), + /** in */ IN("in",false), + /** !in */ NOT_IN("!in",false), + /** && */ AND("&&",false), + /** || */ OR("||",false), + /** <=> */ IFF("<=>",false); + + /** The constructor. + * @param label - the label (for printing debugging messages) + * @param isArrow - true if this operator is one of the 17 arrow operators + */ + private Op(String label, boolean isArrow) { + this.label=label; + this.isArrow=isArrow; + } + + /** The human readable label for this operator. */ + private final String label; + + /** True if and only if this operator is the Cartesian product "->", a "seq" multiplicity, + * or is a multiplicity arrow of the form "?->?". + */ + public final boolean isArrow; + + /** Constructs a new ExprBinary node. + * @param pos - the original position in the source file (can be null if unknown) + * @param left - the left hand side expression + * @param right - the right hand side expression + */ + public final Expr make(Pos pos, Pos closingBracket, Expr left, Expr right) { + switch(this) { + case AND: return ExprList.makeAND(pos, closingBracket, left, right); + case OR: return ExprList.makeOR(pos, closingBracket, left, right); + case DOMAIN: { + // Special optimization + Expr f = right.deNOP(); + if (f instanceof Field && ((Field)f).sig==left.deNOP()) return right; + break; + } + case MUL: case DIV: case REM: case LT: case LTE: case GT: case GTE: case SHL: case SHR: case SHA: + case NOT_LT: case NOT_GT: case NOT_LTE: case NOT_GTE: { + left = left.typecheck_as_int(); + right = right.typecheck_as_int(); + break; + } + case IFF: case IMPLIES: { + left = left.typecheck_as_formula(); + right = right.typecheck_as_formula(); + break; + } + case IPLUS: case IMINUS: { + left = left.typecheck_as_int(); + right = right.typecheck_as_int(); + break; + } + case PLUS: case MINUS: case EQUALS: case NOT_EQUALS: { + //[AM]: these are always relational operators now, so no casts +// Type a=left.type, b=right.type; +// if (a.hasCommonArity(b) || (a.is_int && b.is_int)) break; +// if (Type.SIGINT2INT) { +// if (a.is_int && b.intersects(SIGINT.type)) { right=right.cast2int(); break; } +// if (b.is_int && a.intersects(SIGINT.type)) { left=left.cast2int(); break; } +// } +// if (Type.INT2SIGINT) { +// if (a.is_int && b.hasArity(1)) { left=left.cast2sigint(); break; } +// if (b.is_int && a.hasArity(1)) { right=right.cast2sigint(); break; } +// } + break; + } + default: { + left = left.typecheck_as_set(); + right = right.typecheck_as_set(); + } + } + Err e=null; + Type type=EMPTY; + JoinableList errs = left.errors.make(right.errors); + if (errs.isEmpty()) switch(this) { + case LT: case LTE: case GT: case GTE: case NOT_LT: case NOT_LTE: case NOT_GT: case NOT_GTE: + case AND: case OR: case IFF: case IMPLIES: + type = Type.FORMULA; + break; + case MUL: case DIV: case REM: case SHL: case SHR: case SHA: + type = Type.smallIntType(); + break; + case PLUSPLUS: + type = left.type.unionWithCommonArity(right.type); + if (type==EMPTY) e=error(pos, "++ can be used only between two expressions of the same arity.", left, right); + break; + case PLUS: case MINUS: case EQUALS: case NOT_EQUALS: + if (this==EQUALS || this==NOT_EQUALS) { + if (left.type.hasCommonArity(right.type) || (left.type.is_int() && right.type.is_int())) { + type=Type.FORMULA; + break; + } + } else { + type = (this==PLUS ? left.type.unionWithCommonArity(right.type) : left.type.pickCommonArity(right.type)); + if (type!=EMPTY) break; + } + e=error(pos, this+" can be used only between 2 expressions of the same arity, or between 2 integer expressions.", left, right); + break; + case IPLUS: case IMINUS: + type = Type.smallIntType(); + break; + case IN: case NOT_IN: + type=(left.type.hasCommonArity(right.type)) ? Type.FORMULA : EMPTY; + if (type==EMPTY) e=error(pos,this+" can be used only between 2 expressions of the same arity.", left, right); + break; + case JOIN: + type=left.type.join(right.type); + if (type==EMPTY) return ExprBadJoin.make(pos, closingBracket, left, right); + break; + case DOMAIN: + type=right.type.domainRestrict(left.type); + if (type==EMPTY) e=new ErrorType(left.span(), + "This must be a unary set, but instead it has the following possible type(s):\n"+left.type); + break; + case RANGE: + type=left.type.rangeRestrict(right.type); + if (type==EMPTY) e=new ErrorType(right.span(), + "This must be a unary set, but instead it has the following possible type(s):\n"+right.type); + break; + case INTERSECT: + type=left.type.intersect(right.type); + if (type==EMPTY) e=error(pos,"& can be used only between 2 expressions of the same arity.", left, right); + break; + default: + type=left.type.product(right.type); + } + if ((isArrow && left.mult==1) || (!isArrow && left.mult!=0)) + errs = errs.make(new ErrorSyntax(left.span(), "Multiplicity expression not allowed here.")); + if ((isArrow && right.mult==1) || (!isArrow && this!=Op.IN && right.mult!=0)) + errs = errs.make(new ErrorSyntax(right.span(), "Multiplicity expression not allowed here.")); + return new ExprBinary(pos, closingBracket, this, left, right, type, errs.make(e)); + } + + /** Returns the human readable label for this operator. */ + @Override public final String toString() { return label; } + + /** Returns the human readable label already encoded for HTML */ + public final String toHTML() { return "" + Util.encode(label) + ""; } + } + + //============================================================================================================// + + /** {@inheritDoc} */ + @Override public Expr resolve(Type p, Collection warns) { + if (errors.size()>0) return this; + ErrorWarning w=null; + Type a=left.type, b=right.type; + switch(op) { + case MUL: case DIV: case REM: case LT: case LTE: case GT: case GTE: case SHL: case SHR: case SHA: + case NOT_LTE: case NOT_GTE: case NOT_LT: case NOT_GT: { + a=(b=Type.smallIntType()); + break; + } + case AND: case OR: case IFF: case IMPLIES: { + a=(b=Type.FORMULA); + break; + } + case EQUALS: case NOT_EQUALS: { + p=a.intersect(b); + if (p.hasTuple()) {a=p; b=p;} else {a=a.pickCommonArity(b); b=b.pickCommonArity(a);} + //[AM] +// if (left.type.is_int() && right.type.is_int()) { +// a=Type.makeInt(a); b=Type.makeInt(b); +// } else + if (warns==null) { + break; + } else if (left.type.hasTuple() && right.type.hasTuple() && !(left.type.intersects(right.type))) { + w=warn("== is redundant, because the left and right expressions are always disjoint."); + } else if (left.isSame(right)) { + w=warn("== is redundant, because the left and right expressions always have the same value."); + } + break; + } + case IN: case NOT_IN: { + a=a.pickCommonArity(b); + b=b.intersect(a); + if (warns==null) break; + if (left.type.hasNoTuple() && right.type.hasNoTuple()) + w=warn("Subset operator is redundant, because both subexpressions are always empty."); + else if (left.type.hasNoTuple()) + w=warn("Subset operator is redundant, because the left subexpression is always empty."); + else if (right.type.hasNoTuple()) + w=warn("Subset operator is redundant, because the right subexpression is always empty."); + else if (b.hasNoTuple()) + w=warn("Subset operator is redundant, because the left and right subexpressions are always disjoint."); + else if (left.isSame(right)) + w=warn("Subset operator is redundant, because the left and right expressions always have the same value."); + break; + } + case INTERSECT: { + a=a.intersect(p); + b=b.intersect(p); + if (warns!=null && type.hasNoTuple()) w=warn("& is irrelevant because the two subexpressions are always disjoint."); + break; + } + case IPLUS: case IMINUS: { + a = Type.smallIntType(); + b = Type.smallIntType(); + break; + } + case PLUSPLUS: case PLUS: { + a=a.intersect(p); + b=b.intersect(p); + //[AM] +// if (op==Op.PLUS && p.is_int()) { a=Type.makeInt(a); b=Type.makeInt(b); } + if (warns==null) break; + if (a==EMPTY && b==EMPTY) + w=warn(this+" is irrelevant since both subexpressions are redundant.", p); + else if (a==EMPTY) + w=warn(this+" is irrelevant since the left subexpression is redundant.", p); + else if (b==EMPTY || (op==Op.PLUSPLUS && !right.type.canOverride(left.type))) + w=warn(this+" is irrelevant since the right subexpression is redundant.", p); + break; + } + case MINUS: { + a=p; + b=p.intersect(b); + //[AM] +// if (p.is_int()) { +// a=Type.makeInt(a); b=Type.makeInt(b); +// } else + if (warns!=null && (type.hasNoTuple() || b.hasNoTuple())) { + w=warn("- is irrelevant since the right expression is redundant.", p); + } + break; + } + case JOIN: { + if (warns!=null && type.hasNoTuple()) w=warn("The join operation here always yields an empty set."); + a=(b=EMPTY); + for (ProductType aa: left.type) for (ProductType bb: right.type) if (p.hasArity(aa.arity()+bb.arity()-2)) { + PrimSig j = aa.get(aa.arity()-1).intersect(bb.get(0)); + if (j != Sig.NONE) for (ProductType cc:p.intersect(aa.join(bb))) if (!cc.isEmpty()) { + List v = new ArrayList(cc.arity() + 1); + for(int i=0; ir2 in parentType} + // rightType' = {r2 | r2 in rightType and there exists r1 in leftType such that r1:>r2 in parentType} + if (warns!=null && type.hasNoTuple()) w=warn(":> is irrelevant because the result is always empty."); + Type leftType=EMPTY, rightType=EMPTY; + for(ProductType bb:b) if (bb.arity()==1) for(ProductType aa:a) if (p.hasArity(aa.arity())) + for (ProductType cc:p.intersect(aa.columnRestrict(bb.get(0), aa.arity()-1))) if (!cc.isEmpty()) { + leftType = leftType.merge(cc); + rightType = rightType.merge(cc, cc.arity()-1, cc.arity()); + } + if (leftType==EMPTY || rightType==EMPTY) { // We try to proceed the best we can + leftType = a.pickCommonArity(p); + rightType = b.extract(1); + } + a=leftType; b=rightType; break; + } + default: { + // leftType' == {r1 | r1 in leftType and there exists r2 in rightType such that r1->r2 in parentType} + // rightType' == {r2 | r2 in rightType and there exists r1 in leftType such that r1->r2 in parentType} + if (warns==null) { + // do nothing + } else if (a.hasTuple()) { + if (b.hasNoTuple()) w=warn("The left expression of -> is irrelevant because the right expression is always empty."); + } else { + if (b.hasTuple()) w=warn("The right expression of -> is irrelevant because the left expression is always empty."); + } + Type leftType=EMPTY, rightType=EMPTY; + for (ProductType aa:a) if (!aa.isEmpty()) + for (ProductType bb:b) if (!bb.isEmpty() && p.hasArity(aa.arity()+bb.arity())) + for (ProductType cc:p.intersect(aa.product(bb))) if (!cc.isEmpty()) { + leftType = leftType.merge(cc, 0, aa.arity()); + rightType = rightType.merge(cc, aa.arity(), cc.arity()); + } + // We try to proceed the best we can; we should have issued a relevance warning already. + if (leftType==EMPTY || rightType==EMPTY) { leftType=a; rightType=b; } + a=leftType; + b=rightType; + } + } + Expr left = this.left.resolve(a, warns); + Expr right = this.right.resolve(b, warns); + if (w!=null) warns.add(w); + return (left==this.left && right==this.right) ? this : op.make(pos, closingBracket, left, right); + } + + //============================================================================================================// + + /** {@inheritDoc} */ + public int getDepth() { + int a=left.getDepth(), b=right.getDepth(); + if (a>=b) return 1+a; else return 1+b; + } + + /** {@inheritDoc} */ + @Override public final T accept(VisitReturn visitor) throws Err { return visitor.visit(this); } + + /** {@inheritDoc} */ + @Override public String getHTML() { return op.toHTML() + " " + type + ""; } + + /** {@inheritDoc} */ + @Override public List getSubnodes() { return Util.asList(left, right); } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/ast/ExprCall.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/ast/ExprCall.java new file mode 100644 index 00000000..e0cab7bc --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/ast/ExprCall.java @@ -0,0 +1,276 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4compiler.ast; + +import java.util.Collection; +import java.util.List; +import edu.mit.csail.sdg.alloy4.JoinableList; +import edu.mit.csail.sdg.alloy4.Pos; +import edu.mit.csail.sdg.alloy4.Env; +import edu.mit.csail.sdg.alloy4.Err; +import edu.mit.csail.sdg.alloy4.ErrorSyntax; +import edu.mit.csail.sdg.alloy4.ErrorWarning; +import edu.mit.csail.sdg.alloy4.ErrorType; +import edu.mit.csail.sdg.alloy4.ConstList; +import edu.mit.csail.sdg.alloy4.Util; +import edu.mit.csail.sdg.alloy4.ConstList.TempList; +import edu.mit.csail.sdg.alloy4compiler.ast.Sig.Field; +import static edu.mit.csail.sdg.alloy4compiler.ast.Type.EMPTY; + +/** Immutable; represents a call. + * + *

Invariant: type!=EMPTY => (all x:args | x.mult==0) + */ + +public final class ExprCall extends Expr { + + /** The actual function being called; never null. */ + public final Func fun; + + /** The list of arguments to the call. */ + public final ConstList args; + + /** The extra weight added to this node on top of the combined weights of the arguments. */ + public final long extraWeight; + + /** Caches the span() result. */ + private Pos span=null; + + //============================================================================================================// + + /** {@inheritDoc} */ + @Override public Pos span() { + Pos p=span; + if (p==null) { + p=pos.merge(closingBracket); + for(Expr a:args) p=p.merge(a.span()); + span=p; + } + return p; + } + + //============================================================================================================// + + /** {@inheritDoc} */ + @Override public void toString(StringBuilder out, int indent) { + if (indent<0) { + out.append(fun.label); + if (args.size()==0) return; + out.append('['); + for(int i=0; i0) out.append(", "); args.get(i).toString(out,-1); } + out.append(']'); + } else { + for(int i=0; i with type=").append(type).append('\n'); + for(Expr a:args) { a.toString(out, indent+2); } + } + } + + //============================================================================================================// + + /** This visitor assumes the input expression is already fully typechecked, and derive a tight bound on the return type. */ + private static final class DeduceType extends VisitReturn { + private final Env env = new Env(); + private DeduceType() { } + @Override public Type visit(ExprITE x) throws Err { + Type t = x.left.accept(this); + if (t.size()==0) return t; + Type t2 = x.right.accept(this); + return t.unionWithCommonArity(t2); + } + @Override public Type visit(ExprBinary x) throws Err { + switch(x.op) { + case IMPLIES: case GT: case GTE: case LT: case LTE: case IFF: case EQUALS: case IN: case OR: case AND: + case NOT_LT: case NOT_GT: case NOT_LTE: case NOT_GTE: case NOT_IN: case NOT_EQUALS: + return Type.FORMULA; + case MUL: case DIV: case REM: case SHL: case SHR: case SHA: + return Type.smallIntType(); + } + Type a = x.left.accept(this); + Type b = x.right.accept(this); + switch(x.op) { + case JOIN: return a.join(b); + case DOMAIN: return b.domainRestrict(a); + case RANGE: return a.rangeRestrict(b); + case INTERSECT: return a.intersect(b); + case PLUSPLUS: return a.unionWithCommonArity(b); + case PLUS: return a.unionWithCommonArity(b); //[AM]: return (a.is_int() && b.is_int()) ? Type.makeInt(a.unionWithCommonArity(b)) : a.unionWithCommonArity(b); + case IPLUS: case IMINUS: return Type.smallIntType(); + case MINUS: return a.pickCommonArity(b); //[AM]: return (a.is_int() && b.is_int()) ? Type.makeInt(a.pickCommonArity(b)) : a.pickCommonArity(b); + default: return a.product(b); + } + } + @Override public Type visit(ExprUnary x) throws Err { + Type t = x.sub.accept(this); + switch(x.op) { + case NOOP: case LONEOF: case ONEOF: case SETOF: case SOMEOF: case EXACTLYOF: return t; + case CARDINALITY: case CAST2INT: return Type.smallIntType(); + case CAST2SIGINT: return Sig.SIGINT.type; + case TRANSPOSE: return t.transpose(); + case CLOSURE: return t.closure(); + case RCLOSURE: return Type.make2(Sig.UNIV); + default: return Type.FORMULA; + } + } + @Override public Type visit(ExprQt x) throws Err { + if (x.op == ExprQt.Op.SUM) return Type.smallIntType(); + if (x.op != ExprQt.Op.COMPREHENSION) return Type.FORMULA; + Type ans = null; + for(Decl d: x.decls) { + Type t = d.expr.accept(this); + for(ExprHasName v: d.names) { + env.put((ExprVar)v, t); + if (ans==null) ans=t; else ans=ans.product(t); + } + } + for(Decl d: x.decls) for(ExprHasName v: d.names) env.remove((ExprVar)v); + return (ans==null) ? EMPTY : ans; + } + @Override public Type visit(ExprLet x) throws Err { + env.put(x.var, x.expr.accept(this)); + Type ans = x.sub.accept(this); + env.remove(x.var); + return ans; + } + @Override public Type visit(ExprCall x) throws Err { + throw new ErrorSyntax(x.span(), "Return type declaration cannot contain predicate/function calls."); + } + @Override public Type visit(ExprVar x) { Type t=env.get(x); return (t!=null && t!=EMPTY) ? t : x.type; } + @Override public Type visit(ExprConstant x) { return x.type; } + @Override public Type visit(Sig x) { return x.type; } + @Override public Type visit(Field x) { return x.type; } + @Override public Type visit(ExprList x) { return Type.FORMULA; } + } + + //============================================================================================================// + + /** Constructs an ExprCall node with the given function "pred/fun" and the list of arguments "args". */ + private ExprCall (Pos pos, Pos closingBracket, boolean ambiguous, Type type, + Func fun, ConstList args, long extraWeight, long weight, JoinableList errs) { + super(pos, closingBracket, ambiguous, type, 0, weight, errs); + this.fun = fun; + this.args = args; + this.extraWeight = extraWeight; + } + + //============================================================================================================// + + /** Returns true if we can determine the two expressions are equivalent; may sometimes return false. */ + @Override public boolean isSame(Expr obj) { + while(obj instanceof ExprUnary && ((ExprUnary)obj).op==ExprUnary.Op.NOOP) obj=((ExprUnary)obj).sub; + if (obj==this) return true; + if (!(obj instanceof ExprCall)) return false; + ExprCall x=(ExprCall)obj; + if (fun!=x.fun || args.size()!=x.args.size()) return false; + for(int i=0; i args, long extraPenalty) { + if (extraPenalty<0) extraPenalty = 0; + if (args==null) args = ConstList.make(); + long weight = extraPenalty; + boolean ambiguous = false; + JoinableList errs = emptyListOfErrors; + TempList newargs = new TempList(args.size()); + if (args.size() != fun.count()) { + errs = errs.make( + new ErrorSyntax(pos, ""+fun+" has "+fun.count()+" parameters but is called with "+args.size()+" arguments.")); + } + for(int i=0; i0 && x.errors.isEmpty() && !x.type.hasArity(a)) + errs = errs.make(new ErrorType(x.span(), "This should have arity "+a+" but instead its possible type(s) are "+x.type)); + newargs.add(x); + } + Type t=Type.FORMULA; + if (!fun.isPred && errs.size()==0) { + final Type tt = fun.returnDecl.type; + try { + // This provides a limited form of polymorphic function, + // by using actual arguments at each call site to derive a tighter bound on the return value. + DeduceType d = new DeduceType(); + for(int i=0; i warns) { + if (errors.size()>0) return this; + TempList args = new TempList(this.args.size()); + boolean changed = false; + for(int i=0; i T accept(VisitReturn visitor) throws Err { return visitor.visit(this); } + + /** {@inheritDoc} */ + @Override public String getHTML() { return "call " + fun.label + " " + type + ""; } + + /** {@inheritDoc} */ + @Override public List getSubnodes() { + if (args.size()==0) { + Expr b = fun.getBody(); + return Util.asList(make(b.pos(), b.span(), b.getHTML(), b.getSubnodes())); + } + Pos p = pos; + if (p == Pos.UNKNOWN) p = span(); + Browsable f = make(p, p, (fun.isPred ? "pred " : "fun ")+fun.label , fun.getSubnodes()); + Browsable a = make(span(), span(), "" + args.size() + " argument" + (args.size()==1 ? "" : "s"), args); + return Util.asList(f, a); + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/ast/ExprChoice.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/ast/ExprChoice.java new file mode 100644 index 00000000..a400364c --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/ast/ExprChoice.java @@ -0,0 +1,207 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4compiler.ast; + +import static edu.mit.csail.sdg.alloy4compiler.ast.Type.EMPTY; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import edu.mit.csail.sdg.alloy4.ConstList; +import edu.mit.csail.sdg.alloy4.Err; +import edu.mit.csail.sdg.alloy4.ErrorType; +import edu.mit.csail.sdg.alloy4.ErrorWarning; +import edu.mit.csail.sdg.alloy4.Pos; + +/** Immutable; represents an unresolved node that has several possibilities. */ + +public final class ExprChoice extends Expr { + + /** The unmodifiable list of Expr(s) from that this ExprChoice can refer to. */ + public final ConstList choices; + + /** The unmodifiable list of String(s) explaining where each choice came from. */ + public final ConstList reasons; + + /** Caches the span() result. */ + private Pos span=null; + + //============================================================================================================// + + /** {@inheritDoc} */ + @Override public Pos span() { + Pos p=span; + if (p==null) { + p=pos; + for(Expr a:choices) p=p.merge(a.span()); + span=p; + } + return p; + } + + //============================================================================================================// + + /** {@inheritDoc} */ + @Override public void toString(StringBuilder out, int indent) { + if (indent<0) { + out.append("<"); + for(Expr e:choices) { e.toString(out,-1); out.append(";"); } + out.append(">"); + } else { + for(int i=0; i choices, ConstList reasons, Type type, long weight) { + super(pos, null, true, type, 0, weight, emptyListOfErrors.make(type==EMPTY ? complain(pos,choices) : null)); + this.choices = choices; + this.reasons = reasons; + } + + //============================================================================================================// + + /** Construct an ExprChoice node. */ + public static Expr make(Pos pos, ConstList choices, ConstList reasons) { + if (choices.size()==0) return new ExprBad(pos, "", new ErrorType(pos, "This expression failed to be typechecked.")); + if (choices.size()==1 && choices.get(0).errors.isEmpty()) return choices.get(0); // Shortcut + Type type=EMPTY; + boolean first=true; + long weight=0; + for(Expr x:choices) { + type=x.type.merge(type); + if (first || weight>x.weight) if (x.type!=EMPTY) { weight=x.weight; first=false; } + } + return new ExprChoice(pos, choices, reasons, type, weight); + } + + //============================================================================================================// + + /** Resolve the list of choices, or return an ExprBad object containing the list of unresolvable ambiguities. */ + private Expr resolveHelper(boolean firstPass, final Type t, List choices, List reasons, Collection warns) { + List ch = new ArrayList(choices.size()); + List re = new ArrayList(choices.size()); + // We first prefer exact matches + for(int i=0; iint +// if (ch.size()==0 && Type.SIGINT2INT && t.is_int) { +// for(int i=0; isigint +// if (ch.size()==0 && Type.INT2SIGINT && t.hasArity(1)) { +// for(int i=0; i1) { + List ch2 = new ArrayList(ch.size()); + List re2 = new ArrayList(ch.size()); + long w = 0; + for(int i=0; i0 && c.weight>w) continue; else if (ch2.size()==0 || c.weight1) { + ch2 = new ArrayList(ch.size()); + for(Expr c:ch) ch2.add(c.resolve(t, null)); + return resolveHelper(false, t, ch2, re, warns); + } + } + // If we are down to exactly 1 match, return it + if (ch.size()==1) return ch.get(0).resolve(t, warns); + // If we are faced with 2 or more choices, but they all result in emptyset-of-the-same-arity, then just return emptyset + none: + while(ch.size()>1) { + int arity = -1; + for(Expr c: ch) { + if (c.type.is_bool || c.type.is_int() || c.type.hasTuple()) break none; + int a = c.type.arity(); + if (a<1) break none; + if (arity<0) arity=a; else if (arity!=a) break none; + } + Expr ans = Sig.NONE; + while(arity>1) {ans=ans.product(Sig.NONE); arity--;} + return ExprUnary.Op.NOOP.make(span(), ans); + } + // Otherwise, complain! + String txt; + if (ch.size()>1) { + txt="\nThis name is ambiguous due to multiple matches:"; + } else { + txt="\nThis name cannot be resolved; its relevant type does not intersect with any of the following candidates:"; + re = reasons; + } + StringBuilder msg = new StringBuilder(txt); + for(String r:re) msg.append('\n').append(r); + return new ExprBad(pos, toString(), new ErrorType(pos, msg.toString())); + } + + /** {@inheritDoc} */ + @Override public Expr resolve(Type t, Collection warns) { + if (errors.size()>0) return this; else return resolveHelper(true, t, choices, reasons, warns); + } + + //============================================================================================================// + + /** {@inheritDoc} */ + public int getDepth() { + int max = 1; + for(Expr x: choices) { int tmp=x.getDepth(); if (max T accept(VisitReturn visitor) throws Err { + if (!errors.isEmpty()) throw errors.pick(); + throw new ErrorType(span(), "This expression failed to be resolved."); + } + + /** {@inheritDoc} */ + @Override public String getHTML() { return "error (parser or typechecker failed)"; } + + /** {@inheritDoc} */ + @Override public List getSubnodes() { return new ArrayList(0); } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/ast/ExprConstant.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/ast/ExprConstant.java new file mode 100644 index 00000000..8b38520c --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/ast/ExprConstant.java @@ -0,0 +1,176 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4compiler.ast; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import edu.mit.csail.sdg.alloy4.Pos; +import edu.mit.csail.sdg.alloy4.Err; +import edu.mit.csail.sdg.alloy4.ErrorWarning; +import static edu.mit.csail.sdg.alloy4compiler.ast.Sig.UNIV; + +/** Immutable; represents a constant in the AST. */ + +public final class ExprConstant extends Expr { + + /** The type of constant. */ + public final Op op; + + /** If this node is a String constant, then this field stores the String, else this field stores "". */ + public final String string; + + /** If this node is a number constant, then this field stores the number, else this field stores 0. */ + public final int num; + + /** Return the number if this node is a number constant, otherwise return 0. */ + public int num() { return num; } + + /** {@inheritDoc} */ + @Override public Pos span() { return pos; } + + /** {@inheritDoc} */ + @Override public void toString(StringBuilder out, int indent) { + if (indent<0) { + if (op==Op.NUMBER) out.append(num); else if (op==Op.STRING) out.append(string); else out.append(op); + } else { + for(int i=0; i warns) { return this; } + + /** {@inheritDoc} */ + @Override public final T accept(VisitReturn visitor) throws Err { return visitor.visit(this); } + + /** {@inheritDoc} */ + public int getDepth() { return 1; } + + /** {@inheritDoc} */ + @Override public String getHTML() { + switch(op) { + case TRUE: return "true"; + case FALSE: return "false"; + case IDEN: return "iden"; + case MAX: return "fun/max"; + case MIN: return "fun/min"; + case NEXT: return "fun/next"; + case EMPTYNESS: return "none"; + case STRING: return "\"" + string + "\""; + } + return "" + num + ""; + } + + /** {@inheritDoc} */ + @Override public List getSubnodes() { return new ArrayList(0); } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/ast/ExprCustom.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/ast/ExprCustom.java new file mode 100644 index 00000000..fea6d801 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/ast/ExprCustom.java @@ -0,0 +1,49 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4compiler.ast; + +import java.util.Collection; +import edu.mit.csail.sdg.alloy4.Pos; +import edu.mit.csail.sdg.alloy4.Err; +import edu.mit.csail.sdg.alloy4.ErrorWarning; +import edu.mit.csail.sdg.alloy4.JoinableList; +import static edu.mit.csail.sdg.alloy4compiler.ast.Type.EMPTY; + +/** Immutable; represents a custom node. + * + *

Invariant: this.type==EMPTY && this.errors.size()==1 + */ + +public abstract class ExprCustom extends Expr { + + /** {@inheritDoc} */ + @Override public Pos span() { return pos; } + + /** Constructs an ExprCustom object. + * @param pos - the Pos for this expression (can be Pos.UNKNOWN if unknown) + * @param error - the error to display if this node does not get desugared + */ + public ExprCustom(Pos pos, Err error) { + super(pos, null, false, EMPTY, 0, 0, new JoinableList(error)); + if (error==null) throw new NullPointerException(); + } + + /** {@inheritDoc} */ + @Override public Expr resolve(Type t, Collection warns) { return this; } + + /** {@inheritDoc} */ + @Override public final T accept(VisitReturn visitor) throws Err { throw errors.pick(); } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/ast/ExprHasName.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/ast/ExprHasName.java new file mode 100644 index 00000000..714c381a --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/ast/ExprHasName.java @@ -0,0 +1,44 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4compiler.ast; + +import edu.mit.csail.sdg.alloy4.Pos; + +/** Immutable; represents a named entity (such as a Field, or a LET or QUANTIFICATION variable, or a function/predicate parameter). */ + +public abstract class ExprHasName extends Expr { + + /** The label associated with this object; it's used for pretty-printing and does not have to be unique. */ + public final String label; + + /** Constructs an ExprHasName object */ + ExprHasName(Pos pos, String label, Type type) { + super(pos, null, false, type, 0, 0, null); + this.label = (label==null ? "" : label); + } + + /** {@inheritDoc} */ + @Override public final boolean isSame(Expr obj) { + while(obj instanceof ExprUnary && ((ExprUnary)obj).op==ExprUnary.Op.NOOP) obj=((ExprUnary)obj).sub; + return this==obj; + } + + /** {@inheritDoc} */ + @Override public final Pos span() { return pos; } + + /** {@inheritDoc} */ + public final int getDepth() { return 1; } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/ast/ExprITE.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/ast/ExprITE.java new file mode 100644 index 00000000..003900a1 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/ast/ExprITE.java @@ -0,0 +1,166 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4compiler.ast; + +import static edu.mit.csail.sdg.alloy4compiler.ast.Type.EMPTY; + +import java.util.Collection; +import java.util.List; + +import edu.mit.csail.sdg.alloy4.Err; +import edu.mit.csail.sdg.alloy4.ErrorSyntax; +import edu.mit.csail.sdg.alloy4.ErrorType; +import edu.mit.csail.sdg.alloy4.ErrorWarning; +import edu.mit.csail.sdg.alloy4.JoinableList; +import edu.mit.csail.sdg.alloy4.Pos; +import edu.mit.csail.sdg.alloy4.Util; + +/** Immutable; represents an if-then-else expression. + * + *

Invariant: type!=EMPTY => (cond.mult==0 && left.mult==0 && right.mult==0) + */ + +public final class ExprITE extends Expr { + + /** The condition formula. */ + public final Expr cond; + + /** The then-clause. */ + public final Expr left; + + /** The else-clause. */ + public final Expr right; + + /** Caches the span() result. */ + private Pos span = null; + + /** {@inheritDoc} */ + @Override public Pos span() { + Pos p = span; + if (p==null) span = (p = cond.span().merge(right.span()).merge(left.span())); + return p; + } + + /** {@inheritDoc} */ + @Override public void toString(StringBuilder out, int indent) { + if (indent<0) { + out.append('('); + cond.toString(out,-1); + out.append(" => "); + left.toString(out,-1); + out.append(" else "); + right.toString(out,-1); + out.append(')'); + } else { + for(int i=0; i warns) { + if (errors.size()>0) return this; + Type a = left.type, b = right.type; + if (p.size()>0) { + a = a.intersect(p); + b = b.intersect(p); + //if (p.is_int()) { a=Type.makeInt(a); b=Type.makeInt(b); } + if (p.is_bool) { a=Type.makeBool(a); b=Type.makeBool(b); } + if (warns!=null && left.type.hasTuple() && !a.hasTuple()) warns.add(new ErrorWarning(left.span(),"This subexpression is redundant.")); + if (warns!=null && right.type.hasTuple() && !b.hasTuple()) warns.add(new ErrorWarning(right.span(),"This subexpression is redundant.")); + } else { + a = p; + b = p; + } + Expr cond = this.cond.resolve(Type.FORMULA, warns); + Expr left = this.left.resolve(a, warns); + Expr right = this.right.resolve(b, warns); + return (cond==this.cond && left==this.left && right==this.right) ? this : make(pos,cond,left,right); + } + + /** {@inheritDoc} */ + public int getDepth() { + int a = cond.getDepth(), b = left.getDepth(), c = right.getDepth(); + if (a>=b) return 1+(a>=c ? a : c); else return 1+(b>=c ? b : c); + } + + /** {@inheritDoc} */ + @Override public final T accept(VisitReturn visitor) throws Err { return visitor.visit(this); } + + /** {@inheritDoc} */ + @Override public String getHTML() { return "if-then-else" + " " + type + ""; } + + /** {@inheritDoc} */ + @Override public List getSubnodes() { return Util.asList(cond, left, right); } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/ast/ExprLet.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/ast/ExprLet.java new file mode 100644 index 00000000..5265312d --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/ast/ExprLet.java @@ -0,0 +1,139 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4compiler.ast; + +import java.util.Collection; +import java.util.List; +import edu.mit.csail.sdg.alloy4.ErrorType; +import edu.mit.csail.sdg.alloy4.Pos; +import edu.mit.csail.sdg.alloy4.Err; +import edu.mit.csail.sdg.alloy4.ErrorWarning; +import edu.mit.csail.sdg.alloy4.ErrorSyntax; +import edu.mit.csail.sdg.alloy4.JoinableList; +import edu.mit.csail.sdg.alloy4.Util; + +/** Immutable; represents an expression of the form (let a=b | x). + * + *

Invariant: type!=EMPTY => (var.type.unambiguos() && sub.mult==0) + */ + +public final class ExprLet extends Expr { + + /** The LET variable. */ + public final ExprVar var; + + /** The expression bound to the LET variable. */ + public final Expr expr; + + /** The body of the LET expression. */ + public final Expr sub; + + /** Caches the span() result. */ + private Pos span = null; + + //=============================================================================================================// + + /** {@inheritDoc} */ + @Override public Pos span() { + Pos p = span; + if (p==null) span = (p = var.span().merge(expr.span()).merge(sub.span())); + return p; + } + + //=============================================================================================================// + + /** {@inheritDoc} */ + @Override public void toString(StringBuilder out, int indent) { + if (indent<0) { + out.append("(let ").append(var.label).append("= ").append(expr.toString()).append(" | "); + sub.toString(out,-1); + out.append(')'); + } else { + for(int i=0; i errs = var.errors.make(expr.errors).make(sub.errors); + if (expr.mult!=0) + errs = errs.make(new ErrorSyntax(expr.span(), "Multiplicity expression not allowed here.")); + if (sub.mult != 0) + errs = errs.make(new ErrorSyntax(sub.span(), "Multiplicity expression not allowed here.")); + if (errs.size()==0 && var.type!=expr.type) + if (/*[AM] var.type.is_int()!=expr.type.is_int()|| */ + var.type.is_bool != expr.type.is_bool || + var.type.arity() != expr.type.arity()) + errs = errs.make(new ErrorType(var.span(), "This variable has type "+var.type+" but is bound to a value of type "+expr.type)); + return new ExprLet(pos, var, expr, sub, errs); + } + + //=============================================================================================================// + + /** {@inheritDoc} */ + @Override public Expr resolve(Type p, Collection warns) { + if (errors.size()>0) return this; + // The var and expr are always already fully resolved, so we only need to resolve sub + Expr newSub = sub.resolve(p, warns); + if (warns!=null && !newSub.hasVar(var)) warns.add(new ErrorWarning(var.pos, "This variable is unused.")); + return (sub==newSub) ? this : make(pos, var, expr, newSub); + } + + //=============================================================================================================// + + /** {@inheritDoc} */ + public int getDepth() { + int a=var.getDepth(), b=sub.getDepth(), c=expr.getDepth(); + if (a T accept(VisitReturn visitor) throws Err { return visitor.visit(this); } + + /** {@inheritDoc} */ + @Override public String getHTML() { return "let " + type + ""; } + + /** {@inheritDoc} */ + @Override public List getSubnodes() { + Browsable a = make(var.pos, var.pos, "var "+var.label+" = ...", expr); + Browsable b = make(sub.span(), sub.span(), "where...", sub); + return Util.asList(a, b); + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/ast/ExprList.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/ast/ExprList.java new file mode 100644 index 00000000..d95c730a --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/ast/ExprList.java @@ -0,0 +1,238 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4compiler.ast; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import edu.mit.csail.sdg.alloy4.Pos; +import edu.mit.csail.sdg.alloy4.Err; +import edu.mit.csail.sdg.alloy4.ErrorWarning; +import edu.mit.csail.sdg.alloy4.ErrorSyntax; +import edu.mit.csail.sdg.alloy4.ErrorType; +import edu.mit.csail.sdg.alloy4.ConstList; +import edu.mit.csail.sdg.alloy4.ConstList.TempList; +import edu.mit.csail.sdg.alloy4.JoinableList; +import static edu.mit.csail.sdg.alloy4compiler.ast.Type.EMPTY; + +/** Immutable; represents disjoint[] or pred/totalOrder[] or (... and ... and ..) and other similar list of arugments. + * + *

Invariant: type!=EMPTY => (all x:args | x.mult==0) + */ + +public final class ExprList extends Expr { + + /** This class contains all possible builtin predicates. */ + public static enum Op { + /** DISJOINT (meaning the argument relations are all disjoint) */ DISJOINT, + /** TOTALORDER (meaning it's a total order over the arguments) */ TOTALORDER, + /** AND (meaning the logical conjunction of all arguments) */ AND, + /** OR (meaning the logical disjunction of all arguments) */ OR + }; + + /** The builtin operator. */ + public final Op op; + + /** The unmodifiable list of arguments. */ + public final ConstList args; + + /** Caches the span() result. */ + private Pos span = null; + + //============================================================================================================// + + /** {@inheritDoc} */ + @Override public Pos span() { + Pos p=span; + if (p==null) { + p=pos.merge(closingBracket); + for(Expr a:args) p=p.merge(a.span()); + span=p; + } + return p; + } + + //============================================================================================================// + + /** {@inheritDoc} */ + @Override public void toString(StringBuilder out, int indent) { + if (indent<0) { + out.append(op).append("["); + for(int i=0; i0) out.append(", "); args.get(i).toString(out,-1); } + out.append(']'); + } else { + for(int i=0; i list = new TempList(2); + list.add(a); + list.add(b); + return make(pos, closingBracket, Op.AND, list.makeConst()); + } + + /** Generates the expression (arg1 || arg2) */ + public static ExprList makeOR(Pos pos, Pos closingBracket, Expr a, Expr b) { + TempList list = new TempList(2); + list.add(a); + list.add(b); + return make(pos, closingBracket, Op.OR, list.makeConst()); + } + + /** Generates the expression pred/totalOrder[arg1, args2, arg3...] */ + public static ExprList makeTOTALORDER(Pos pos, Pos closingBracket, List args) { return make(pos, closingBracket, Op.TOTALORDER, args); } + + /** Generates the expression disj[arg1, args2, arg3...] */ + public static ExprList makeDISJOINT(Pos pos, Pos closingBracket, List args) { return make(pos, closingBracket, Op.DISJOINT, args); } + + /** Return a new ExprList object that is the same as this one except with one additional argument. */ + public ExprList addArg(Expr x) { + List args = new ArrayList(this.args); + args.add(x); + return make(pos, closingBracket, op, args); + } + + //============================================================================================================// + + /** {@inheritDoc} */ + @Override public Expr resolve(Type p, Collection warns) { + TempList newargs = new TempList(args.size()); + boolean changed = false; + if (errors.size()>0) return this; + if (op==Op.AND || op==Op.OR) { + for(int i=0; i T accept(VisitReturn visitor) throws Err { return visitor.visit(this); } + + /** {@inheritDoc} */ + @Override public String getHTML() { return "" + op + " [ ]"; } + + /** {@inheritDoc} */ + @Override public List getSubnodes() { return args; } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/ast/ExprQt.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/ast/ExprQt.java new file mode 100644 index 00000000..b773c574 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/ast/ExprQt.java @@ -0,0 +1,285 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4compiler.ast; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.NoSuchElementException; +import edu.mit.csail.sdg.alloy4.Pos; +import edu.mit.csail.sdg.alloy4.Err; +import edu.mit.csail.sdg.alloy4.ErrorSyntax; +import edu.mit.csail.sdg.alloy4.ErrorWarning; +import edu.mit.csail.sdg.alloy4.ErrorType; +import edu.mit.csail.sdg.alloy4.ConstList; +import edu.mit.csail.sdg.alloy4.JoinableList; +import edu.mit.csail.sdg.alloy4.ConstList.TempList; +import static edu.mit.csail.sdg.alloy4compiler.ast.Type.EMPTY; + +/** Immutable; represents a quantified expression. + * + * It can have one of the following forms: + * + *
+ *
( all a,b:t | formula ) + *
( no a,b:t | formula ) + *
( lone a,b:t | formula ) + *
( one a,b:t | formula ) + *
( some a,b:t | formula ) + *
( sum a,b:t | integer expression ) + *
{ a,b:t | formula } + *
{ a,b:t } + *
+ * + *
Invariant: type!=EMPTY => sub.mult==0 + *
Invariant: type!=EMPTY => vars.size()>0 + */ + +public final class ExprQt extends Expr { + + /** The operator (ALL, NO, LONE, ONE, SOME, SUM, or COMPREHENSION) */ + public final Op op; + + /** The unmodifiable list of variables. */ + public final ConstList decls; + + /** The body of the quantified expression. */ + public final Expr sub; + + /** Caches the span() result. */ + private Pos span; + + /** Return the number of variables. */ + public int count() { + int n = 0; + for(Decl d: decls) n = n + d.names.size(); + return n; + } + + /** Return the i-th variable. */ + public ExprVar get(int i) { + if (i<0) throw new NoSuchElementException(); + for(Decl d: decls) { + if (i < d.names.size()) return (ExprVar) (d.names.get(i)); + i = i - d.names.size(); + } + throw new NoSuchElementException(); + } + + /** Return the i-th variable's bound. */ + public Expr getBound(int i) { + if (i<0) throw new NoSuchElementException(); + for(Decl d: decls) { + if (i < d.names.size()) return d.expr; + i = i - d.names.size(); + } + throw new NoSuchElementException(); + } + + //=============================================================================================================// + + /** {@inheritDoc} */ + @Override public Pos span() { + Pos p = span; + // We intentionally do NOT merge the VAR's position into the span. + // That allows us to control the highlighting of this component + // simply by deciding this.pos and this.closingBracket + if (p == null) span = (p = pos.merge(closingBracket).merge(sub.span())); + return p; + } + + //=============================================================================================================// + + /** {@inheritDoc} */ + @Override public void toString(StringBuilder out, int indent) { + if (indent<0) { + boolean first = true; + if (op!=Op.COMPREHENSION) out.append('(').append(op).append(' '); else out.append('{'); + for(Decl d: decls) for(ExprHasName v: d.names) { if (!first) out.append(','); first=false; out.append(v.label); } + if (op!=Op.COMPREHENSION || !(sub instanceof ExprConstant) || ((ExprConstant)sub).op!=ExprConstant.Op.TRUE) + {out.append(" | "); sub.toString(out,-1);} + if (op!=Op.COMPREHENSION) out.append(')'); else out.append('}'); + } else { + for(int i=0; i decls, Expr sub) { + Type t = this==SUM ? Type.smallIntType() : (this==COMPREHENSION ? Type.EMPTY : Type.FORMULA); + if (this!=SUM) sub = sub.typecheck_as_formula(); else sub = sub.typecheck_as_int(); + boolean ambiguous = sub.ambiguous; + JoinableList errs = emptyListOfErrors; + if (sub.mult!=0) errs = errs.make(new ErrorSyntax(sub.span(), "Multiplicity expression not allowed here.")); + long weight = sub.weight; + if (decls.size()==0) errs = errs.make(new ErrorSyntax(pos, "List of variables cannot be empty.")); + for(Decl d: decls) { + Expr v = d.expr; + ambiguous = ambiguous || v.ambiguous; + weight = weight + v.weight; + errs = errs.make(v.errors); + if (v.errors.size()>0) continue; + if (v.type.size()==0) { + errs = errs.make(new ErrorType(v.span(), "This must be a set or relation. Instead, its type is " + v.type)); + continue; + } + ExprUnary.Op op = v.mult(); + if (op==ExprUnary.Op.EXACTLYOF) { errs = errs.make(new ErrorType(v.span(), "This cannot be an exactly-of expression.")); continue; } + if (this!=SUM && this!=COMPREHENSION) continue; + if (!v.type.hasArity(1)) { + errs = errs.make(new ErrorType(v.span(), "This must be a unary set. Instead, its type is " + v.type)); + continue; + } + if (v.mult==1) { + if (op == ExprUnary.Op.SETOF) + errs = errs.make(new ErrorType(v.span(), "This cannot be a set-of expression.")); + else if (op == ExprUnary.Op.SOMEOF) + errs = errs.make(new ErrorType(v.span(), "This cannot be a some-of expression.")); + else if (op == ExprUnary.Op.LONEOF) + errs = errs.make(new ErrorType(v.span(), "This cannot be a lone-of expression.")); + } + if (this==COMPREHENSION) { + Type t1 = v.type.extract(1); + for(int n=d.names.size(); n>0; n--) if (t==EMPTY) t = t1; else t = t.product(t1); + } + } + if (errs.isEmpty()) errs = sub.errors; // if the vars have errors, then the subexpression's errors will be too confusing, so let's skip them + return new ExprQt(pos, closingBracket, this, t, ConstList.make(decls), sub, ambiguous, weight, errs); + } + + /** Returns the human readable label for this operator */ + @Override public final String toString() { return label; } + } + + //=============================================================================================================// + + /** {@inheritDoc} */ + @Override public Expr resolve(Type unused, Collection warns) { + if (warns!=null && op!=Op.COMPREHENSION) { + for(int i=0; i1 && d.disjoint != null); + } + if (!hasDisjoint) return this; + TempList newdecls = new TempList(decls.size()); + Expr guard = null; + for(Decl d: decls) { + if (d.names.size()<=1 || d.disjoint==null) { newdecls.add(d); continue; } + guard = ExprList.makeDISJOINT(d.disjoint, null, d.names).and(guard); + newdecls.add(new Decl(null, null, null, d.names, d.expr)); + } + if (guard==null) return this; + Expr sub; + switch(op) { + case SUM: sub = guard.ite(this.sub, ExprConstant.ZERO); break; + case ALL: sub = guard.implies(this.sub); break; + default: sub = guard.and(this.sub); + } + return op.make(pos, closingBracket, newdecls.makeConst(), sub); + } + + //=============================================================================================================// + + /** {@inheritDoc} */ + public int getDepth() { + int max = sub.getDepth(); + for(Decl d: decls) for(ExprHasName x: d.names) { int tmp = x.getDepth(); if (max T accept(VisitReturn visitor) throws Err { return visitor.visit(this); } + + /** {@inheritDoc} */ + @Override public String getHTML() { + StringBuilder sb = new StringBuilder("").append(op).append(" "); + boolean first = true; + for (Decl d: decls) for(ExprHasName v: d.names) { if (!first) sb.append(", "); sb.append(v.label); first=false; } + return sb.append("... ").append(type).append("").toString(); + } + + /** {@inheritDoc} */ + @Override public List getSubnodes() { + ArrayList ans = new ArrayList(); + for(Decl d: decls) for(ExprHasName v: d.names) { + ans.add(make(v.pos, v.pos, "var "+v.label+" "+v.type+"", d.expr)); + } + ans.add(make(sub.span(), sub.span(), "body", sub)); + return ans; + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/ast/ExprUnary.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/ast/ExprUnary.java new file mode 100644 index 00000000..7099f886 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/ast/ExprUnary.java @@ -0,0 +1,348 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4compiler.ast; + +import static edu.mit.csail.sdg.alloy4compiler.ast.Sig.SIGINT; +import static edu.mit.csail.sdg.alloy4compiler.ast.Sig.UNIV; +import static edu.mit.csail.sdg.alloy4compiler.ast.Type.EMPTY; + +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.List; + +import edu.mit.csail.sdg.alloy4.DirectedGraph; +import edu.mit.csail.sdg.alloy4.Err; +import edu.mit.csail.sdg.alloy4.ErrorSyntax; +import edu.mit.csail.sdg.alloy4.ErrorType; +import edu.mit.csail.sdg.alloy4.ErrorWarning; +import edu.mit.csail.sdg.alloy4.JoinableList; +import edu.mit.csail.sdg.alloy4.Pos; +import edu.mit.csail.sdg.alloy4.Util; +import edu.mit.csail.sdg.alloy4compiler.ast.Sig.PrimSig; +import edu.mit.csail.sdg.alloy4compiler.ast.Type.ProductType; + +/** Immutable; represents a unary expression of the form "(OP subexpression)" + * + *

Invariant: type!=EMPTY => sub.mult==0 + */ + +public final class ExprUnary extends Expr { + + /** The unary operator. */ + public final Op op; + + /** The subexpression. */ + public final Expr sub; + + /** Caches the span() result. */ + private Pos span=null; + + //============================================================================================================// + + /** {@inheritDoc} */ + @Override public Pos span() { + Pos p=span; + if (p==null) { if (op==Op.NOOP && pos!=Pos.UNKNOWN) span=(p=pos); else span=(p=pos.merge(sub.span())); } + return p; + } + + //============================================================================================================// + + /** {@inheritDoc} */ + @Override public void toString(StringBuilder out, int indent) { + if (indent<0) { + switch(op) { + case SOMEOF: out.append("some "); break; + case LONEOF: out.append("lone "); break; + case ONEOF: out.append("one "); break; + case SETOF: out.append("set "); break; + case EXACTLYOF: out.append("exactly "); break; + case CAST2INT: out.append("int["); sub.toString(out,-1); out.append(']'); return; + case CAST2SIGINT: out.append("Int["); sub.toString(out,-1); out.append(']'); return; + case NOOP: break; + default: out.append(op).append(' '); + } + sub.toString(out,-1); + } else { + for(int i=0; iint"), + /** integer-to-IntAtom */ CAST2SIGINT("int->Int"), + /** No-Operation */ NOOP("NOOP"); + + /** The constructor */ + private Op(String label) {this.label=label;} + + /** The human readable label for this operator */ + private final String label; + + /** Construct an ExprUnary node. + * @param pos - the original position of the "unary operator" in the file (can be null if unknown) + * @param sub - the subexpression + * + *

Alloy4 disallows multiplicity like this: "variable : one (X lone-> Y)", + *
that is, a some/lone/one in front of an arrow multiplicity declaration. + *
Alloy4 does allow "variable : set (X lone-> Y)", where we ignore the word "set". + *
(This desugaring is done by the ExprUnary.Op.make() method, so ExprUnary's constructor never sees it) + */ + public final Expr make(Pos pos, Expr sub) { return make(pos, sub, null, 0); } + + /** Construct an ExprUnary node. + * @param pos - the original position of the "unary operator" in the file (can be null if unknown) + * @param sub - the subexpression + * @param extraError - if nonnull, it will be appended as an extra error + * @param extraWeight - it's the amount of extra weight + * + *

Alloy4 disallows multiplicity like this: "variable : one (X lone-> Y)", + *
that is, a some/lone/one in front of an arrow multiplicity declaration. + *
Alloy4 does allow "variable : set (X lone-> Y)", where we ignore the word "set". + *
(This desugaring is done by the ExprUnary.Op.make() method, so ExprUnary's constructor never sees it) + */ + public final Expr make(Pos pos, Expr sub, Err extraError, long extraWeight) { + if (pos==null || pos==Pos.UNKNOWN) { if (this==NOOP) pos = sub.pos; else pos = sub.span(); } + JoinableList errors = sub.errors.make(extraError); + if (sub.mult!=0) { + if (this==SETOF) return sub; + if (this!=NOOP && extraError==null) + errors = errors.make(new ErrorSyntax(sub.span(), "Multiplicity expression not allowed here.")); + // When you have a multiplicity expression like (A one->one B), and you call cint() on it, + // cint() will try to compose a NOOP node around the (A one->one B) with the error message "This must be an integer!" + // So in such a case, we will have a "NOOP" in front of a "MULTIPLICITY", and we don't want + // to clutter the output window with an extra useless report of "Multiplicity expression not allowed here!" + } + extraError=null; + switch(this) { + case NOOP: break; + case NOT: sub=sub.typecheck_as_formula(); break; + case CAST2SIGINT: + if (sub instanceof ExprUnary) + if (((ExprUnary) sub).op == CAST2SIGINT) + return sub; + sub=sub.typecheck_as_int(); + break; + case CAST2INT: + if (sub instanceof ExprUnary) { + // shortcircuit + ExprUnary sub2 = (ExprUnary) sub; + if (sub2.op == CAST2INT) + return sub2; + if (sub2.op == CAST2SIGINT) + return sub2.sub; + } + sub=sub.typecheck_as_set(); + break; + default: sub=sub.typecheck_as_set(); + } + Type type=sub.type; + if (sub.errors.isEmpty()) switch(this) { + case EXACTLYOF: case SOMEOF: case LONEOF: case ONEOF: case SETOF: + if (this==SETOF || this==EXACTLYOF) type=Type.removesBoolAndInt(sub.type); else type=sub.type.extract(1); + if (type==EMPTY) extraError=new ErrorType(sub.span(), "After the some/lone/one multiplicity symbol, " + + "this expression must be a unary set.\nInstead, its possible type(s) are:\n" + sub.type); + break; + case NOT: case NO: case SOME: case LONE: case ONE: + type=Type.FORMULA; + break; + case TRANSPOSE: + type=sub.type.transpose(); + if (type==EMPTY) extraError=new ErrorType(sub.span(), "~ can be used only with a binary relation.\n" + + "Instead, its possible type(s) are:\n"+sub.type); + break; + case RCLOSURE: case CLOSURE: + type=sub.type.closure(); + if (type==EMPTY) extraError=new ErrorType(sub.span(), label+" can be used only with a binary relation.\n" + + "Instead, its possible type(s) are:\n"+sub.type); + if (this==RCLOSURE) type=Type.make2(UNIV); + break; + case CARDINALITY: + type=Type.smallIntType(); + break; + case CAST2INT: + if (!sub.type.hasArity(1)) extraError=new ErrorType(sub.span(), "int[] can be used only with a unary set.\n" + + "Instead, its possible type(s) are:\n"+sub.type); + type=Type.smallIntType(); + break; + case CAST2SIGINT: + type=SIGINT.type; + break; + } + return new ExprUnary(pos, this, sub, type, extraWeight + sub.weight, errors.make(extraError)); + } + + /** Returns the human readable label for this operator */ + @Override public final String toString() { return label; } + + /** Returns the human readable label already encoded for HTML */ + public final String toHTML() { + if (this == CAST2INT) return "Int->int"; + if (this == CAST2SIGINT) return "int->Int"; + return label; + } + } + + //============================================================================================================// + + /** {@inheritDoc} */ + @Override public Expr resolve(Type p, Collection warns) { + if (errors.size()>0) return this; + ErrorWarning w1=null, w2=null; + Type s=p; + switch(op) { + case NOT: + s=Type.FORMULA; + break; + case TRANSPOSE: case RCLOSURE: case CLOSURE: + if (warns!=null && op!=Op.TRANSPOSE && type.join(type).hasNoTuple()) + w1=new ErrorWarning(pos, this+" is redundant since its domain and range are disjoint: "+sub.type.extract(2)); + s = (op!=Op.TRANSPOSE) ? resolveClosure(p, sub.type) : sub.type.transpose().intersect(p).transpose() ; + if (warns!=null && s==EMPTY && p.hasTuple()) + w2=new ErrorWarning(sub.span(), + "The value of this expression does not contribute to the value of the parent.\nParent's relevant type = " + +p+"\nThis expression's type = "+sub.type.extract(2)); + break; + case CARDINALITY: case NO: case ONE: case SOME: case LONE: + s=Type.removesBoolAndInt(sub.type); + break; + case CAST2SIGINT: + s=Type.smallIntType(); + break; + case CAST2INT: + s=sub.type.intersect(SIGINT.type); + if (warns!=null && s.hasNoTuple()) + w1=new ErrorWarning(sub.span(), + "This expression should contain Int atoms.\nInstead, its possible type(s) are:\n"+sub.type.extract(1)); + break; + } + Expr sub = this.sub.resolve(s, warns); + if (w1!=null) warns.add(w1); + if (w2!=null) warns.add(w2); + return (sub==this.sub) ? this : op.make(pos, sub, null, weight-(this.sub.weight)); + } + + //============================================================================================================// + + /** Helper method that computes the relevant type for a closure expression. + * + *

Return Value == { c1->c2 | c1->c2 in childType, AND exists p1->p2 in parentType + * where p1..c1..c2..p2 is a path in the closure graph } + * + *

+ * We need to do this because of situations like this follow: + * Suppose e's type is "A->B + B->C". + * Therefore, ^e = A->B + B->C + A->C which makes sense. + * But as we compute the relevance type back down, we may have lost some entries, + * and possibly end up with only A->B + A->C so we need to rediscover the relevant edges. + */ + private static Type resolveClosure (Type parent, Type child) { + LinkedHashSet nodes = new LinkedHashSet(); + DirectedGraph graph = new DirectedGraph(); + // For each (v1->v2) in childType, add (v1->v2) into the graph. + for (ProductType c:child) if (c.arity()==2) { + PrimSig a=c.get(0), b=c.get(1); + nodes.add(a); + nodes.add(b); + graph.addEdge(a,b); + } + // For each distinct v1 and v2 in the graph where v1&v2!=empty, add the edges v1->v2 and v2->v1. + for (PrimSig a:nodes) for (PrimSig b:nodes) if (a!=b && a.intersects(b)) graph.addEdge(a,b); + // For each a->b in ParentType: + // 1) add a + // 2) add b + // 3) if a has subtypes/supertypes in the graph, connect between a and them. + // 4) if b has subtypes/supertypes in the graph, connect between b and them. + for (ProductType p:parent) if (p.arity()==2) { + PrimSig a=p.get(0), b=p.get(1); + // Add edges between a and all its subtypes and supertypes + if (!nodes.contains(a)) { + for (PrimSig x:nodes) if (a.intersects(x)) { graph.addEdge(a,x); graph.addEdge(x,a); } + nodes.add(a); + } + // Add edges between b and all its subtypes and supertypes + if (!nodes.contains(b)) { + for (PrimSig x:nodes) if (b.intersects(x)) { graph.addEdge(b,x); graph.addEdge(x,b); } + nodes.add(b); + } + } + // For each c1->c2 in childType, add c1->c2 into the finalType if there exists p1->p2 in parentType + // such that p1->..->c1->c2->..->p2 is a path in the graph. + Type answer=Type.EMPTY; + for (ProductType c:child) if (c.arity()==2) { + PrimSig c1=c.get(0), c2=c.get(1); + for (ProductType p:parent) if (p.arity()==2) { + PrimSig p1=p.get(0), p2=p.get(1); + if (graph.hasPath(p1,c1) && graph.hasPath(c2,p2)) { answer=answer.merge(c); break; } + } + } + return answer; + } + + //============================================================================================================// + + /** {@inheritDoc} */ + public int getDepth() { return 1 + sub.getDepth(); } + + /** {@inheritDoc} */ + @Override public final T accept(VisitReturn visitor) throws Err { return visitor.visit(this); } + + /** {@inheritDoc} */ + @Override public String getHTML() { return op==Op.NOOP ? sub.getHTML() : (op+" " + type + ""); } + + /** {@inheritDoc} */ + @Override public List getSubnodes() { return op==Op.NOOP ? sub.getSubnodes() : Util.asList(sub); } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/ast/ExprVar.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/ast/ExprVar.java new file mode 100644 index 00000000..2fdc74db --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/ast/ExprVar.java @@ -0,0 +1,75 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4compiler.ast; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import edu.mit.csail.sdg.alloy4.Pos; +import edu.mit.csail.sdg.alloy4.Err; +import edu.mit.csail.sdg.alloy4.ErrorWarning; + +/** Immutable; represents a LET or QUANTIFICATION variable in the AST. + * + *

Invariant: type!=EMPTY => (type==expr.type && !expr.ambiguous) + */ + +public final class ExprVar extends ExprHasName { + + /** {@inheritDoc} */ + @Override public void toString(StringBuilder out, int indent) { + if (indent<0) { + out.append(label); + } else { + for(int i=0; i with type=").append(type).append('\n'); + } + } + + /** Constructs an ExprVar object */ + private ExprVar(Pos pos, String label, Type type) { + super(pos, label, type); + } + + /** Constructs an ExprVar variable with the EMPTY type + * @param pos - the original position in the source file (can be null if unknown) + * @param label - the label for this variable (it is only used for pretty-printing and does not have to be unique) + */ + public static ExprVar make(Pos pos, String label) { + return new ExprVar(pos, label, Type.EMPTY); + } + + /** Constructs an ExprVar variable with the given type + * @param pos - the original position in the source file (can be null if unknown) + * @param label - the label for this variable (it is only used for pretty-printing and does not have to be unique) + * @param type - the type + */ + public static ExprVar make(Pos pos, String label, Type type) { + return new ExprVar(pos, label, type); + } + + /** {@inheritDoc} */ + @Override public Expr resolve(Type p, Collection warns) { return this; } + + /** {@inheritDoc} */ + @Override public T accept(VisitReturn visitor) throws Err { return visitor.visit(this); } + + /** {@inheritDoc} */ + @Override public String getHTML() { return "variable: " + label + " " + type + ""; } + + /** {@inheritDoc} */ + @Override public List getSubnodes() { return new ArrayList(0); } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/ast/Func.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/ast/Func.java new file mode 100644 index 00000000..fa334ad1 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/ast/Func.java @@ -0,0 +1,207 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4compiler.ast; + +import java.util.ArrayList; +import java.util.List; +import java.util.NoSuchElementException; +import edu.mit.csail.sdg.alloy4.ConstList; +import edu.mit.csail.sdg.alloy4.Pos; +import edu.mit.csail.sdg.alloy4.Err; +import edu.mit.csail.sdg.alloy4.ErrorSyntax; +import edu.mit.csail.sdg.alloy4.ErrorType; +import edu.mit.csail.sdg.alloy4.Util; + +/** Mutable; represents a predicate or function. + * + *

Invariant: the list of parameters do not contain duplicates + *

Invariant: none of the parameter declaration contains a predicate/function call + *

Invariant: the return type declaration does not contain a predicate/function call + */ + +public final class Func extends Browsable { + + /** The location in the original file where this predicate/function is declared; never null. */ + public final Pos pos; + + /** If nonnull, then this predicate/function is private (and this.isPrivate is the location of the "private" keyword) */ + public final Pos isPrivate; + + /** The label of this predicate/function; it does not need to be unique. */ + public final String label; + + /** True if this is a predicate; false if this is a function. */ + public final boolean isPred; + + /** The list of parameter declarations; may be an empty list if this predicate/function has no parameters. */ + public final ConstList decls; + + /** The declared return type; never null. */ + public final Expr returnDecl; + + /** Return the number of parameters. */ + public int count() { + int n = 0; + for(Decl d: decls) n = n + d.names.size(); + return n; + } + + /** Return the i-th parameter where i goes from 0 to count()-1 */ + public ExprVar get(int i) { + if (i<0) throw new NoSuchElementException(); + for(Decl d: decls) { + if (i params() { + int n = count(); + List list = new ArrayList(n); + for(Decl d: decls) for(ExprHasName name: d.names) list.add((ExprVar)name); + return list; + } + + /** Constructs a new predicate/function. + * + *

The first declaration's bound should be an expression with no free variables. + *
The second declaration's bound should be an expression with no free variables, except possibly the parameters in first declaration. + *
The third declaration's bound should be an expression with no free variables, except possibly the parameters in first two declarations. + *
etc. + *
The return declaration should have no free variables, except possibly the list of input parameters. + * + * @param pos - the original position in the file + * @param label - the label for this predicate/function (does not have to be unique) + * @param decls - the list of parameter declarations (can be null or an empty list if this predicate/function has no parameters) + * @param returnDecl - the return declaration (null if this is a predicate rather than a function) + * + * @throws ErrorType if returnType!=null and returnType cannot be unambiguously typechecked to be a set/relation + * @throws ErrorSyntax if the list of parameters contain duplicates + * @throws ErrorSyntax if at least one of the parameter declaration contains a predicate/function call + * @throws ErrorSyntax if this function's return type declaration contains a predicate/function call + */ + public Func(Pos pos, String label, List decls, Expr returnDecl, Expr body) throws Err { + this(pos, null, label, decls, returnDecl, body); + } + + /** Constructs a new predicate/function. + * + *

The first declaration's bound should be an expression with no free variables. + *
The second declaration's bound should be an expression with no free variables, except possibly the parameters in first declaration. + *
The third declaration's bound should be an expression with no free variables, except possibly the parameters in first two declarations. + *
etc. + *
The return declaration should have no free variables, except possibly the list of input parameters. + * + * @param pos - the original position in the file + * @param isPrivate - if nonnull, then the user intended this func/pred to be "private" + * @param label - the label for this predicate/function (does not have to be unique) + * @param decls - the list of parameter declarations (can be null or an empty list if this predicate/function has no parameters) + * @param returnDecl - the return declaration (null if this is a predicate rather than a function) + * + * @throws ErrorType if returnType!=null and returnType cannot be unambiguously typechecked to be a set/relation + * @throws ErrorSyntax if the list of parameters contain duplicates + * @throws ErrorSyntax if at least one of the parameter declaration contains a predicate/function call + * @throws ErrorSyntax if this function's return type declaration contains a predicate/function call + */ + public Func(Pos pos, Pos isPrivate, String label, List decls, Expr returnDecl, Expr body) throws Err { + if (pos==null) pos = Pos.UNKNOWN; + this.pos = pos; + this.isPrivate = isPrivate; + this.label = (label==null ? "" : label); + this.isPred = (returnDecl==null); + if (returnDecl==null) returnDecl = ExprConstant.FALSE; + if (returnDecl.mult==0 && returnDecl.type.arity()==1) returnDecl = ExprUnary.Op.ONEOF.make(null, returnDecl); + this.returnDecl = returnDecl; + this.body = body; + if (body.mult!=0) throw new ErrorSyntax(body.span(), "Multiplicity expression not allowed here."); + this.decls = ConstList.make(decls); + for(int n=count(), i=0; iPrecondition: The expression should have no free variables, + * except possibly the list of function parameters. + * + * @throws ErrorSyntax if newBody.mult!=0 + * @throws ErrorType if newBody cannot be unambiguously resolved + * @throws ErrorType if newBody's type is incompatible with the original declared type of this predicate/function + */ + public void setBody(Expr newBody) throws Err { + if (isPred) { + newBody = newBody.typecheck_as_formula(); + if (newBody.ambiguous) newBody = newBody.resolve_as_formula(null); + if (newBody.errors.size()>0) throw newBody.errors.pick(); + } else { + newBody = newBody.typecheck_as_set(); + if (newBody.ambiguous) newBody = newBody.resolve_as_set(null); + if (newBody.errors.size()>0) throw newBody.errors.pick(); + if (newBody.type.arity() != returnDecl.type.arity()) + throw new ErrorType(newBody.span(), + "Function return type is "+returnDecl.type+",\nso the body must be a relation with arity " + +returnDecl.type.arity()+".\nSo the body's type cannot be: "+newBody.type); + } + if (newBody.mult!=0) throw new ErrorSyntax(newBody.span(), "Multiplicity expression not allowed here."); + this.body = newBody; + } + + /** Return the body of this predicate/function. + *
If the user has not called setBody() to set the body, + *
then the default body is "false" (if this is a predicate), + *
or the empty set/relation of the appropriate arity (if this is a function). + */ + public Expr getBody() { return body; } + + /** Convenience method that calls this function with the given list of arguments. */ + public Expr call(Expr... args) { return ExprCall.make(null, null, this, Util.asList(args), 0); } + + /** Returns a human-readable description for this predicate/function */ + @Override public final String toString() { return (isPred ? "pred " : "fun ") + label; } + + /** {@inheritDoc} */ + @Override public final Pos pos() { return pos; } + + /** {@inheritDoc} */ + @Override public final Pos span() { return pos; } + + /** {@inheritDoc} */ + @Override public String getHTML() { return (isPred ? "pred " : "fun ") + label; } + + /** {@inheritDoc} */ + @Override public List getSubnodes() { + ArrayList ans = new ArrayList(); + for(Decl d: decls) for(ExprHasName v: d.names) { + ans.add(make(v.pos, v.pos, "parameter "+v.label+" "+v.type+"", d.expr)); + } + if (!isPred) ans.add(make(returnDecl.span(), returnDecl.span(), "return type " + returnDecl.type + "", returnDecl)); + ans.add(make(body.span(), body.span(), "body " + body.type + "", body)); + return ans; + } + +} \ No newline at end of file diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/ast/Module.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/ast/Module.java new file mode 100644 index 00000000..09d364bb --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/ast/Module.java @@ -0,0 +1,77 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4compiler.ast; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.List; +import javax.swing.JFrame; +import edu.mit.csail.sdg.alloy4.ConstList; +import edu.mit.csail.sdg.alloy4.Err; +import edu.mit.csail.sdg.alloy4.Listener; +import edu.mit.csail.sdg.alloy4.Pair; +import edu.mit.csail.sdg.alloy4.SafeList; +import edu.mit.csail.sdg.alloy4compiler.ast.Command; +import edu.mit.csail.sdg.alloy4compiler.ast.Expr; +import edu.mit.csail.sdg.alloy4compiler.ast.Func; +import edu.mit.csail.sdg.alloy4compiler.ast.Sig; + +/** This interface represents an Alloy module. */ + +public interface Module { + + /** Returns the text of the "MODULE" line at the top of the file; "unknown" if the line has not be parsed from the file yet. */ + public String getModelName(); + + /** Return the simplest path pointing to this Module ("" if this is the main module) */ + public String path(); + + /** Return the list containing THIS MODULE and all modules reachable from this module. */ + public SafeList getAllReachableModules(); + + /** Return the list of all relative filenames included from this MODULE. */ + public List getAllReachableModulesFilenames(); + + /** Return the list containing UNIV, SIGINT, SEQIDX, STRING, NONE, and all sigs defined in this module or a reachable submodule. */ + public ConstList getAllReachableSigs(); + + /** Returns an unmodifiable list of all signatures defined inside this module. */ + public SafeList getAllSigs(); + + /** Return an unmodifiable list of all functions in this module. */ + public SafeList getAllFunc(); + + /** Return an unmodifiable list of all assertions in this module. */ + public ConstList> getAllAssertions(); + + /** Return an unmodifiable list of all facts in this module. */ + public SafeList> getAllFacts(); + + /** Return the conjunction of all facts in this module and all reachable submodules (not including field constraints, nor including sig appended constraints) */ + public Expr getAllReachableFacts(); + + /** Return an unmodifiable list of all commands in this module. */ + public ConstList getAllCommands(); + + /** Add a global expression; if the name already exists, it is removed first. */ + public void addGlobal(String name, Expr value); + + /** Display this object (and so objects) as a tree; if listener!=null, it will receive OurTree.Event.SELECT events. */ + public JFrame showAsTree(Listener listener); + + /** Parse one expression by starting fromt this module as the root module. */ + public Expr parseOneExpressionFromString(String input) throws Err, FileNotFoundException, IOException; +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/ast/Sig.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/ast/Sig.java new file mode 100644 index 00000000..d0a6cd03 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/ast/Sig.java @@ -0,0 +1,590 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4compiler.ast; + +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import edu.mit.csail.sdg.alloy4.Pos; +import edu.mit.csail.sdg.alloy4.Err; +import edu.mit.csail.sdg.alloy4.ErrorFatal; +import edu.mit.csail.sdg.alloy4.ErrorType; +import edu.mit.csail.sdg.alloy4.ErrorWarning; +import edu.mit.csail.sdg.alloy4.ErrorSyntax; +import edu.mit.csail.sdg.alloy4.ConstList; +import edu.mit.csail.sdg.alloy4.Version; +import edu.mit.csail.sdg.alloy4.ConstList.TempList; +import edu.mit.csail.sdg.alloy4compiler.ast.Attr.AttrType; +import edu.mit.csail.sdg.alloy4.SafeList; +import edu.mit.csail.sdg.alloy4.Util; + +/** Mutable; represents a signature. */ + +public abstract class Sig extends Expr { + + /** The built-in "univ" signature. */ + public static final PrimSig UNIV = new PrimSig("univ", null, false); + + /** The built-in "Int" signature. */ + public static final PrimSig SIGINT = new PrimSig("Int", UNIV, false); + + /** The built-in "seq/Int" signature. */ + public static final PrimSig SEQIDX = new PrimSig("seq/Int", SIGINT, true); + + /** The built-in "String" signature. */ + public static final PrimSig STRING = new PrimSig("String", UNIV, true); + + /** The built-in "none" signature. */ + public static final PrimSig NONE = new PrimSig("none", null, false); + + /** The built-in "none" signature. */ + public static final PrimSig GHOST = mkGhostSig(); + + private static final PrimSig mkGhostSig() { + try { + return new PrimSig("Univ", null, new Attr[0]); + } catch (Err e) { + return null; // never happens + } + } + + /** Returns the name for this sig; this name need not be unique. */ + @Override public final String toString() { return label; } + + /** {@inheritDoc} */ + @Override public final void toString(StringBuilder out, int indent) { + if (indent<0) { + out.append(label); + } else { + for(int i=0; i Note: at most one of "lone", "one", "some" can be nonnull for each sig. + */ + public final Pos isOne; + + /** Nonnull if this sig's multiplicity is declared to be some. + *

Note: at most one of "lone", "one", "some" can be nonnull for each sig. + */ + public final Pos isSome; + + /** Nonnull if the user wanted this sig to be private. + *

Note: this value is always null for builtin sigs. + */ + public final Pos isPrivate; + + /** Nonnull if the sig is toplevel and is an enum. + *

Note: this value is always null for builtin sigs. + */ + public final Pos isEnum; + + /** Nonnull if this sig is a meta sig. + *

Note: this value is always null for builtin sigs. + */ + public final Pos isMeta; + + /** The label for this sig; this name does not need to be unique. */ + public final String label; + + /** The declaration that quantifies over each atom in this sig. */ + public final Decl decl; + + /** The list of "per atom" fact associated with this signature; each fact is allowed to refer to this.decl.get() */ + private final SafeList facts = new SafeList(); + + /** Returns true if this sig is a toplevel sig (meaning: it is UNIV, or it is a non-subset sig with parent==UNIV) */ + public final boolean isTopLevel() { + return (this!=NONE) && (this instanceof PrimSig) && (this==UNIV || ((PrimSig)this).parent==UNIV); + } + + /** Constructs a new builtin PrimSig. */ + private Sig(String label) { + super(Pos.UNKNOWN, null); + Expr oneof = ExprUnary.Op.ONEOF.make(null, this); + ExprVar v = ExprVar.make(null, "this", oneof.type); + this.decl = new Decl(null, null, null, Util.asList(v), oneof); + this.builtin = true; + this.isAbstract = null; + this.isLone = null; + this.isOne = null; + this.isSome = null; + this.label = label; + this.isSubset = null; + this.isSubsig = Pos.UNKNOWN; + this.isPrivate = null; + this.isMeta = null; + this.isEnum = null; + this.attributes = ConstList.make(); + } + + /** Constructs a new PrimSig or SubsetSig. */ + private Sig(Type type, String label, Attr... attributes) throws Err { + super(AttrType.WHERE.find(attributes), type); + this.attributes = Util.asList(attributes); + Expr oneof = ExprUnary.Op.ONEOF.make(null, this); + ExprVar v = ExprVar.make(null, "this", oneof.type); + this.decl = new Decl(null, null, null, Util.asList(v), oneof); + Pos isAbstract=null, isLone=null, isOne=null, isSome=null, isSubsig=null, isSubset=null, isPrivate=null, isMeta=null, isEnum=null; + for(Attr a: attributes) if (a!=null) switch(a.type) { + case ABSTRACT: isAbstract = a.pos.merge(isAbstract); break; + case ENUM: isEnum = a.pos.merge(isEnum); break; + case LONE: isLone = a.pos.merge(isLone); break; + case META: isMeta = a.pos.merge(isMeta); break; + case ONE: isOne = a.pos.merge(isOne); break; + case PRIVATE: isPrivate = a.pos.merge(isPrivate); break; + case SOME: isSome = a.pos.merge(isSome); break; + case SUBSET: isSubset = a.pos.merge(isSubset); break; + case SUBSIG: isSubsig = a.pos.merge(isSubsig); break; + } + this.isPrivate = isPrivate; + this.isMeta = isMeta; + this.isEnum = isEnum; + this.isAbstract = isAbstract; + this.isLone = isLone; + this.isOne = isOne; + this.isSome = isSome; + this.isSubset = isSubset; + this.isSubsig = isSubsig; + this.label = label; + this.builtin = false; + if (isLone!=null && isOne!=null) throw new ErrorSyntax(isLone.merge(isOne), "You cannot declare a sig to be both lone and one."); + if (isLone!=null && isSome!=null) throw new ErrorSyntax(isLone.merge(isSome), "You cannot declare a sig to be both lone and some."); + if (isOne!=null && isSome!=null) throw new ErrorSyntax(isOne.merge(isSome), "You cannot declare a sig to be both one and some."); + if (isSubset!=null && isAbstract!=null) throw new ErrorSyntax(isAbstract, "Subset signature cannot be abstract."); + if (isSubset!=null && isSubsig!=null) throw new ErrorSyntax(isAbstract, "Subset signature cannot be a regular subsignature."); + } + + /** Returns true if we can determine the two expressions are equivalent; may sometimes return false. */ + @Override public boolean isSame(Expr obj) { + Sig me = this; + while(obj instanceof ExprUnary && ((ExprUnary)obj).op==ExprUnary.Op.NOOP) obj=((ExprUnary)obj).sub; + while(obj instanceof SubsetSig && ((SubsetSig)obj).exact && ((SubsetSig)obj).parents.size()==1) obj = ((SubsetSig)obj).parents.get(0); + while(me instanceof SubsetSig && ((SubsetSig)me).exact && ((SubsetSig)me).parents.size()==1) me = ((SubsetSig)me).parents.get(0); + return (me == obj); + } + + /** Returns true iff "this is equal or subtype of that" */ + public abstract boolean isSameOrDescendentOf(Sig that); + + /** {@inheritDoc} */ + public int getDepth() { return 1; } + + /** Add a new per-atom fact; this expression is allowed to refer to this.decl.get() */ + public void addFact(Expr fact) throws Err { + if (fact.ambiguous) fact = fact.resolve_as_formula(null); + if (!fact.errors.isEmpty()) throw fact.errors.pick(); + if (!fact.type.is_bool) throw new ErrorType(fact.span(), "This expression must be a formula; instead its type is "+fact.type); + facts.add(fact); + } + + /** Return the list of per-atom facts; each expression is allowed to refer to this.decl.get() */ + public SafeList getFacts() { return facts.dup(); } + + /** {@inheritDoc} */ + @Override public final String getHTML() { return "sig " + label + " " + type + ""; } + + /** {@inheritDoc} */ + @Override public final List getSubnodes() { + TempList ans = new TempList(); + if (this instanceof PrimSig) { + Sig parent = ((PrimSig)this).parent; + if (parent!=null && !parent.builtin) ans.add(make(parent.pos, parent.span(), "extends sig " + parent.label, parent.getSubnodes())); + } else { + ConstList parents = ((SubsetSig)this).parents; + for(Sig p: parents) ans.add(make(p.pos, p.span(), "in sig " + p.label, p.getSubnodes())); + } + for(Decl d: fields) for(ExprHasName v: d.names) { + ans.add(make(v.span(), v.span(), "field " + v.label + " " + v.type + "", d.expr)); + } + for(Expr f: facts) ans.add(make(f.span(), f.span(), "fact", f)); + return ans.makeConst(); + } + + //==============================================================================================================// + + /** Mutable; reresents a non-subset signature. + * + *

Note: except for "children()", the return value of every method is always valid for all time; + * for example, given sigs A and B, and you call C=A.intersect(B), then the result C will always be + * the intersection of A and B even if the caller later constructs more sigs or subsigs or subsetsigs... + */ + + public static final class PrimSig extends Sig { + + /** Stores its immediate children sigs (not including NONE) + *

Note: if this==UNIV, then this list will always be empty, since we don't keep track of UNIV's children + */ + private final SafeList children = new SafeList(); + + /** Returns its immediate children sigs (not including NONE) + *

Note: if this==UNIV, then this method will throw an exception, since we don't keep track of UNIV's children + */ + public SafeList children() throws Err { + if (this==UNIV) throw new ErrorFatal("Internal error (cannot enumerate the subsigs of UNIV)"); + return children.dup(); + } + + /** Returns its subsigs and their subsigs and their subsigs, etc. + *

Note: if this==UNIV, then this method will throw an exception, since we don't keep track of UNIV's children + */ + public Iterable descendents() throws Err { + if (this==UNIV) throw new ErrorFatal("Internal error (cannot enumerate the subsigs of UNIV)"); + Iterable answer = children.dup(); + for(PrimSig x:children) answer = Util.fastJoin(answer, x.descendents()); + return answer; + } + + /** If this is UNIV or NONE, then this field is null, else this field is the parent sig. */ + public final PrimSig parent; + + /** Constructs a builtin PrimSig. */ + private PrimSig(String label, PrimSig parent, boolean add) { + super(label); + this.parent = parent; + if (add) this.parent.children.add(this); + } + + /** Constructs a non-builtin sig. + * + * @param label - the name of this sig (it does not need to be unique) + * @param parent - the parent (must not be null, and must not be NONE) + * @param attributes - the list of optional attributes such as ABSTRACT, LONE, ONE, SOME, SUBSIG, PRIVATE, META, or ENUM + * + * @throws ErrorSyntax if the signature has two or more multiplicities + * @throws ErrorType if you attempt to extend the builtin sigs NONE, SIGINT, SEQIDX, or STRING + */ + public PrimSig (String label, PrimSig parent, Attr... attributes) throws Err { + super(((parent!=null && parent.isEnum!=null) ? parent.type : null), label, Util.append(attributes, Attr.SUBSIG)); + if (parent==SIGINT) throw new ErrorSyntax(pos, "sig "+label+" cannot extend the builtin \"Int\" signature"); + if (parent==SEQIDX) throw new ErrorSyntax(pos, "sig "+label+" cannot extend the builtin \"seq/Int\" signature"); + if (parent==STRING) throw new ErrorSyntax(pos, "sig "+label+" cannot extend the builtin \"String\" signature"); + if (parent==NONE) throw new ErrorSyntax(pos, "sig "+label+" cannot extend the builtin \"none\" signature"); + if (parent==null) parent=UNIV; else if (parent!=UNIV) parent.children.add(this); + this.parent = parent; + if (isEnum!=null && parent!=UNIV) throw new ErrorType(pos, "sig "+label+" is not a toplevel sig, so it cannot be an enum."); + for( ; parent!=null ; parent=parent.parent) if (parent.isEnum!=null) { + if (parent!=this.parent) throw new ErrorSyntax(pos, "sig "+label+" cannot extend a signature which is an atom in an enum."); + if (isOne==null) throw new ErrorSyntax(pos, "sig "+label+" is an atom in an enum, so it must have the \"one\" multiplicity."); + } + } + + /** Constructs a toplevel non-builtin sig. + * + * @param label - the name of this sig (it does not need to be unique) + * @param attributes - the list of optional attributes such as ABSTRACT, LONE, ONE, SOME, SUBSIG, PRIVATE, META, or ENUM + * + * @throws ErrorSyntax if the signature has two or more multiplicities + */ + public PrimSig(String label, Attr... attributes) throws Err { this(label, null, attributes); } + + /** {@inheritDoc} */ + @Override public boolean isSameOrDescendentOf(Sig that) { + if (this==NONE || this==that || that==UNIV) return true; + if (this==UNIV || that==NONE) return false; + for(PrimSig me=this; me!=null; me=me.parent) if (me==that) return true; + return false; + } + + /** Returns the intersection between this and that (and returns "none" if they do not intersect). */ + public PrimSig intersect(PrimSig that) { + if (this.isSameOrDescendentOf(that)) return this; + if (that.isSameOrDescendentOf(this)) return that; + return NONE; + } + + /** Returns true iff the intersection between this and that is not "none". */ + public boolean intersects(PrimSig that) { + if (this.isSameOrDescendentOf(that)) return this!=NONE; + if (that.isSameOrDescendentOf(this)) return that!=NONE; + return false; + } + + /** Returns the most-specific-sig that contains this and that. + * In particular, if this extends that, then return that. + */ + public PrimSig leastParent(PrimSig that) { + if (isSameOrDescendentOf(that)) return that; + PrimSig me=this; + while(true) { + if (that.isSameOrDescendentOf(me)) return me; + me=me.parent; + if (me==null) return UNIV; + } + } + } + + //==============================================================================================================// + + /** Mutable; reresents a subset signature. */ + + public static final class SubsetSig extends Sig { + + /** The list of Sig that it is a subset of; this list is never empty. */ + public final ConstList parents; + + /** If true, then this sig is EXACTLY equal to the union of its parents. */ + public final boolean exact; + + /** Computes the type for this sig. */ + private static Type getType(String label, Iterable parents) throws Err { + Type ans = null; + if (parents!=null) for(Sig parent: parents) { + if (parent==UNIV) return UNIV.type; + if (ans==null) ans=parent.type; else ans=ans.unionWithCommonArity(parent.type); + } + return (ans!=null) ? ans : (UNIV.type); + } + + /** Constructs a subset sig. + * + * @param label - the name of this sig (it does not need to be unique) + * @param parents - the list of parents (if this list is null or empty, we assume the caller means UNIV) + * @param attributes - the list of optional attributes such as EXACT, SUBSET, LONE, ONE, SOME, PRIVATE, or META + * + * @throws ErrorSyntax if the signature has two or more multiplicities + * @throws ErrorType if parents only contains NONE + */ + public SubsetSig(String label, Collection parents, Attr... attributes) throws Err { + super(getType(label,parents), label, Util.append(attributes, Attr.SUBSET)); + if (isEnum!=null) throw new ErrorType(pos, "Subset signature cannot be an enum."); + boolean exact = false; + for(Attr a: attributes) if (a!=null && a.type==AttrType.EXACT) exact = true; + this.exact = exact; + TempList temp = new TempList(parents==null ? 1 : parents.size()); + if (parents==null || parents.size()==0) { + temp.add(UNIV); + } else { + for(Sig parent:parents) { + if (!Version.experimental) { + if (parent==SIGINT) throw new ErrorSyntax(pos, "sig "+label+" cannot be a subset of the builtin \"Int\" signature"); + if (parent==SEQIDX) throw new ErrorSyntax(pos, "sig "+label+" cannot be a subset of the builtin \"seq/Int\" signature"); + if (parent==STRING) throw new ErrorSyntax(pos, "sig "+label+" cannot be a subset of the builtin \"String\" signature"); + } + if (parent==Sig.UNIV) {temp.clear(); temp.add(UNIV); break;} + if (parent!=Sig.NONE && !temp.contains(parent)) temp.add(parent); + } + } + if (temp.size()==0) throw new ErrorType(pos, "Sig "+label+" must have at least one non-empty parent."); + this.parents = temp.makeConst(); + } + + /** {@inheritDoc} */ + @Override public boolean isSameOrDescendentOf(Sig that) { + if (that==UNIV || that==this) return true; + if (that==NONE) return false; + for(Sig p:parents) if (p.isSameOrDescendentOf(that)) return true; + return false; + } + } + + //==============================================================================================================// + + /** Mutable; represents a field. */ + + public static final class Field extends ExprHasName { + + /** The sig that this field belongs to; never null. */ + public final Sig sig; + + /** Nonnull if the user wanted this field to be private. */ + public final Pos isPrivate; + + /** Nonnull if this field is a meta field. */ + public final Pos isMeta; + + /** True if this is a defined field. */ + public final boolean defined; + + /** The declaration that this field came from. */ + private Decl decl; + + /** Return the declaration that this field came from. */ + public Decl decl() { return decl; } + + /** Constructs a new Field object. */ + private Field(Pos pos, Pos isPrivate, Pos isMeta, Pos disjoint, Pos disjoint2, Sig sig, String label, Expr bound) throws Err { + super(pos, label, sig.type.product(bound.type)); + this.defined = bound.mult() == ExprUnary.Op.EXACTLYOF; + if (sig.builtin) throw new ErrorSyntax(pos, "Builtin sig \""+sig+"\" cannot have fields."); + if (!bound.errors.isEmpty()) throw bound.errors.pick(); + if (!this.defined && bound.hasCall()) throw new ErrorSyntax(pos, "Field \""+label+"\" declaration cannot contain a function or predicate call."); + if (bound.type.arity()>0 && bound.type.hasNoTuple()) throw new ErrorType(pos, "Cannot bind field "+label+" to the empty set or empty relation."); + this.isPrivate = (isPrivate!=null ? isPrivate : sig.isPrivate); + this.isMeta = (isMeta!=null ? isMeta : sig.isMeta); + this.sig = sig; + } + + /** Returns a human-readable description of this field's name. */ + @Override public String toString() { + if (sig.label.length()==0) return label; else return "field ("+sig+" <: "+label+")"; + } + + /** {@inheritDoc} */ + @Override public void toString(StringBuilder out, int indent) { + if (indent<0) { + out.append("(").append(sig.label).append(" <: ").append(label).append(")"); + } else { + for(int i=0; ifield " + label + " " + type + ""; } + + /** {@inheritDoc} */ + @Override public List getSubnodes() { + Expr bound = decl.expr; + Browsable s = make(sig.pos, sig.span(), "from sig "+sig.label, sig.getSubnodes()); + Browsable b = make(bound.span(), bound.span(), "bound", bound); + return Util.asList(s, b); + } + } + + //==============================================================================================================// + + /** The list of fields. */ + private final SafeList fields = new SafeList(); + + /** Return the list of fields as a unmodifiable list of declarations (where you can see which fields are declared to be disjoint) */ + public final SafeList getFieldDecls() { + return fields.dup(); + } + + /** Return the list of fields as a combined unmodifiable list (without telling you which fields are declared to be disjoint) */ + public final SafeList getFields() { + SafeList ans = new SafeList(); + for(Decl d: fields) for(ExprHasName n: d.names) ans.add((Field)n); + return ans.dup(); + } + + /** Add then return a new field, where "all x: ThisSig | x.F in bound" + *

Note: the bound must be fully-typechecked and have exactly 0 free variable, or have "x" as its sole free variable. + * + * @param label - the name of this field (it does not need to be unique) + * @param bound - the new field will be bound by "all x: one ThisSig | x.ThisField in bound" + * + * @throws ErrorSyntax if the sig is one of the builtin sig + * @throws ErrorSyntax if the bound contains a predicate/function call + * @throws ErrorType if the bound is not fully typechecked or is not a set/relation + */ + public final Field addField (String label, Expr bound) throws Err { + bound = bound.typecheck_as_set(); + if (bound.ambiguous) bound = bound.resolve_as_set(null); + if (bound.mult==0 && bound.type.arity()==1) bound = ExprUnary.Op.ONEOF.make(null, bound); // If unary, and no multiplicity symbol, we assume it's oneOf + final Field f = new Field(null, null, null, null, null, this, label, bound); + final Decl d = new Decl(null, null, null, Arrays.asList(f), bound); + f.decl = d; + fields.add(d); + return f; + } + + /** Add then return a new field, where "all x: ThisSig | x.F in bound" + *

Note: the bound must be fully-typechecked and have exactly 0 free variable, or have "x" as its sole free variable. + * + * @param pos - the position in the original file where this field was defined (can be null if unknown) + * @param isPrivate - if nonnull, that means the user intended this field to be "private" + * @param isMeta - if nonnull, that means the user intended this field to be "meta" + * @param labels - the names of the fields to be added (these names does not need to be unique) + * @param bound - the new field will be bound by "all x: one ThisSig | x.ThisField in bound" + * + * @throws ErrorSyntax if the sig is one of the builtin sig + * @throws ErrorSyntax if the bound contains a predicate/function call + * @throws ErrorType if the bound is not fully typechecked or is not a set/relation + */ + public final Field[] addTrickyField (Pos pos, Pos isPrivate, Pos isDisjoint, Pos isDisjoint2, Pos isMeta, String[] labels, Expr bound) throws Err { + bound = bound.typecheck_as_set(); + if (bound.ambiguous) bound = bound.resolve_as_set(null); + if (bound.mult==0 && bound.type.arity()==1) bound = ExprUnary.Op.ONEOF.make(null, bound); // If unary, and no multiplicity symbol, we assume it's oneOf + final Field[] f = new Field[labels.length]; + for(int i=0; i Note: the definition must be fully-typechecked and have exactly 0 free variables. + *

Note: currently the defined field must consist product and union operators over sigs. + * + * @param pos - the position in the original file where this field was defined (can be null if unknown) + * @param isPrivate - if nonnull, that means this field should be marked as private + * @param isMeta - if nonnull, that means this field should be marked as meta + * @param label - the name of this field (it does not need to be unique) + * @param bound - the new field will be defined to be exactly equal to sig.product(definition) + * + * @throws ErrorSyntax if the sig is one of the builtin sig + * @throws ErrorSyntax if the bound contains a predicate/function call + * @throws ErrorType if the bound is not fully typechecked or is not a set/relation + */ + public final Field addDefinedField(Pos pos, Pos isPrivate, Pos isMeta, String label, Expr bound) throws Err { + bound = bound.typecheck_as_set(); + if (bound.ambiguous) bound = bound.resolve_as_set(null); + if (bound.mult() != ExprUnary.Op.EXACTLYOF) bound = ExprUnary.Op.EXACTLYOF.make(null, bound); + final Field f = new Field(pos, isPrivate, isMeta, null, null, this, label, bound); + final Decl d = new Decl(null, null, null, Arrays.asList(f), bound); + f.decl = d; + fields.add(d); + return f; + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/ast/Type.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/ast/Type.java new file mode 100644 index 00000000..ea992594 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/ast/Type.java @@ -0,0 +1,899 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4compiler.ast; + +import static edu.mit.csail.sdg.alloy4compiler.ast.Sig.NONE; +import static edu.mit.csail.sdg.alloy4compiler.ast.Sig.UNIV; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; + +import edu.mit.csail.sdg.alloy4.ConstList; +import edu.mit.csail.sdg.alloy4.Err; +import edu.mit.csail.sdg.alloy4.ErrorType; +import edu.mit.csail.sdg.alloy4.SafeList; +import edu.mit.csail.sdg.alloy4.ConstList.TempList; +import edu.mit.csail.sdg.alloy4compiler.ast.Sig.PrimSig; + +/** Immutable; represents the type of an expression. + * + *

Invariant: all x:entries | x.arity()>0 + * + *

Note: except for "toString()" and "fold()", the return value of every method is always valid for all time; + * for example, given types A and B, and you call C=A.intersect(B), then the result C will always be + * the intersection of A and B even if the caller later constructs more sigs or subsigs or subsetsigs... + */ + +public final class Type implements Iterable { + + //[AM] +// /** This configuration option is true if we want to automatically cast from int to Int when necessary. */ +// public static final boolean INT2SIGINT=true; +// +// /** This configuration option is true if we want to automatically cast from Int to int when necessary. */ +// public static final boolean SIGINT2INT=true; + + /** Immutable; represents a list of PrimSig objects. + * + *

Invariant: "one of the sig in the list is NONE" iff "every sig in the list is NONE". + * + *

Note: the return value of every method is always valid for all time; + * for example, given ProductType A and B, and you call C=A.intersect(B), then the result C will always be + * the intersection of A and B even if the caller later constructs more sigs or subsigs or subsetsigs... + */ + public static final class ProductType { + + /** The array of PrimSig objects. */ + private final PrimSig[] types; + + /** The ProductType with arity==0 */ + private static final ProductType zero = new ProductType(new PrimSig[0]); + + /** Constructs a new ProductType object consisting of the given array of PrimSig objects. + *

Precondition: "one of the sig in the list is NONE" iff "every sig in the list is NONE" + *

Note: it will use the array as-is, so the caller should give up its reference to the array. + *

Note: this constructor promises it won't call any method or read anything from any of the sig(s). + */ + private ProductType(PrimSig[] array) { + types = array; + } + + /** Constructs a new ProductType made of exactly 1 PrimSig; + *

Note: this constructor promises it won't call any method or read anything from the sig. + */ + private ProductType(PrimSig sig) { + types = new PrimSig[]{sig}; + } + + /** Constructs a new ProductType made of exactly n references to the same PrimSig object. + *

Note: this constructor promises it won't call any method or read anything from the sig. + */ + private ProductType(int n, PrimSig sig) { + types = new PrimSig[n]; + for(int i=0; i=0; i--) if (types[i]!=x.types[i]) return false; + return true; + } + + /** Returns true if (this[i] is equal or subtype of that[i]) for every i. + *

Precondition: this.arity == that.arity + */ + private boolean isSubtypeOf(ProductType that) { + if (this==that) return true; + for(int i=types.length-1; i>=0; i--) if (!types[i].isSameOrDescendentOf(that.types[i])) return false; + return true; + } + + /** Returns true if (this[i] is equal or subtype of that[i]) for every i. + *

Precondition: this.arity == that.arity + */ + private boolean isSubtypeOf(List that) { + for(int i=types.length-1; i>=0; i--) if (!types[i].isSameOrDescendentOf(that.get(i))) return false; + return true; + } + + /** Returns the arity of this ProductType object. */ + public int arity() { return types.length; } + + /** Returns a specific PrimSig in this ProductType + * @throws ArrayIndexOutOfBoundsException if (i < 0) or (i >= arity) + */ + public PrimSig get(int i) { return types[i]; } + + /** Returns true if this.arity==0 or this==NONE->..->NONE */ + public boolean isEmpty() { return types.length==0 || types[0]==NONE; } + + /** Returns the tranpose of this + *

Precondition: this.arity()==2 + */ + private ProductType transpose() { + if (types[0]==types[1]) return this; else return new ProductType(new PrimSig[]{types[1], types[0]}); + } + + /** Returns the cross product of this and that. + *

Note: If either or both is NONE->..->NONE, then we return NONE->..->NONE instead. + */ + ProductType product(ProductType that) { + final int n = types.length + that.types.length; + if (n<0) throw new OutOfMemoryError(); // This means the addition overflowed! + if (this.isEmpty()) return (n==this.types.length) ? this : (new ProductType(n, NONE)); + if (that.isEmpty()) return (n==that.types.length) ? that : (new ProductType(n, NONE)); + final PrimSig[] ans = new PrimSig[n]; + int j=0; + for(int i=0; i Note: if (this[i] & that[i]) is empty for at least one i, then we return "NONE->..->NONE" instead. + * + *

Precondition: this.arity == that.arity + */ + private ProductType intersect(ProductType that) { + if (isEmpty()) return this; + if (that.isEmpty()) return that; + final int n = types.length; + final PrimSig[] ans = new PrimSig[n]; + for(int i=0; i Precondition: this.arity == that.arity + */ + private boolean intersects(ProductType that) { + for(int i=types.length-1; i>=0; i--) if (!types[i].intersects(that.types[i])) return false; + return true; + } + + /** Returns the relational join of this and that. + * + *

Note: if (this.rightmost() & that.leftmost()) is empty, we return NONE->..->NONE instead. + * + *

Precondition: (this.arity > 0) && (that.arity > 0) && (this.arity!=1 || that.arity!=1) + */ + ProductType join(ProductType that) { + int left=types.length, right=that.types.length, n=left+right-2; + if (left<=1 && right<=1) return zero; // We try to do the best we can, in the face of precondition violation + if (n<0) throw new OutOfMemoryError(); // This means the addition overflowed! + final PrimSig a=types[left-1], b=that.types[0], c=a.intersect(b); + if (c==NONE) return new ProductType(n, c); + final PrimSig[] types = new PrimSig[n]; + int j=0; + for(int i=0; ithis[1]->this[2]->this[3]..->this[n-1] + * except the i-th entry is replaced by (this[i] & that). + * + *

Otherwise, this method returns NONE->..->NONE + */ + ProductType columnRestrict(PrimSig that, int i) { + if (i<0 || i>=types.length || isEmpty()) return this; + that = types[i].intersect(that); + if (that==types[i]) return this; + if (that==NONE) return new ProductType(types.length, that); + final PrimSig[] newlist = new PrimSig[types.length]; + for(int j=0; j"); + ans.append(types[i]); + } + return ans.toString(); + } + } + + /** Constant value with is_int==false, is_bool==false, and entries.size()==0. */ + public static final Type EMPTY = new Type(false, null, 0); + + //[AM] + /* Can't be final because it relies on a static Sig.SIGINT being initialized */ + private static Type SMALL_INT = null; + + /** Constant value with is_int==false, is_bool==true, and entries.size()==0. */ + public static final Type FORMULA = new Type(true, null, 0); + + /** Constant value with is_int==true, is_bool==true, and entries.size()==0. */ + public static final Type INTANDFORMULA = new Type(true, null, 0); + + /** True if primitive integer value is a possible value in this type. */ +// private final boolean is_int; + private boolean is_small_int; + + /** True if primitive boolean value is a possible value in this type. */ + public final boolean is_bool; + + /** Contains a summary of the arities in this type. + * + *
The (1<<0) bitmask is nonzero iff arity X exists for some X>30 + *
The (1<<1) bitmask is nonzero iff arity 1 exists + *
The (1<<2) bitmask is nonzero iff arity 2 exists + *
The (1<<3) bitmask is nonzero iff arity 3 exists + *
... + *
The (1<<30) bitmask is nonzero iff arity 30 exists + */ + private final int arities; + + /** Contains the list of ProductType entries in this type. */ + private final ConstList entries; + + public boolean is_int() { return checkIntType(); } + public boolean is_small_int() { return is_int() && is_small_int; } + + private boolean checkIntType() { + if (entries == null) + return false; + for (ProductType e : entries) { + if (e.arity() == 1 && e.get(0) == Sig.SIGINT) + return true; + } + return false; + } + + public static Type smallIntType() { + if (SMALL_INT == null) { + SMALL_INT = make(Sig.SIGINT); + SMALL_INT.is_small_int = true; + } + return SMALL_INT; + } + + /** Returns an iterator that iterates over the ProductType entries in this type. + *

This iterator will reject all modification requests. + */ + public Iterator iterator() { return entries.iterator(); } + + /** Merge "x" into the set of entries, then return the new arity bitmask. + *
Precondition: entries and arities are consistent + */ + private static int add(TempList entries, int arities, ProductType x) { + if (x==null || x.types.length==0) return arities; + final int arity=x.types.length; + // If x is subsumed by a ProductType in this, return. Likewise, remove all entries in this that are subsumed by x. + for(int n=entries.size(), i=n-1; i>=0; i--) { + ProductType y=entries.get(i); + if (y.types.length != arity) continue; + if (x.isSubtypeOf(y)) return arities; + if (y.isSubtypeOf(x)) { n--; entries.set(i, entries.get(n)).remove(n); } + } + if (arity>30) arities = arities | 1; else arities = arities | (1 << arity); + entries.add(x); + return arities; + } + + /** Create a new type consisting of the given set of entries, set of arities, and the given is_int/is_bool values; + *

Precondition: entries and arities must be consistent + */ + private Type(boolean is_bool, ConstList entries, int arities) { + this.is_bool = is_bool; + if (entries==null || entries.size()==0 || arities==0) { + this.entries = ConstList.make(); + this.arities = 0; + } else { + this.entries = entries; + this.arities = arities; + } + } + + /** Create a new type consisting of the given set of entries, set of arities, and the given is_int/is_bool values; + *

Precondition: entries and arities must be consistent + */ + private static Type make(boolean is_bool, ConstList entries, int arities) { + if (entries==null || entries.size()==0 || arities==0) { + return is_bool?FORMULA:EMPTY; + } + return new Type(is_bool, entries, arities); + } + + /** Create the type consisting of the given ProductType entry. + */ + static Type make(ProductType productType) { + int ar=productType.types.length; + if (ar==0) return EMPTY; + return make(false, ConstList.make(1,productType), (ar>30) ? 1 : (1<list[start+1]->..->list[end-1] + * (If start<0, end<0, end>list.size(), or start>=end, this method will return EMPTY) + */ + static Type make(List list, int start, int end) { + if (start<0 || end<0 || start>=end || end>list.size()) return EMPTY; + PrimSig[] newlist = new PrimSig[end-start]; + for(int i=start, j=0; isig". */ + static Type make2(PrimSig sig) { + return make(new ProductType(2,sig)); + } + + /** Create a new type that is the same as "old", except the "is_int" flag is set to true. */ +// static Type makeInt(Type old) { +// if (old.is_int()) return old; else return make(true, old.is_bool, old.entries, old.arities); +// } + + /** Create a new type that is the same as "old", except the "is_bool" flag is set to true. */ + static Type makeBool(Type old) { + if (old.is_bool) return old; else return make(true, old.entries, old.arities); + } + + /** Create a new type that is the same as "old", except the "is_bool" and "is_int" flags are both set to false. */ + public static Type removesBoolAndInt(Type old) { + if (!old.is_bool && !old.is_int()) return old; else return make(false, old.entries, old.arities); + } + + /** Returns true iff ((this subsumes that) and (that subsumes this)) */ + @Override public boolean equals(Object that) { + if (this==that) return true; + if (!(that instanceof Type)) return false; + Type x = (Type)that; + if (arities != x.arities || /*[AM] is_int() != x.is_int() || */is_bool != x.is_bool) return false; + again1: + for(ProductType aa:entries) { + for(ProductType bb:x.entries) if (aa.types.length==bb.types.length && aa.isSubtypeOf(bb)) continue again1; + return false; + } + again2: + for(ProductType bb:x.entries) { + for(ProductType aa:entries) if (aa.types.length==bb.types.length && bb.isSubtypeOf(aa)) continue again2; + return false; + } + return true; + } + + /** Returns a hash code consistent with equals() */ + @Override public int hashCode() { return arities * /*[AM] (is_int()?1732051:1) **/ (is_bool?314157:1); } + + /** Returns true if this.size()==0 or every entry consists only of NONE. */ + public boolean hasNoTuple() { + for(int i=entries.size()-1; i>=0; i--) if (!entries.get(i).isEmpty()) return false; + return true; + } + + /** Returns true if this.size()>0 and at least one entry consists of something other than NONE. */ + public boolean hasTuple() { + for(int i=entries.size()-1; i>=0; i--) if (!entries.get(i).isEmpty()) return true; + return false; + } + + /** Returns the number of ProductType entries in this type. */ + public int size() { return entries.size(); } + + /** Returns true iff this contains an entry of the given arity. */ + public boolean hasArity(int arity) { + if (arity<=30) return arity>0 && ((arities & (1<=0; i--) if (entries.get(i).types.length==arity) return true; + return false; + } + + /** If every entry has the same arity, that arity is returned; + *
else if some entries have different arities, we return -1; + *
else we return 0 (which only happens when there are no entries at all). + */ + public int arity() { + if (arities==0) return 0; + int ans=0; + if ((arities & 1)==0) { + for(int i=1; i<=30; i++) if ((arities & (1<=0; j--) { + int i=entries.get(j).types.length; + if (ans==0) ans=i; else if (ans!=i) return -1; + } + } + return ans; + } + + /** Returns true if exists some A in this, some B in that, where (A[0]&B[0]!=empty) + *

This method ignores the "is_int" and "is_bool" flags. + */ + public boolean firstColumnOverlaps(Type that) { + if ((arities | that.arities)!=0) + for (ProductType a:this) + for (ProductType b:that) + if (a.types[0].intersects(b.types[0])) + return true; + return false; + } + + /** Returns true if exists some A in this, some B in that, where (A.arity==B.arity, and A[0]&B[0]!=empty) + *

This method ignores the "is_int" and "is_bool" flags. + */ + public boolean canOverride(Type that) { + if ((arities & that.arities)!=0) + for (ProductType a:this) + for (ProductType b:that) + if (a.types.length==b.types.length && a.types[0].intersects(b.types[0])) + return true; + return false; + } + + /** Returns true iff exists some A in this, some B in that, where A.arity==B.arity */ + public boolean hasCommonArity(Type that) { + if ((arities & 1)!=0 && (that.arities & 1)!=0) { + if (((arities-1) & (that.arities-1))!=0) return true; + for (ProductType a:this) for (ProductType b:that) if (a.types.length==b.types.length) return true; + return false; + } + return (arities & that.arities)!=0; + } + + /** Returns a new type { A->B | A is in this, and B is in that } + * + *

ReturnValue.is_int == false + *
ReturnValue.is_bool == false + * + *

If this.size()==0, or that.size()==0, then result.size()==0 + */ + public Type product(Type that) { + if ((arities | that.arities)==0) return EMPTY; + TempList ee=new TempList(); + int aa=0; + for (ProductType a:this) for (ProductType b:that) aa=add(ee, aa, a.product(b)); + return make(false, ee.makeConst(), aa); + } + + /** Returns true iff { A&B | A is in this, and B is in that } can have tuples. + */ + public boolean intersects(Type that) { + if ((arities & that.arities)!=0) + for (ProductType a:this) if (!a.isEmpty()) + for (ProductType b:that) if (a.types.length==b.types.length && !b.isEmpty() && a.intersects(b)) + return true; + return false; + } + + /** Returns a new type { A&B | A is in this, and B is in that } + * + *

ReturnValue.is_int == false + *
ReturnValue.is_bool == false + * + *

If this.size()==0, or that.size()==0, or they do not have entries with same arity, then result.size()==0 + */ + public Type intersect(Type that) { + if ((arities & that.arities)==0) return EMPTY; + TempList ee=new TempList(); + int aa=0; + for (ProductType a:this) + for (ProductType b:that) + if (a.types.length==b.types.length) + aa=add(ee, aa, a.intersect(b)); + return make(false, ee.makeConst(), aa); + } + + /** Returns a new type { A&that | A is in this } + * + *

ReturnValue.is_int == false + *
ReturnValue.is_bool == false + * + *

If (this.size()==0), or (that.arity is not in this), then result.size()==0 + */ + public Type intersect(ProductType that) { + if (!hasArity(that.types.length)) return EMPTY; + TempList ee=new TempList(); + int aa=0; + for (ProductType a:this) if (a.types.length==that.types.length) aa=add(ee, aa, a.intersect(that)); + return make(false, ee.makeConst(), aa); + } + + /** Returns a new type { A | A is in this, or A is in that } + * + *

ReturnValue.is_int == this.is_int || that.is_int + *
ReturnValue.is_bool == this.is_bool || that.is_bool + * + *

If this.size()==0 and that.size()==0, then result.size()==0 + * + *

As a special guarantee: if that==null, then the merge() method just returns the current object + */ + public Type merge(Type that) { + if (that==null) return this; + if (is_bool==that.is_bool) { + if (this.size()==0) return that; + if (that.size()==0) return this; + } + TempList ee=new TempList(entries); + int aa=arities; + for(ProductType x:that) aa=add(ee,aa,x); + return make(is_bool||that.is_bool, ee.makeConst(), aa); + } + + /** Returns a new type { A | A is in this, or A == that } + * + *

ReturnValue.is_int == this.is_int + *
ReturnValue.is_bool == this.is_bool + */ + public Type merge(ProductType that) { + TempList ee=new TempList(entries); + int aa=add(ee, arities, that); + return make(is_bool, ee.makeConst(), aa); + } + + /** Returns a new type { A | A is in this, or A == that.subList(begin,end) } + * + *

ReturnValue.is_int == this.is_int + *
ReturnValue.is_bool == this.is_bool + */ + public Type merge(ProductType that, int begin, int end) { + if (!(0<=begin && begin ee=new TempList(entries); + int aa=add(ee, arities, new ProductType(array)); + return make(is_bool, ee.makeConst(), aa); + } + + /** Returns a new type { A | A is in this, or A == that } + * + *

ReturnValue.is_int == this.is_int + *
ReturnValue.is_bool == this.is_bool + */ + public Type merge(List that) { + if (that.size() == 0) return this; + PrimSig[] array=new PrimSig[that.size()]; + for(int i=0; i < array.length; i++) { + array[i] = that.get(i); + if (array[i]==NONE) { + if (hasArity(array.length)) return this; + for(i=0; i ee=new TempList(entries); + int aa=add(ee, arities, new ProductType(array)); + return make(is_bool, ee.makeConst(), aa); + } + + /** Returns a new type { A | (A is in this && A.arity in that) or (A is in that && A.arity in this) } + * + *

ReturnValue.is_int == false + *
ReturnValue.is_bool == false + * + *

If this.size()==0 or that.size()==0, then result.size()==0 + * + *

Special promise: if the result would be identical to this, then we will return "this" as-is, without constructing a new object + */ + public Type unionWithCommonArity(Type that) { + if ((arities & that.arities)==0) return EMPTY; + TempList ee=new TempList(); + int aa=0; + if (this.size()>0 && that.size()>0) { + // add() ensures that if x doesn't need to change "ee", then "ee" will stay unchanged + for(ProductType x:this) if (that.hasArity(x.types.length)) aa=add(ee,aa,x); + for(ProductType x:that) if (this.hasArity(x.types.length)) aa=add(ee,aa,x); + } + // So now, if nothing changed, we want to return "this" as-is + if (!is_int() && !is_bool && aa==this.arities && ee.size()==this.entries.size()) { + for(int i=ee.size()-1; ; i--) { + if (i<0) return this; + if (ee.get(i) != this.entries.get(i)) break; + } + } + return make(false, ee.makeConst(), aa); + } + + /** Returns a new type { A | (A is in this && A.arity in that) } + * + *

ReturnValue.is_int == false + *
ReturnValue.is_bool == false + * + *

If this.size()==0 or that.size()==0, then result.size()==0 + */ + public Type pickCommonArity(Type that) { + if (!is_int() && !is_bool && (arities & 1)==0 && (arities & that.arities)==arities) return this; + TempList ee=new TempList(); + int aa=0; + if ((arities & 1)==0) { + for(ProductType x:entries) { + int xa = 1 << x.types.length; + if ((that.arities & xa) != 0) { aa = (aa|xa); ee.add(x); } + } + } else { + for(ProductType x:entries) if (that.hasArity(x.types.length)) aa=add(ee,aa,x); + } + return make(false, ee.makeConst(), aa); + } + + /** Returns a new type { A in this | A.artiy==1 } + * + *

ReturnValue.is_int == false + *
ReturnValue.is_bool == false + * + *

If this.size()==0, or does not contain any ProductType entries of the given arity, then result.size()==0 + */ + public Type pickUnary() { + if ((arities & (1<<1))==0) return EMPTY; + if (!is_int() && !is_bool && arities==(1<<1)) return this; + TempList ee=new TempList(); + for(ProductType x: entries) if (x.types.length == 1) ee.add(x); + return make(false, ee.makeConst(), (1<<1)); + } + + /** Returns a new type { A in this | A.artiy==2 } + * + *

ReturnValue.is_int == false + *
ReturnValue.is_bool == false + * + *

If this.size()==0, or does not contain any ProductType entries of the given arity, then result.size()==0 + */ + public Type pickBinary() { + if ((arities & (1<<2))==0) return EMPTY; + if (!is_int() && !is_bool && arities==(1<<2)) return this; + TempList ee=new TempList(); + for(ProductType x: entries) if (x.types.length == 2) ee.add(x); + return make(false, ee.makeConst(), (1<<2)); + } + + /** Returns a new type { A | A is binary and ~A is in this } + * + *

ReturnValue.is_int == false + *
ReturnValue.is_bool == false + * + *

If this.size()==0, or does not contain any binary ProductType entries, then result.size()==0 + */ + public Type transpose() { + if ((arities & (1<<2))==0) return EMPTY; + TempList ee=new TempList(); + int aa=0; + for(ProductType a:this) if (a.types.length==2) aa=add(ee, aa, a.transpose()); + return make(false, ee.makeConst(), aa); + } + + /** Returns true if for all A in this, there exists B in that, where A is equal or subset of B. + *

Note: if this.is_int && !that.is_int, we return false immediately. + *

Note: if this.is_bool && !that.is_bool, we return false immediately. + *

Note: if this nothing above is violated, and this type has no relational entry in it, we return true. + */ + public boolean isSubtypeOf(Type that) { + if (this==that) return true; + if (is_int() && !that.is_int()) return false; + if (is_bool && !that.is_bool) return false; + List> those = that.fold(); + again: + for(ProductType a: this) { + for(List b: those) if (a.arity()==b.size() && a.isSubtypeOf(b)) continue again; + return false; + } + return true; + } + + /** Returns a new type { A.B | exists A in this, exists B in that, where A.arity+B.arity>2 } + *

If this.size()==0, or that.size()==0, or none of the entries have the right arity, then result.size()==0. + * + *

ReturnValue.is_int == false + *
ReturnValue.is_bool == false + */ + public Type join(Type that) { + if (size()==0 || that.size()==0) return EMPTY; + TempList ee=new TempList(); + int aa=0; + for (ProductType a:this) for (ProductType b:that) if (a.types.length>1 || b.types.length>1) aa=add(ee, aa, a.join(b)); + return make(false, ee.makeConst(), aa); + } + + /** Returns a new type { R[0]->..->R[n-1] | + * exists n-ary A in this, exists unary B in that, such that R[i]==A[i] except R[0]==(A[0] & B) + * + *

ReturnValue.is_int == false + *
ReturnValue.is_bool == false + * + *

If this.size()==0, or that does not contain any unary entry, then result.size()==0 + */ + public Type domainRestrict(Type that) { + if (size()==0 || (that.arities & (1<<1))==0) return EMPTY; + TempList ee=new TempList(); + int aa=0; + for (ProductType b:that) + if (b.types.length==1) + for (ProductType a:this) + aa = add(ee, aa, a.columnRestrict(b.types[0], 0)); + return make(false, ee.makeConst(), aa); + } + + /** Returns a new type { R[0]->..->R[n-1] | + * exists n-ary A in this, exists unary B in that, such that R[i]==A[i] except R[n-1]==(A[n-1] & B) + * + *

ReturnValue.is_int == false + *
ReturnValue.is_bool == false + * + *

If this.size()==0, or that does not contain any unary entry, then result.size()==0 + */ + public Type rangeRestrict(Type that) { + if (size()==0 || (that.arities & (1<<1))==0) return EMPTY; + TempList ee=new TempList(); + int aa=0; + for (ProductType b:that) + if (b.types.length==1) + for (ProductType a:this) + aa = add(ee, aa, a.columnRestrict(b.types[0], a.types.length-1)); + return make(false, ee.makeConst(), aa); + } + + /** Returns a new type { A | (A in this) and (A.arity == arity) } + * + *

ReturnValue.is_int == false + *
ReturnValue.is_bool == false + * + *

If it does not contain any entry with the given arity, then result.size()==0 + */ + public Type extract(int arity) { + final int aa = (arity>30 ? 1 : (1< ee=new TempList(); + for(ProductType x: entries) if (x.types.length == arity) ee.add(x); + return make(false, ee.makeConst(), aa); + } + + /** Returns a new type u + u.u + u.u.u + ... (where u == the set of binary entries in this type) + * + *

ReturnValue.is_int == false + *
ReturnValue.is_bool == false + * + *

If it does not contain any binary entries, then result.size()==0 + */ + public Type closure() { + Type ans=extract(2), u=ans, uu=u.join(u); + while(uu.hasTuple()) { + Type oldans=ans, olduu=uu; + ans=ans.unionWithCommonArity(uu); + uu=uu.join(u); + if (oldans==ans && olduu.equals(uu)) break; + // The special guarantee of unionWithCommonArity() allows us to use the cheaper "oldans==ans" here + // instead of doing the more expensive "oldans.equals(ans)" + } + return ans; + } + + /** Convert this type into a UNION of PRODUCT of sigs. + * @throws ErrorType if it does not contain exactly one arity + * @throws ErrorType if is_int is true + * @throws ErrorType if is_bool is true + */ + public Expr toExpr() throws Err { + int arity = arity(); + if (is_bool || arity<1) + throw new ErrorType("Cannot convert this type into a bounding expression."); + Expr ans = null; + for(ProductType pt:this) { + Expr pro = null; + for(int i=0; i If {a}+this.entries contain a set of entries X1..Xn, such that + *
(1) For each X: X[j]==a[j] for i!=j, and X[i].super==a[i].super + *
(2) X1[i]..Xn[i] exhaust all the direct subsignatures of an abstract parent sig + *
THEN: + *
we remove X1..Xn, then return the merged result of X1..Xn + *
ELSE + *
we change nothing, and simply return null + * + *

Precondition: a[i] is not NONE, and a[i].parent is abstract, and a[i].parent!=UNIV + */ + private static List fold(ArrayList> entries, List a, int i) { + PrimSig parent = a.get(i).parent; + SafeList children; + try { children=parent.children(); } catch(Err ex) { return null; } // Exception only occurs if a[i].parent==UNIV + List subs = children.makeCopy(); + ArrayList> toDelete = new ArrayList>(); + for(int bi=entries.size()-1; bi>=0; bi--) { + List b=entries.get(bi); + if (b.size() == a.size()) { + for(int j=0; ;j++) { + if (j>=b.size()) {toDelete.add(b); subs.remove(b.get(i)); break;} + PrimSig bt1=a.get(j), bt2=b.get(j); + if (i==j && bt2.parent!=parent) break; + if (i!=j && bt2!=bt1) break; + } + } + } + subs.remove(a.get(i)); + if (subs.size()!=0) return null; + entries.removeAll(toDelete); + entries.remove(a); + a=new ArrayList(a); + a.set(i, parent); + return a; + } + + /** Return the result of folding this Type (that is, whenever a subset of relations are identical + * except for 1 position, where together they comprise of all direct subsigs of an abstract sig, + * then we merge them) + * + *

Note: the result is only current with respect to the current existing sig objects + */ + public List> fold() { + ArrayList> e = new ArrayList>(); + for(ProductType xx: entries) { + List x = Arrays.asList(xx.types); + while(true) { + int n = x.size(); + boolean changed = false; + for(int i=0; i folded = fold(e, x, i); + if (folded!=null) {x=folded; changed=true; i--;} + } + } + if (changed==false) break; + } + e.add(x); + } + return e; + } + + /** Returns a human-readable description of this type. */ + @Override public String toString() { + boolean first=true; + StringBuilder ans=new StringBuilder("{"); + //[AM] if (is_int()) { first=false; ans.append("PrimitiveInteger"); } + if (is_bool) { if (!first) ans.append(", "); first=false; ans.append("PrimitiveBoolean"); } + for(List r:fold()) { + if (!first) ans.append(", "); + first=false; + for(int i=0; i"); ans.append(r.get(i)); } + } + return ans.append('}').toString(); + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/ast/VisitQuery.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/ast/VisitQuery.java new file mode 100644 index 00000000..555ca6df --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/ast/VisitQuery.java @@ -0,0 +1,101 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4compiler.ast; + +import edu.mit.csail.sdg.alloy4.Err; +import edu.mit.csail.sdg.alloy4compiler.ast.Sig.Field; + +/** This abstract class implements a Query visitor that walks over an Expr and its subnodes. + *
As soon as one of the node returns a nonnull value,the nonnull value will be propagated to be the output of this visitor. + * + *

This default implementation will return null on all the leaf Expr nodes and thus the final answer will be null. + *
To implement a particular query, you need to extend this class. + */ + +public abstract class VisitQuery extends VisitReturn { + + /** Constructs a VisitQuery object. */ + public VisitQuery() { } + + /** Visits an ExprBinary node (A OP B) by calling accept() on A then B. */ + @Override public T visit(ExprBinary x) throws Err { + T ans=x.left.accept(this); + if (ans==null) ans=x.right.accept(this); + return ans; + } + + /** Visits an ExprList node F[X1,X2,X3..] by calling accept() on X1, X2, X3... */ + @Override public T visit(ExprList x) throws Err { + for(Expr y:x.args) { T ans=y.accept(this); if (ans!=null) return ans; } + return null; + } + + /** Visits an ExprCall node F[X1,X2,X3..] by calling accept() on X1, X2, X3... */ + @Override public T visit(ExprCall x) throws Err { + for(Expr y:x.args) { T ans=y.accept(this); if (ans!=null) return ans; } + return null; + } + + /** Visits an ExprConstant node (this default implementation simply returns null) */ + @Override public T visit(ExprConstant x) throws Err { + return null; + } + + /** Visits an ExprITE node (C => X else Y) by calling accept() on C, X, then Y. */ + @Override public T visit(ExprITE x) throws Err { + T ans = x.cond.accept(this); + if (ans==null) ans = x.left.accept(this); + if (ans==null) ans = x.right.accept(this); + return ans; + } + + /** Visits an ExprLet node (let a=x | y) by calling accept() on "a", "x", then "y". */ + @Override public T visit(ExprLet x) throws Err { + T ans = x.var.accept(this); + if (ans==null) ans = x.expr.accept(this); + if (ans==null) ans = x.sub.accept(this); + return ans; + } + + /** Visits an ExprQt node (all a,b,c:X1, d,e,f:X2... | F) by calling accept() on a,b,c,X1,d,e,f,X2... then on F. */ + @Override public T visit(ExprQt x) throws Err { + for(Decl d: x.decls) { + for(ExprHasName v: d.names) { T ans = v.accept(this); if (ans!=null) return ans; } + T ans = d.expr.accept(this); if (ans!=null) return ans; + } + return x.sub.accept(this); + } + + /** Visits an ExprUnary node (OP X) by calling accept() on X. */ + @Override public T visit(ExprUnary x) throws Err { + return x.sub.accept(this); + } + + /** Visits a ExprVar node (this default implementation simply returns null) */ + @Override public T visit(ExprVar x) throws Err { + return null; + } + + /** Visits a Sig node (this default implementation simply returns null) */ + @Override public T visit(Sig x) throws Err { + return null; + } + + /** Visits a Field node (this default implementation simply returns null) */ + @Override public T visit(Field x) throws Err { + return null; + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/ast/VisitReturn.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/ast/VisitReturn.java new file mode 100644 index 00000000..8192484e --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/ast/VisitReturn.java @@ -0,0 +1,72 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4compiler.ast; + +import edu.mit.csail.sdg.alloy4.Err; +import edu.mit.csail.sdg.alloy4compiler.ast.Sig.Field; + +/** This abstract class defines what a Return Visitor's interface needs to be. */ + +public abstract class VisitReturn { + + /** Constructs a VisitReturn object. */ + public VisitReturn() { } + + /** This is the start method that begins a traversal over the given expression. */ + public final T visitThis(Expr x) throws Err { return x.accept(this); } + + /** Visits a ExprBad node */ + public T visit(ExprBad x) throws Err { throw x.errors.pick(); } + + /** Visits a ExprBadCall node */ + public T visit(ExprBadCall x) throws Err { throw x.errors.pick(); } + + /** Visits a ExprBadJoin node */ + public T visit(ExprBadJoin x) throws Err { throw x.errors.pick(); } + + /** Visits an ExprBinary node. */ + public abstract T visit(ExprBinary x) throws Err; + + /** Visits an ExprList node. */ + public abstract T visit(ExprList x) throws Err; + + /** Visits an ExprCall node. */ + public abstract T visit(ExprCall x) throws Err; + + /** Visits an ExprConstant node. */ + public abstract T visit(ExprConstant x) throws Err; + + /** Visits an ExprITE node. */ + public abstract T visit(ExprITE x) throws Err; + + /** Visits an ExprLet node. */ + public abstract T visit(ExprLet x) throws Err; + + /** Visits an ExprQt node. */ + public abstract T visit(ExprQt x) throws Err; + + /** Visits an ExprUnary node. */ + public abstract T visit(ExprUnary x) throws Err; + + /** Visits an ExprVar node. */ + public abstract T visit(ExprVar x) throws Err; + + /** Visits a Sig node. */ + public abstract T visit(Sig x) throws Err; + + /** Visits a Field node. */ + public abstract T visit(Field x) throws Err; +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/ast/package.html b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/ast/package.html new file mode 100644 index 00000000..b5f3ab20 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/ast/package.html @@ -0,0 +1,5 @@ + + +This package contains the definition of AST nodes. + + diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/parser/Alloy.cup b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/parser/Alloy.cup new file mode 100644 index 00000000..1796efb0 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/parser/Alloy.cup @@ -0,0 +1,1038 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2008, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* + * Warning: this file alone is not enough to correctly parse Alloy4 since the actual + * language is not LALR(1); instead, we have to pre-process the token stream + * using CompFilter.java to rearrange/transform the token stream, and then we can + * parse the transformed token stream using this LALR(1) grammar. For more information, + * please refer to CompFilter.java + */ + +import java.util.Stack; +import java.util.List; +import java.util.ArrayList; +import java.util.TreeSet; +import java.util.Map; +import java.util.LinkedHashMap; +import java.io.BufferedReader; +import java.io.FileNotFoundException; +import java.io.Reader; +import java.io.IOException; +import java.io.StringReader; +import java_cup.runtime.*; +import edu.mit.csail.sdg.alloy4.Err; +import edu.mit.csail.sdg.alloy4.ErrorFatal; +import edu.mit.csail.sdg.alloy4.ErrorSyntax; +import edu.mit.csail.sdg.alloy4.Pos; +import edu.mit.csail.sdg.alloy4.Pair; +import edu.mit.csail.sdg.alloy4.Util; +import edu.mit.csail.sdg.alloy4.Version; +import edu.mit.csail.sdg.alloy4compiler.ast.Attr.AttrType; +import edu.mit.csail.sdg.alloy4compiler.ast.CommandScope; +import edu.mit.csail.sdg.alloy4compiler.ast.Decl; +import edu.mit.csail.sdg.alloy4compiler.ast.Expr; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprBadJoin; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprITE; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprLet; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprBinary; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprList; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprConstant; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprQt; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprUnary; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprVar; +import edu.mit.csail.sdg.alloy4compiler.ast.Sig; +import edu.mit.csail.sdg.alloy4compiler.ast.Sig.PrimSig; + +//===========================================================================// + +parser code {: + + public CompModule alloymodule=null; + + @Override public Symbol parse() throws java.lang.Exception { + int act; // current action code + Symbol lhs_sym = null; // the Symbol/stack element returned by a reduce + short handle_size, lhs_sym_num; // information about production being reduced with + boolean logging = "yes".equals(System.getProperty("debug")); + production_tab = production_table(); + action_tab = action_table(); + reduce_tab = reduce_table(); + init_actions(); + user_init(); + // start + cur_token = scan(); + stack.removeAllElements(); + stack.push(getSymbolFactory().startSymbol("START", 0, start_state())); + tos = 0; + for (_done_parsing = false; !_done_parsing; ) { + act = get_action(((Symbol)stack.peek()).parse_state, cur_token.sym); + if (act > 0) { // "shift"; thus, we shift to the encoded state by pushing it on the stack + if (logging) System.out.println("shift " + cur_token.sym); + cur_token.parse_state = act-1; + stack.push(cur_token); + tos++; + cur_token = scan(); + } else if (act<0) { // "reduce" + if (logging) System.out.println("reduce " + ((-act)-1)); + lhs_sym = do_action((-act)-1, this, stack, tos); + lhs_sym_num = production_tab[(-act)-1][0]; + handle_size = production_tab[(-act)-1][1]; + for (int i = 0; i < handle_size; i++) { stack.pop(); tos--; } + act = get_reduce(((Symbol)stack.peek()).parse_state, lhs_sym_num); + lhs_sym.parse_state = act; + stack.push(lhs_sym); + tos++; + } else { // "error" + if (logging) System.out.println("error"); + syntax_error(cur_token); + done_parsing(); + } + } + return lhs_sym; + } + + public void syntax_error(Symbol x) throws Err { + Map ch = new LinkedHashMap(); + ch.put(CompSym.ARROW, "->"); + ch.put(CompSym.ANY_ARROW_SOME, "->"); + ch.put(CompSym.ANY_ARROW_ONE, "->"); + ch.put(CompSym.ANY_ARROW_LONE, "->"); + ch.put(CompSym.SOME_ARROW_ANY, "some"); + ch.put(CompSym.SOME_ARROW_SOME, "some"); + ch.put(CompSym.SOME_ARROW_ONE, "some"); + ch.put(CompSym.SOME_ARROW_LONE, "some"); + ch.put(CompSym.ONE_ARROW_ANY, "one"); + ch.put(CompSym.ONE_ARROW_SOME, "one"); + ch.put(CompSym.ONE_ARROW_ONE, "one"); + ch.put(CompSym.ONE_ARROW_LONE, "one"); + ch.put(CompSym.LONE_ARROW_ANY, "lone"); + ch.put(CompSym.LONE_ARROW_SOME, "lone"); + ch.put(CompSym.LONE_ARROW_ONE, "lone"); + ch.put(CompSym.LONE_ARROW_LONE, "lone"); + ch.put(CompSym.INTADD, "fun"); + ch.put(CompSym.INTSUB, "fun"); + ch.put(CompSym.INTMUL, "fun"); + ch.put(CompSym.INTDIV, "fun"); + ch.put(CompSym.INTREM, "fun"); + ch.put(CompSym.INTMIN, "fun"); + ch.put(CompSym.INTMAX, "fun"); + ch.put(CompSym.INTNEXT, "fun"); + ch.put(CompSym.TOTALORDER, "pred"); + ch.put(CompSym.ABSTRACT, "abstract"); + ch.put(CompSym.ALL, "all"); + ch.put(CompSym.ALL2, "all"); + ch.put(CompSym.AMPERSAND, "&"); + ch.put(CompSym.AND, "&&"); + ch.put(CompSym.AS, "as"); + ch.put(CompSym.ASSERT, "assert"); + ch.put(CompSym.AT, "@"); + ch.put(CompSym.BAR, "|"); + ch.put(CompSym.BUT, "but"); + ch.put(CompSym.CARET, "^"); + ch.put(CompSym.CHECK, "check"); + ch.put(CompSym.COLON, ":"); + ch.put(CompSym.COMMA, ", "); + ch.put(CompSym.DISJ, "disj"); + ch.put(CompSym.DOMAIN, "<:"); + ch.put(CompSym.DOT, "."); + ch.put(CompSym.ELSE, "else"); + ch.put(CompSym.ENUM, "enum"); + ch.put(CompSym.EQUALS, "="); + ch.put(CompSym.EXACTLY, "exactly"); + ch.put(CompSym.EXH, "exh"); + ch.put(CompSym.EXPECT, "expect"); + ch.put(CompSym.EXTENDS, "extends"); + ch.put(CompSym.FACT, "fact"); + ch.put(CompSym.FOR, "for"); + ch.put(CompSym.FUN, "fun"); + ch.put(CompSym.GT, ">"); + ch.put(CompSym.GTE, ">="); + ch.put(CompSym.HASH, "#"); + ch.put(CompSym.IDEN, "iden"); + ch.put(CompSym.IFF, "iff"); + ch.put(CompSym.IMPLIES, "=>"); + ch.put(CompSym.IN, "in"); + ch.put(CompSym.INT, "int"); + ch.put(CompSym.LBRACE, "{"); + ch.put(CompSym.LBRACKET, "["); + ch.put(CompSym.LET, "let"); + ch.put(CompSym.LONE2, "lone"); + ch.put(CompSym.LONE, "lone"); + ch.put(CompSym.LPAREN, "("); + ch.put(CompSym.LT, "<"); + ch.put(CompSym.LTE, "<="); + ch.put(CompSym.MINUS, "-"); + ch.put(CompSym.MODULE, "module"); + ch.put(CompSym.NO2, "no"); + ch.put(CompSym.NO, "no"); + ch.put(CompSym.NONE, "none"); + ch.put(CompSym.NOT, "!"); + ch.put(CompSym.NOTEQUALS, "!"); + ch.put(CompSym.NOTGT, "!"); + ch.put(CompSym.NOTGTE, "!"); + ch.put(CompSym.NOTIN, "!"); + ch.put(CompSym.NOTLT, "!"); + ch.put(CompSym.NOTLTE, "!"); + ch.put(CompSym.ONE2, "one"); + ch.put(CompSym.ONE, "one"); + ch.put(CompSym.OPEN, "open"); + ch.put(CompSym.OR, "||"); + ch.put(CompSym.PART, "part"); + ch.put(CompSym.PLUS, "+"); + ch.put(CompSym.PLUSPLUS, "++"); + ch.put(CompSym.PRED, "pred"); + ch.put(CompSym.PRIVATE, "private"); + ch.put(CompSym.RANGE, ":>"); + ch.put(CompSym.RBRACE, "}"); + ch.put(CompSym.RBRACKET, "]"); + ch.put(CompSym.RPAREN, ")"); + ch.put(CompSym.RUN, "run"); + ch.put(CompSym.SEQ, "seq"); + ch.put(CompSym.SET, "set"); + ch.put(CompSym.SHL, "<<"); + ch.put(CompSym.SHR, ">>>"); + ch.put(CompSym.SHA, ">>"); + ch.put(CompSym.SIG, "sig"); + ch.put(CompSym.SIGINT, "Int"); + ch.put(CompSym.SLASH, "/"); + ch.put(CompSym.SOME2, "some"); + ch.put(CompSym.SOME, "some"); + ch.put(CompSym.STAR, "*"); + ch.put(CompSym.STRING, "String"); + ch.put(CompSym.SUM2, "sum"); + ch.put(CompSym.SUM, "sum"); + ch.put(CompSym.THIS, "this"); + ch.put(CompSym.TILDE, "~"); + ch.put(CompSym.UNIV, "univ"); + ch.put(CompSym.ID, "NAME"); + ch.put(CompSym.NUMBER, "NUMBER"); + ch.put(CompSym.STR, "STRING"); + TreeSet list = new TreeSet(); + Pos p=Pos.UNKNOWN; + if (x!=null && x.value instanceof Pos) p=(Pos)(x.value); + else if (x!=null && x.value instanceof Expr) p=((Expr)(x.value)).pos; + else if (x!=null) p=x.pos; + if (!stack.empty()) for(Map.Entry e:ch.entrySet()) { + int key=e.getKey(), act=get_action(((Symbol)stack.peek()).parse_state, key); + if (act==0) continue; + try { + if (act>0 || alloy_confirm(key)) list.add(e.getValue()); + } catch(Throwable ex) { + // If the parser is really really confused, alloy_confirm() could fail with array out-of-bound exception, etc. + } + } + String result="There are "+list.size()+" possible tokens that can appear here:\n"; + for(String item:list) result=result+item+" "; + throw new ErrorSyntax(p, (list.size()!=0)?result:""); + } + + private boolean alloy_confirm(int key) { + int state = ((Symbol)stack.peek()).parse_state; + Stack newstack=new Stack(); for(Object x:stack) newstack.push(x); + while(true) { + int act = get_action(state, key); + if (act>0) return true; + if (act==0) return false; + int lhs_sym_num = production_tab[(-act)-1][0]; + int handle_size = production_tab[(-act)-1][1]; + for (int i = 0; i < handle_size; i++) { if (newstack.empty()) return false; newstack.pop(); } + if (newstack.empty()) return false; + if (newstack.peek() instanceof Symbol) state=((Symbol)newstack.peek()).parse_state; + state=get_reduce(state, lhs_sym_num); + newstack.push(null); + } + } + + static CompModule alloy_parseStream (List seenDollar, + Map loaded, Map fc, CompModule root, + int lineOffset, String filename, String prefix, int initialResolutionMode) throws Err, FileNotFoundException, IOException { + Reader isr=null; + try { + if (root==null && prefix.length()!=0) throw new ErrorFatal("Internal error (parse subfile with root==null)"); + if (root!=null && prefix.length()==0) throw new ErrorFatal("Internal error (parse topfile with root!=null)"); + CompModule u = new CompModule(root, filename, prefix); + if (root == null) + u.addOpen(null, null, ExprVar.make(null, "util/integer"), null, ExprVar.make(null, "integer")); + u.resolution = initialResolutionMode; + String content = fc!=null ? fc.get(filename) : null; + if (content==null && loaded!=null) content = loaded.get(filename); + if (content==null) content = Util.readAll(filename); + if (loaded!=null) loaded.put(filename,content); + content = Util.convertLineBreak(content); + isr = new StringReader(content); + CompFilter s = new CompFilter(u, seenDollar, filename, lineOffset, new BufferedReader(isr)); + CompParser p = new CompParser(s); + p.alloymodule=u; + try {p.parse();} catch(Throwable ex) {if (ex instanceof Err) throw (Err)ex; throw new ErrorFatal("Parser Exception", ex);} + // if no sigs are defined by the user, add one + if (root == null && u.getAllSigs().isEmpty()) { + u.addGhostSig(); + } + return u; + } finally { + Util.close(isr); + } + } + +:}; + +action code {: + /** This function is needed to handle a difficult parsing ambiguity. + * + *

+ * "some EXPR", "one EXPR", and "lone EXPR" + * can be either formulas (saying the EXPR has at least 1, exactly 1, or at most 1 tuple), + * or multiplicity constraints (saying something else has this multiplicity). + * + *

+ * So we let the parser generate the former by default. + * And whenever we construct a Decl "x: y" object, + * or an binary expression "x in y", or a function return type, + * we call this method on y to convert it into a multiplicity constraint. + * + *

+ * This is safe, because in all 3 cases, a formula would be illegal. + * So the first form is always wrong. + * + *

+ * And this is sufficient, because those are the only 3 places + * where a mulitplicity constraint is allowed to appear. + * + * @return a newly formed multiplciity constraint (if this.op==SOME or LONE or ONE), + * otherwise it just returns the original node. + */ + private Expr mult(Expr x) throws Err { + if (x instanceof ExprUnary) { + ExprUnary y=(ExprUnary)x; + if (y.op==ExprUnary.Op.SOME) return ExprUnary.Op.SOMEOF.make(y.pos, y.sub); + if (y.op==ExprUnary.Op.LONE) return ExprUnary.Op.LONEOF.make(y.pos, y.sub); + if (y.op==ExprUnary.Op.ONE) return ExprUnary.Op.ONEOF.make(y.pos, y.sub); + } + return x; + } + private void nod(ExprVar name) throws Err { + if (name.label.indexOf('$')>=0) throw new ErrorSyntax(name.pos, "The name cannot contain the '$' symbol."); + } + private void nod(List names) throws Err { + if (names!=null) for(ExprVar n:names) if (n!=null && n.label.indexOf('$')>=0) throw new ErrorSyntax(n.pos, "The name cannot contain the '$' symbol."); + } + private void c(boolean follow, ExprVar o, ExprVar x, ExprVar n, Expr e, List s, ExprConstant c) throws Err { + if (n!=null) nod(n); + int bitwidth=(-1), maxseq=(-1), overall=(-1), expects=(c==null ? -1 : c.num); + Pos p = o.pos.merge(n!=null ? n.span() : e.span()); + for(int i=s.size()-1; i>=0; i--) { + Sig j=s.get(i).sig; int k=s.get(i).startingScope; + p=p.merge(j.pos); + if (j.label.equals("univ")) { overall=k; s.remove(i); continue; } + if (j.label.equals("int")) { if (bitwidth>=0) throw new ErrorSyntax(j.pos, "The bitwidth cannot be specified more than once."); bitwidth=k; s.remove(i); continue; } + if (j.label.equals("seq")) { if (maxseq>=0) throw new ErrorSyntax(j.pos, "The maximum sequence length cannot be specified more than once."); maxseq=k; s.remove(i); continue; } + } + if (n!=null) + parser.alloymodule.addCommand(follow, p, n.label, o.label.equals("c"), overall, bitwidth, maxseq, expects, s, x); + else + parser.alloymodule.addCommand(follow, p, e, o.label.equals("c"), overall, bitwidth, maxseq, expects, s, x); + } + private Expr t(Pos pos, Pos oldClosing, Expr left, Expr right, Pos close) throws Err { + if (right instanceof ExprVar) { + String n = ((ExprVar)right).label; + if (n.equals("int")) return ExprUnary.Op.CAST2INT.make(pos, left); + if (n.equals("disj")) return ExprList.makeDISJOINT(pos, close, Util.asList(left)); + if (n.equals("pred/totalOrder")) return ExprList.makeTOTALORDER(pos, close, Util.asList(left)); + } + else if (right instanceof ExprList) { + return ((ExprList)right).addArg(left); + } + return ExprBadJoin.make(pos, oldClosing, left, right); + } +:}; + +//===========================================================================// + +terminal Pos ARROW; // -> +terminal Pos ANY_ARROW_SOME; // ->some // The filter allows whitespace/comment in these 15 "*->*" tokens +terminal Pos ANY_ARROW_ONE; // ->one // The filter allows whitespace/comment in these 15 "*->*" tokens +terminal Pos ANY_ARROW_LONE; // ->lone // The filter allows whitespace/comment in these 15 "*->*" tokens +terminal Pos SOME_ARROW_ANY; // some-> // The filter allows whitespace/comment in these 15 "*->*" tokens +terminal Pos SOME_ARROW_SOME; // some->some // The filter allows whitespace/comment in these 15 "*->*" tokens +terminal Pos SOME_ARROW_ONE; // some->one // The filter allows whitespace/comment in these 15 "*->*" tokens +terminal Pos SOME_ARROW_LONE; // some->lone // The filter allows whitespace/comment in these 15 "*->*" tokens +terminal Pos ONE_ARROW_ANY; // one-> // The filter allows whitespace/comment in these 15 "*->*" tokens +terminal Pos ONE_ARROW_SOME; // one->some // The filter allows whitespace/comment in these 15 "*->*" tokens +terminal Pos ONE_ARROW_ONE; // one->one // The filter allows whitespace/comment in these 15 "*->*" tokens +terminal Pos ONE_ARROW_LONE; // one->lone // The filter allows whitespace/comment in these 15 "*->*" tokens +terminal Pos LONE_ARROW_ANY; // lone->any // The filter allows whitespace/comment in these 15 "*->*" tokens +terminal Pos LONE_ARROW_SOME; // lone->some // The filter allows whitespace/comment in these 15 "*->*" tokens +terminal Pos LONE_ARROW_ONE; // lone->one // The filter allows whitespace/comment in these 15 "*->*" tokens +terminal Pos LONE_ARROW_LONE; // lone->lone // The filter allows whitespace/comment in these 15 "*->*" tokens + +terminal Pos INTADD; +terminal Pos INTSUB; +terminal Pos INTMUL; +terminal Pos INTDIV; +terminal Pos INTREM; +terminal Pos INTMIN; +terminal Pos INTMAX; +terminal Pos INTNEXT; +terminal Pos TOTALORDER; + +terminal Pos ABSTRACT; // abstract +terminal Pos ALL; // all // The filter enables us to disambiguate +terminal Pos ALL2; // all // The filter enables us to disambiguate +terminal Pos AMPERSAND; // & +terminal Pos AND; // && and +terminal Pos AS; // as +terminal Pos ASSERT; // assert +terminal Pos AT; // @ +terminal Pos BAR; // | +terminal Pos BUT; // but +terminal Pos CARET; // ^ +terminal Pos CHECK; // check +terminal Pos COLON; // : +terminal Pos COMMA; // , +terminal Pos DISJ; // disj disjoint +terminal Pos DOMAIN; // <: +terminal Pos DOT; // . +terminal Pos ELSE; // else +terminal Pos ENUM; // enum +terminal Pos EQUALS; // = == +terminal Pos EXACTLY; // exactly +terminal Pos EXH; // exh exhaustive +terminal Pos EXPECT; // expect +terminal Pos EXTENDS; // extends +terminal Pos FACT; // fact +terminal Pos FOR; // for +terminal Pos FUN; // fun +terminal Pos GT; // > +terminal Pos GTE; // >= +terminal Pos HASH; // # +terminal Pos IDEN; // iden +terminal Pos IFF; // <=> iff +terminal Pos IMPLIES; // => implies +terminal Pos IN; // in +terminal Pos INT; // int +terminal Pos LBRACE; // { +terminal Pos LBRACKET; // [ +terminal Pos LET; // let +terminal Pos LONE2; // lone // The filter enables us to disambiguate +terminal Pos LONE; // lone // The filter enables us to disambiguate +terminal Pos LPAREN; // ( +terminal Pos LT; // < +terminal Pos LTE; // <= =< +terminal Pos MINUS; // - +terminal Pos MODULE; // module +terminal Pos NO2; // no // The filter enables us to disambiguate +terminal Pos NO; // no // The filter enables us to disambiguate +terminal Pos NONE; // none +terminal Pos NOT; // ! not +terminal Pos NOTEQUALS; // != not= // The filter allows whitespace/comment in between +terminal Pos NOTGT; // !> not> // The filter allows whitespace/comment in between +terminal Pos NOTGTE; // !>= not>= // The filter allows whitespace/comment in between +terminal Pos NOTIN; // !in notin // The filter allows whitespace/comment in between +terminal Pos NOTLT; // !< not< // The filter allows whitespace/comment in between +terminal Pos NOTLTE; // !=< not=< // The filter allows whitespace/comment in between +terminal Pos ONE2; // one // The filter enables us to disambiguate +terminal Pos ONE; // one // The filter enables us to disambiguate +terminal Pos OPEN; // open +terminal Pos OR; // || or +terminal Pos PART; // part partition +terminal Pos PLUS; // + +terminal Pos PLUSPLUS; // ++ +terminal Pos PRED; // pred +terminal Pos PRIVATE; // private +terminal Pos RANGE; // :> +terminal Pos RBRACE; // } +terminal Pos RBRACKET; // ] +terminal Pos RPAREN; // ) +terminal Pos RUN; // run +terminal Pos SEQ; // seq +terminal Pos SET; // set +terminal Pos SHL; // << +terminal Pos SHR; // >>> +terminal Pos SHA; // >> +terminal Pos SIG; // sig +terminal Pos SIGINT; // Int +terminal Pos SLASH; // / +terminal Pos SOME2; // some // The filter enables us to disambiguate +terminal Pos SOME; // some // The filter enables us to disambiguate +terminal Pos STAR; // * +terminal Pos STRING; // String +terminal Pos SUM2; // sum // The filter enables us to disambiguate +terminal Pos SUM; // sum // The filter enables us to disambiguate +terminal Pos THIS; // this +terminal Pos TILDE; // ~ +terminal Pos UNIV; // univ + +terminal ExprVar ID; + +terminal ExprConstant NUMBER, STR; + +//===========================================================================// + +nonterminal Expr AndExprA; +nonterminal Expr AndExprB; +nonterminal Expr BaseExpr; +nonterminal Expr Bind; +nonterminal Expr BracketExprA; +nonterminal Expr BracketExprB; +nonterminal Expr CompareExprA; +nonterminal Expr CompareExprB; +nonterminal Command; +nonterminal ExprVar CommandPrefix; +nonterminal Decl Decla; +nonterminal Decl Declb; +nonterminal List Declp; +nonterminal List Decls; +nonterminal List Declz; +nonterminal Expr DomainExprA; +nonterminal Expr DomainExprB; +nonterminal Expr DotExprA; +nonterminal Expr DotExprB; +nonterminal Expr EquivExprA; +nonterminal Expr EquivExprB; +nonterminal ExprConstant Expects; +nonterminal Expr Expr; +nonterminal Expr Super; +nonterminal Expr SuperOpt; +nonterminal Expr SuperP; +nonterminal Expr SuperOrBar; +nonterminal List Exprs; +nonterminal List Exprp; +nonterminal Function; +nonterminal Expr ImpliesExprA; +nonterminal Expr ImpliesExprB; +nonterminal Expr ImpliesExprCloseA; +nonterminal Expr ImpliesExprCloseB; +nonterminal Expr ImpliesExprOpenA; +nonterminal Expr ImpliesExprOpenB; +nonterminal Expr IntersectExprA; +nonterminal Expr IntersectExprB; +nonterminal Expr Let; +nonterminal Macro; +nonterminal Expr MacroBody; +nonterminal ExprVar Name; +nonterminal ExprVar NameHelper; +nonterminal List Names; +nonterminal List Namex; +nonterminal Expr NegExprA; +nonterminal Expr NegExprB; +nonterminal Expr NumUnopExprA; +nonterminal Expr NumUnopExprB; +nonterminal Expr OrExprA; +nonterminal Expr OrExprB; +nonterminal Expr OverrideExprA; +nonterminal Expr OverrideExprB; +nonterminal Predicate; +nonterminal Expr RangeExprA; +nonterminal Expr RangeExprB; +nonterminal Pair RelOp; +nonterminal Expr RelationExprA; +nonterminal Expr RelationExprB; +nonterminal List Scope; +nonterminal Sig; +nonterminal List SigIn; +nonterminal List SigQual; +nonterminal List SigQuals; +nonterminal ExprVar SigRef; +nonterminal List SigRefp; +nonterminal List SigRefs; +nonterminal List SigRefu; +nonterminal File; +nonterminal Spec; +nonterminal CommandScope TypeNumber; +nonterminal CommandScope Typescope; +nonterminal List Typescopes; +nonterminal Expr ShiftExprA; +nonterminal Expr ShiftExprB; +nonterminal Expr MulExprA; +nonterminal Expr MulExprB; +nonterminal Expr UnionDiffExprA; +nonterminal Expr UnionDiffExprB; +nonterminal Expr UnopExprA; +nonterminal Expr UnopExprB; +nonterminal Pos Vis; + +//===========================================================================// + +File ::= Spec {: parser.alloymodule.doneParsing(); :}; + +Spec ::= Spec MODULE:o Name:n {: nod(n); parser.alloymodule.addModelName(o.merge(n.pos) , n.label , new ArrayList()); :}; +Spec ::= Spec MODULE:o Name:n LBRACKET Namex:b RBRACKET:r {: nod(n); nod(b); parser.alloymodule.addModelName(o.merge(r) , n.label , b ); :}; +Spec ::= Spec Vis:p OPEN:o Name:a {: nod(a); parser.alloymodule.addOpen(o.merge(a.pos), p, a, null, null); :}; +Spec ::= Spec Vis:p OPEN:o Name:a AS Name:c {: nod(a); nod(c); parser.alloymodule.addOpen(o.merge(c.pos), p, a, null, c); :}; +Spec ::= Spec Vis:p OPEN:o Name:a LBRACKET SigRefs:b RBRACKET:c {: nod(a); parser.alloymodule.addOpen(o.merge(c), p, a, b, null); :}; +Spec ::= Spec Vis:p OPEN:o Name:a LBRACKET SigRefs:b RBRACKET AS Name:c {: nod(a); nod(c); parser.alloymodule.addOpen(o.merge(c.pos), p, a, b, c); :}; +Spec ::= Spec Vis:p ENUM:o Name:a LBRACE Names:n RBRACE:c {: nod(a); parser.alloymodule.addEnum(o.merge(c), p, a, n, c); :}; +Spec ::= Spec Vis:p ENUM:o Name:a LBRACE RBRACE:c {: nod(a); parser.alloymodule.addEnum(o.merge(c), p, a, null, c); :}; +Spec ::= Spec FACT:o Super:e {: parser.alloymodule.addFact (o , "" , e); :}; +Spec ::= Spec FACT:o Name:n Super:e {: nod(n); parser.alloymodule.addFact (o , n.label , e); :}; +Spec ::= Spec FACT:o STR:n Super:e {: parser.alloymodule.addFact (o , n.string , e); :}; +Spec ::= Spec ASSERT:o Super:e {: parser.alloymodule.addAssertion (o , "" , e); :}; +Spec ::= Spec ASSERT:o Name:n Super:e {: nod(n); parser.alloymodule.addAssertion (o , n.label , e); :}; +Spec ::= Spec ASSERT:o STR:n Super:e {: parser.alloymodule.addAssertion (o , n.string , e); :}; +Spec ::= Spec Sig ; +Spec ::= Spec Function ; +Spec ::= Spec Predicate ; +Spec ::= Spec Macro ; +Spec ::= Spec Command ; +Spec ::= ; + +CommandPrefix ::= CHECK:c {: RESULT = ExprVar.make(c, "c"); :}; +CommandPrefix ::= RUN:r {: RESULT = ExprVar.make(r, "r"); :}; + +Command ::= CommandPrefix:o Name:x Super:e Scope:s Expects:c {: c(false,o,x ,null,e ,s,c); :}; +Command ::= CommandPrefix:o Super:e Scope:s Expects:c {: c(false,o,null,null,e ,s,c); :}; +Command ::= Command IMPLIES CommandPrefix:o Name:x Super:e Scope:s Expects:c {: c(true ,o,x ,null,e ,s,c); :}; +Command ::= Command IMPLIES CommandPrefix:o Super:e Scope:s Expects:c {: c(true ,o,null,null,e ,s,c); :}; +Command ::= CommandPrefix:o Name:x Name:n Scope:s Expects:c {: c(false,o,x ,n ,null,s,c); :}; +Command ::= CommandPrefix:o Name:n Scope:s Expects:c {: c(false,o,null,n ,null,s,c); :}; +Command ::= Command IMPLIES CommandPrefix:o Name:x Name:n Scope:s Expects:c {: c(true ,o,x ,n ,null,s,c); :}; +Command ::= Command IMPLIES CommandPrefix:o Name:n Scope:s Expects:c {: c(true ,o,null,n ,null,s,c); :}; + +Expects ::= {: RESULT=null; :}; +Expects ::= EXPECT NUMBER:a {: RESULT=a; :}; + +Scope ::= FOR NUMBER:a {: RESULT=new ArrayList(); RESULT.add(new CommandScope(a.pos, new PrimSig("univ", AttrType.WHERE.make(a.pos)), true, a.num, a.num, 1)); :}; +Scope ::= FOR NUMBER:a BUT Typescopes:b {: RESULT=b; b.add(new CommandScope(a.pos, new PrimSig("univ", AttrType.WHERE.make(a.pos)), true, a.num, a.num, 1)); :}; +Scope ::= FOR Typescopes:b {: RESULT=b; :}; +Scope ::= {: RESULT=new ArrayList(); :}; + +Typescopes ::= Typescope:a {: RESULT=new ArrayList(); RESULT.add(a); :}; +Typescopes ::= Typescopes:a COMMA Typescope:b {: RESULT=a; a.add(b); :}; + +Typescope ::= TypeNumber:a Name:b {: + nod(b); + RESULT = new CommandScope(a.pos.merge(b.pos), new PrimSig(b.label, AttrType.WHERE.make(a.pos.merge(b.pos))), a.isExact, a.startingScope, a.endingScope, a.increment); +:}; + +//[AM]: INT -> SIGINT +Typescope ::= TypeNumber:a SIGINT:b {: + Pos p = a.pos.merge(b); + if (a.endingScope>a.startingScope) throw new ErrorSyntax(p, "Cannot specify a growing scope for \"Int\""); + if (a.isExact) throw new ErrorSyntax(p, "The exactly keyword is redundant here since the integer bitwidth must be exact."); + RESULT = new CommandScope(p, new PrimSig("int", AttrType.WHERE.make(p)), a.isExact, a.startingScope, a.startingScope, 1); +:}; + +Typescope ::= TypeNumber:a INT:b {: + Pos p = a.pos.merge(b); + if (a.endingScope>a.startingScope) throw new ErrorSyntax(p, "Cannot specify a growing scope for \"Int\""); + if (a.isExact) throw new ErrorSyntax(p, "The exactly keyword is redundant here since the integer bitwidth must be exact."); + RESULT = new CommandScope(p, new PrimSig("int", AttrType.WHERE.make(p)), a.isExact, a.startingScope, a.startingScope, 1); +:}; + +Typescope ::= TypeNumber:a SEQ:b {: + Pos p = a.pos.merge(b); + if (a.endingScope>a.startingScope) throw new ErrorSyntax(p, "Cannot specify a growing scope for \"seq\""); + if (a.isExact) throw new ErrorSyntax(p, "The exactly keyword is redundant here since the number of sequence index has to be exact."); + RESULT = new CommandScope(p, new PrimSig("seq", AttrType.WHERE.make(p)), a.isExact, a.startingScope, a.startingScope, 1); +:}; + +Typescope ::= TypeNumber:e UNIV:f {: if (1==1) throw new ErrorSyntax(e.pos.merge(f), "You cannot set a scope on univ."); :}; + +Typescope ::= TypeNumber:a STRING:b {: RESULT = new CommandScope(a.pos.merge(b), new PrimSig("String", AttrType.WHERE.make(a.pos.merge(b))), a.isExact, a.startingScope, a.endingScope, a.increment); :}; + +//[AM] Typescope ::= TypeNumber:e SIGINT:f {: if (1==1) throw new ErrorSyntax(e.pos.merge(f), "You can no longer set a scope on Int; the number of Int atoms is always exactly equal to 2^(integer bitwidth).\n"); :}; + +Typescope ::= TypeNumber:e NONE:f {: if (1==1) throw new ErrorSyntax(e.pos.merge(f), "You cannot set a scope on none."); :}; + +TypeNumber ::= EXACTLY:e NUMBER:a {: RESULT = new CommandScope( e.merge(a.pos), Sig.NONE, true, a.num, a.num, 1 ); :}; +TypeNumber ::= EXACTLY:e NUMBER:a DOT DOT NUMBER:b {: if (!Version.experimental) throw new ErrorSyntax(a.pos, "Syntax error here."); RESULT = new CommandScope( e.merge(b.pos), Sig.NONE, true, a.num, b.num, 1 ); :}; +TypeNumber ::= EXACTLY:e NUMBER:a DOT DOT NUMBER:b COLON NUMBER:i {: if (!Version.experimental) throw new ErrorSyntax(a.pos, "Syntax error here."); RESULT = new CommandScope( e.merge(i.pos), Sig.NONE, true, a.num, b.num, i.num); :}; +TypeNumber ::= EXACTLY:e NUMBER:a COLON NUMBER:i {: if (!Version.experimental) throw new ErrorSyntax(a.pos, "Syntax error here."); RESULT = new CommandScope( e.merge(i.pos), Sig.NONE, true, a.num, Integer.MAX_VALUE, i.num); :}; +TypeNumber ::= NUMBER:a {: RESULT = new CommandScope(a.pos , Sig.NONE, false, a.num, a.num, 1 ); :}; +TypeNumber ::= NUMBER:a DOT DOT NUMBER:b {: if (!Version.experimental) throw new ErrorSyntax(a.pos, "Syntax error here."); RESULT = new CommandScope(a.pos.merge(b.pos), Sig.NONE, false, a.num, b.num, 1 ); :}; +TypeNumber ::= NUMBER:a DOT DOT NUMBER:b COLON NUMBER:i {: if (!Version.experimental) throw new ErrorSyntax(a.pos, "Syntax error here."); RESULT = new CommandScope(a.pos.merge(i.pos), Sig.NONE, false, a.num, b.num, i.num); :}; +TypeNumber ::= NUMBER:a COLON NUMBER:i {: if (!Version.experimental) throw new ErrorSyntax(a.pos, "Syntax error here."); RESULT = new CommandScope(a.pos.merge(i.pos), Sig.NONE, false, a.num, Integer.MAX_VALUE, i.num); :}; + +Macro ::= Vis:p LET:o Name:n LPAREN Names:d RPAREN MacroBody:v {: nod(n); parser.alloymodule.addMacro(o.merge(v.span()), p, n.label, d , v); :}; +Macro ::= Vis:p LET:o Name:n LPAREN RPAREN MacroBody:v {: nod(n); parser.alloymodule.addMacro(o.merge(v.span()), p, n.label, null , v); :}; +Macro ::= Vis:p LET:o Name:n LBRACKET Names:d RBRACKET MacroBody:v {: nod(n); parser.alloymodule.addMacro(o.merge(v.span()), p, n.label, d , v); :}; +Macro ::= Vis:p LET:o Name:n LBRACKET RBRACKET MacroBody:v {: nod(n); parser.alloymodule.addMacro(o.merge(v.span()), p, n.label, null , v); :}; +Macro ::= Vis:p LET:o Name:n MacroBody:v {: nod(n); parser.alloymodule.addMacro(o.merge(v.span()), p, n.label, null , v); :}; + +MacroBody ::= Super:a {: RESULT=a; :}; +MacroBody ::= EQUALS Expr:a {: RESULT=a; :}; + +Function ::= Vis:p FUN:o Name:n LPAREN Decls:d RPAREN COLON Expr:r Super:v {: nod(n); parser.alloymodule.addFunc(o.merge(v.span()), p, n.label, null, d , mult(r), v); :}; +Function ::= Vis:p FUN:o Name:n LBRACKET Decls:d RBRACKET COLON Expr:r Super:v {: nod(n); parser.alloymodule.addFunc(o.merge(v.span()), p, n.label, null, d , mult(r), v); :}; +Function ::= Vis:p FUN:o Name:n COLON Expr:r Super:v {: nod(n); parser.alloymodule.addFunc(o.merge(v.span()), p, n.label, null, null , mult(r), v); :}; +Function ::= Vis:p FUN:o SigRef:f DOT Name:n LPAREN Decls:d RPAREN COLON Expr:r Super:v {: nod(n); parser.alloymodule.addFunc(o.merge(v.span()), p, n.label, f , d , mult(r), v); :}; +Function ::= Vis:p FUN:o SigRef:f DOT Name:n LBRACKET Decls:d RBRACKET COLON Expr:r Super:v {: nod(n); parser.alloymodule.addFunc(o.merge(v.span()), p, n.label, f , d , mult(r), v); :}; +Function ::= Vis:p FUN:o SigRef:f DOT Name:n COLON Expr:r Super:v {: nod(n); parser.alloymodule.addFunc(o.merge(v.span()), p, n.label, f , null , mult(r), v); :}; + +Predicate ::= Vis:p PRED:o Name:n LPAREN Decls:d RPAREN Super:v {: nod(n); parser.alloymodule.addFunc(o.merge(v.span()), p, n.label, null, d , null, v); :}; +Predicate ::= Vis:p PRED:o Name:n LBRACKET Decls:d RBRACKET Super:v {: nod(n); parser.alloymodule.addFunc(o.merge(v.span()), p, n.label, null, d , null, v); :}; +Predicate ::= Vis:p PRED:o Name:n Super:v {: nod(n); parser.alloymodule.addFunc(o.merge(v.span()), p, n.label, null, null , null, v); :}; +Predicate ::= Vis:p PRED:o SigRef:f DOT Name:n LPAREN Decls:d RPAREN Super:v {: nod(n); parser.alloymodule.addFunc(o.merge(v.span()), p, n.label, f , d , null, v); :}; +Predicate ::= Vis:p PRED:o SigRef:f DOT Name:n LBRACKET Decls:d RBRACKET Super:v {: nod(n); parser.alloymodule.addFunc(o.merge(v.span()), p, n.label, f , d , null, v); :}; +Predicate ::= Vis:p PRED:o SigRef:f DOT Name:n Super:v {: nod(n); parser.alloymodule.addFunc(o.merge(v.span()), p, n.label, f , null , null, v); :}; + +Vis ::= {: RESULT=null; :}; +Vis ::= PRIVATE:p {: RESULT=p; :}; + +Sig ::= SigQuals:a Names:b SigIn:c LBRACE Decls:d RBRACE:o SuperOpt:e + {: + if (e==null) e = ExprConstant.Op.TRUE.make(o, 0); + ExprVar cc = (c!=null && c.size()>0) ? c.remove(c.size()-1) : null; + for(ExprVar bb:b) { + parser.alloymodule.addSig(bb.label, cc, c, d, e, + AttrType.WHERE .makenull(bb.pos.merge(e==null ? o : e.span())), + AttrType.ABSTRACT.makenull(a.get(0)), + AttrType.LONE .makenull(a.get(1)), + AttrType.ONE .makenull(a.get(2)), + AttrType.SOME .makenull(a.get(3)), + AttrType.PRIVATE .makenull(a.get(4))); + } + :}; + +SigQual ::= ABSTRACT:x {: RESULT=new ArrayList(5); RESULT.add(x); RESULT.add(null); RESULT.add(null); RESULT.add(null); RESULT.add(null); :}; +SigQual ::= LONE:x {: RESULT=new ArrayList(5); RESULT.add(null); RESULT.add(x); RESULT.add(null); RESULT.add(null); RESULT.add(null); :}; +SigQual ::= ONE:x {: RESULT=new ArrayList(5); RESULT.add(null); RESULT.add(null); RESULT.add(x); RESULT.add(null); RESULT.add(null); :}; +SigQual ::= SOME:x {: RESULT=new ArrayList(5); RESULT.add(null); RESULT.add(null); RESULT.add(null); RESULT.add(x); RESULT.add(null); :}; +SigQual ::= PRIVATE:x {: RESULT=new ArrayList(5); RESULT.add(null); RESULT.add(null); RESULT.add(null); RESULT.add(null); RESULT.add(x); :}; + +SigQuals ::= SIG {: RESULT=new ArrayList(5); RESULT.add(null); RESULT.add(null); RESULT.add(null); RESULT.add(null); RESULT.add(null); :}; +SigQuals ::= SigQual:a SigQuals:b {: RESULT=a; for(int i=0;i<5;i++) if (a.get(i)==null) a.set(i,b.get(i)); else if (b.get(i)!=null) throw new ErrorSyntax(b.get(i), "The same qualifer cannot be specified more than once for the same sig."); :}; + +SigIn ::= EXTENDS:a SigRef:x {: RESULT=new ArrayList(2); RESULT.add(x); RESULT.add(ExprVar.make(a, "extends")); :}; +SigIn ::= IN:a SigRefu:x {: RESULT=x; x.add(ExprVar.make(a,"in")); :}; +SigIn ::= EQUALS:a SigRefu:x {: RESULT=x; x.add(ExprVar.make(a,"=")); :}; +SigIn ::= {: RESULT=null; :}; + +SigRef ::= Name:x {: RESULT=x; :}; +SigRef ::= UNIV:x {: RESULT=ExprVar.make(x, "univ"); :}; +SigRef ::= STRING:x {: RESULT=ExprVar.make(x, "String"); :}; +SigRef ::= SIGINT:x {: RESULT=ExprVar.make(x, "Int"); :}; +SigRef ::= SEQ:a SLASH SIGINT:b {: RESULT=ExprVar.make(a.merge(b), "seq/Int"); :}; +SigRef ::= NONE:x {: RESULT=ExprVar.make(x, "none"); :}; + +SigRefs ::= {: RESULT=new ArrayList(); :}; +SigRefs ::= SigRefp:x {: RESULT=x; :}; + +SigRefp ::= SigRef:x {: RESULT=new ArrayList(); RESULT.add(x); :}; +SigRefp ::= SigRefp:a COMMA SigRef:b {: a.add(b); RESULT=a; :}; + +SigRefu ::= SigRef:x {: RESULT=new ArrayList(); RESULT.add(x); :}; +SigRefu ::= SigRefu:a PLUS SigRef:b {: a.add(b); RESULT=a; :}; + +Name ::= NameHelper:x {: RESULT=x; :}; +Name ::= THIS:a SLASH NameHelper:b {: RESULT=ExprVar.make(a.merge(b.pos), "this/"+b.label); :}; +Name ::= SEQ:a SLASH NameHelper:b {: RESULT=ExprVar.make(a.merge(b.pos), "seq/"+b.label); :}; + +NameHelper ::= ID:x {: RESULT=x; :}; +NameHelper ::= NameHelper:a SLASH ID:b {: RESULT=ExprVar.make(a.pos.merge(b.pos), a.label+"/"+b.label); :}; + +Names ::= Name:x {: nod(x); RESULT=new ArrayList(); RESULT.add(x); :}; +Names ::= Names:a COMMA Name:b {: nod(b); a.add(b); RESULT=a; :}; + +Namex ::= Name:x {: nod(x); RESULT=new ArrayList(); RESULT.add(x); :}; +Namex ::= EXACTLY Name:x {: nod(x); RESULT=new ArrayList(); RESULT.add(null); RESULT.add(x); :}; +Namex ::= Namex:a COMMA Name:b {: nod(b); a.add(b); RESULT=a; :}; +Namex ::= Namex:a COMMA EXACTLY Name:b {: nod(b); a.add(null); a.add(b); RESULT=a; :}; + +Decla ::= PART:k Names COLON Expr {: if (1==1) throw CompModule.hint(k, "part"); :}; +Decla ::= EXH:k Names COLON Expr {: if (1==1) throw CompModule.hint(k, "exh"); :}; +Decla ::= DISJ:k Names:a COLON Expr:b {: RESULT=new Decl(null, k, null, a, mult(b)); :}; +Decla ::= PRIVATE:p DISJ:k Names:a COLON Expr:b {: RESULT=new Decl(p, k, null, a, mult(b)); :}; +Decla ::= PRIVATE:p Names:a COLON Expr:b {: RESULT=new Decl(p, null, null, a, mult(b)); :}; +Decla ::= Names:a COLON Expr:b {: RESULT=new Decl(null, null, null, a, mult(b)); :}; + +Decla ::= PART:k Names COLON DISJ Expr {: if (1==1) throw CompModule.hint(k, "part"); :}; +Decla ::= EXH:k Names COLON DISJ Expr {: if (1==1) throw CompModule.hint(k, "exh"); :}; +Decla ::= DISJ:k Names:a COLON DISJ:d Expr:b {: RESULT=new Decl(null, k, d, a, mult(b)); :}; +Decla ::= PRIVATE:p DISJ:k Names:a COLON DISJ:d Expr:b {: RESULT=new Decl(p, k, d, a, mult(b)); :}; +Decla ::= PRIVATE:p Names:a COLON DISJ:d Expr:b {: RESULT=new Decl(p, null, d, a, mult(b)); :}; +Decla ::= Names:a COLON DISJ:d Expr:b {: RESULT=new Decl(null, null, d, a, mult(b)); :}; + +Declb ::= Decla:x {: RESULT=x; :}; + +Declb ::= PART:k Names EQUALS Expr {: if (1==1) throw CompModule.hint(k, "part"); :}; +Declb ::= EXH:k Names EQUALS Expr {: if (1==1) throw CompModule.hint(k, "exh"); :}; +Declb ::= DISJ:d Names EQUALS Expr {: if (1==1) throw new ErrorSyntax(d, "Defined fields cannot be disjoint."); :}; +Declb ::= PRIVATE DISJ:d Names EQUALS Expr {: if (1==1) throw new ErrorSyntax(d, "Defined fields cannot be disjoint."); :}; +Declb ::= PRIVATE:p Names:a EQUALS Expr:b {: RESULT=new Decl(p, null, null, a, ExprUnary.Op.EXACTLYOF.make(null, b)); :}; +Declb ::= Names:a EQUALS Expr:b {: RESULT=new Decl(null, null, null, a, ExprUnary.Op.EXACTLYOF.make(null, b)); :}; + +Declb ::= PART:k Names EQUALS DISJ Expr {: if (1==1) throw CompModule.hint(k, "part"); :}; +Declb ::= EXH:k Names EQUALS DISJ Expr {: if (1==1) throw CompModule.hint(k, "exh"); :}; +Declb ::= DISJ Names EQUALS DISJ:d Expr {: if (1==1) throw new ErrorSyntax(d, "Defined fields cannot be disjoint."); :}; +Declb ::= PRIVATE DISJ Names EQUALS DISJ:d Expr {: if (1==1) throw new ErrorSyntax(d, "Defined fields cannot be disjoint."); :}; +Declb ::= PRIVATE Names EQUALS DISJ:d Expr {: if (1==1) throw new ErrorSyntax(d, "Defined fields cannot be disjoint."); :}; +Declb ::= Names EQUALS DISJ:d Expr {: if (1==1) throw new ErrorSyntax(d, "Defined fields cannot be disjoint."); :}; + +Declz ::= Declz:x COMMA Decla:y {: RESULT=x; RESULT.add(y); :}; +Declz ::= Decla:y {: RESULT=new ArrayList(); RESULT.add(y); :}; + +Declp ::= Declp:x COMMA Declb:y {: RESULT=x; RESULT.add(y); :}; +Declp ::= Declb:y {: RESULT=new ArrayList(); RESULT.add(y); :}; + +Decls ::= {: RESULT=new ArrayList(); :}; +Decls ::= Declb:x {: RESULT=new ArrayList(); RESULT.add(x); :}; +Decls ::= Declb:x COMMA Decls:y {: RESULT=y; RESULT.add(0,x); :}; +Decls ::= COMMA Decls:y {: RESULT=y; :}; + +Let ::= Name:a EQUALS:o Expr:b SuperOrBar:x {: + nod(a); + if (a.label.indexOf('/')>=0) throw new ErrorSyntax(a.pos, "Let variable name cannot contain \'/\'"); + if (a.label.indexOf('@')>=0) throw new ErrorSyntax(a.pos, "Let variable name cannot contain \'@\'"); + RESULT = ExprLet.make(o, ExprVar.make(a.pos, a.label), b, x); +:}; + +Let ::= Name:a EQUALS:o Expr:b COMMA Let:x {: + nod(a); + if (a.label.indexOf('/')>=0) throw new ErrorSyntax(a.pos, "Let variable name cannot contain \'/\'"); + if (a.label.indexOf('@')>=0) throw new ErrorSyntax(a.pos, "Let variable name cannot contain \'@\'"); + RESULT = ExprLet.make(o, ExprVar.make(a.pos, a.label), b, x); +:}; + +SuperOpt ::= {: RESULT=null; :}; +SuperOpt ::= Super:x {: RESULT=x; :}; +Super ::= LBRACE:a SuperP:x RBRACE:b {: RESULT=ExprUnary.Op.NOOP.make(a.merge(b), x); :}; +Super ::= LBRACE:a RBRACE:b {: RESULT=ExprConstant.Op.TRUE.make(a.merge(b), 0); :}; +SuperP ::= Expr:a {: RESULT=a; :}; +SuperP ::= SuperP:a Expr:b {: RESULT=ExprBinary.Op.AND.make(null, null, a, b); :}; + +SuperOrBar ::= BAR Expr:x {: RESULT=x; :}; +SuperOrBar ::= Super:x {: RESULT=x; :}; + +Exprs ::= {: RESULT=new ArrayList(); :}; +Exprs ::= Exprp:x {: RESULT=x; :}; +Exprp ::= Expr:x {: RESULT=new ArrayList(); RESULT.add(x); :}; +Exprp ::= Exprp:a COMMA Expr:b {: a.add(b); RESULT=a; :}; + +//============================================================================= + +Expr ::= OrExprA:x {: RESULT = x; :}; +Expr ::= OrExprB:x {: RESULT = x; :}; +Expr ::= Bind:x {: RESULT = x; :}; +Bind ::= LET Let:x {: RESULT = x; :}; +Bind ::= ALL2:o Declp:a SuperOrBar:b {: RESULT = ExprQt.Op.ALL .make(o, null, a, b); :}; +Bind ::= NO2:o Declp:a SuperOrBar:b {: RESULT = ExprQt.Op.NO .make(o, null, a, b); :}; +Bind ::= SOME2:o Declp:a SuperOrBar:b {: RESULT = ExprQt.Op.SOME.make(o, null, a, b); :}; +Bind ::= LONE2:o Declp:a SuperOrBar:b {: RESULT = ExprQt.Op.LONE.make(o, null, a, b); :}; +Bind ::= ONE2:o Declp:a SuperOrBar:b {: RESULT = ExprQt.Op.ONE .make(o, null, a, b); :}; +Bind ::= SUM2:o Declp:a SuperOrBar:b {: RESULT = ExprQt.Op.SUM .make(o, null, a, b); :}; + +OrExprA ::= EquivExprA:a {: RESULT=a; :}; +OrExprA ::= OrExprB:a OR:o Bind:b {: RESULT=ExprBinary.Op.OR.make(o, null, a, b); :}; +OrExprB ::= EquivExprB:b {: RESULT=b; :}; +OrExprB ::= OrExprB:a OR:o EquivExprB:b {: RESULT=ExprBinary.Op.OR.make(o, null, a, b); :}; + +EquivExprA ::= ImpliesExprA:b {: RESULT=b; :}; +EquivExprA ::= EquivExprB:a IFF:o Bind:b {: RESULT=ExprBinary.Op.IFF.make(o, null, a, b); :}; +EquivExprB ::= ImpliesExprB:b {: RESULT=b; :}; +EquivExprB ::= EquivExprB:a IFF:o ImpliesExprB:b {: RESULT=ExprBinary.Op.IFF.make(o, null, a, b); :}; + +ImpliesExprA ::= ImpliesExprCloseA:a {: RESULT=a; :}; +ImpliesExprA ::= ImpliesExprOpenA:a {: RESULT=a; :}; +ImpliesExprCloseA ::= AndExprA:a {: RESULT=a; :}; +ImpliesExprCloseA ::= AndExprB:a IMPLIES:o ImpliesExprCloseB:b ELSE ImpliesExprCloseA:c {: RESULT = ExprITE.make(o,a,b,c); :}; +ImpliesExprOpenA ::= AndExprB:a IMPLIES:o ImpliesExprCloseB:b ELSE ImpliesExprOpenA:c {: RESULT = ExprITE.make(o,a,b,c); :}; +ImpliesExprOpenA ::= AndExprB:a IMPLIES:o ImpliesExprA:b {: RESULT = ExprBinary.Op.IMPLIES.make(o, null, a, b); :}; + +ImpliesExprCloseA ::= AndExprB:a IMPLIES:o ImpliesExprCloseB:b ELSE Bind:c {: RESULT = ExprITE.make(o,a,b,c); :}; +ImpliesExprOpenA ::= AndExprB:a IMPLIES:o Bind:b {: RESULT = ExprBinary.Op.IMPLIES.make(o, null, a, b); :}; + +ImpliesExprB ::= ImpliesExprCloseB:a {: RESULT=a; :}; +ImpliesExprB ::= ImpliesExprOpenB:a {: RESULT=a; :}; +ImpliesExprCloseB ::= AndExprB:a {: RESULT=a; :}; +ImpliesExprCloseB ::= AndExprB:a IMPLIES:o ImpliesExprCloseB:b ELSE ImpliesExprCloseB:c {: RESULT = ExprITE.make(o,a,b,c); :}; +ImpliesExprOpenB ::= AndExprB:a IMPLIES:o ImpliesExprCloseB:b ELSE ImpliesExprOpenB:c {: RESULT = ExprITE.make(o,a,b,c); :}; +ImpliesExprOpenB ::= AndExprB:a IMPLIES:o ImpliesExprB:b {: RESULT = ExprBinary.Op.IMPLIES.make(o, null, a, b); :}; + +AndExprA ::= NegExprA:a {: RESULT=a; :}; +AndExprA ::= AndExprB:a AND:o Bind:b {: RESULT=ExprBinary.Op.AND.make(o, null, a, b); :}; +AndExprB ::= NegExprB:b {: RESULT=b; :}; +AndExprB ::= AndExprB:a AND:o NegExprB:b {: RESULT=ExprBinary.Op.AND.make(o, null, a, b); :}; + +NegExprA ::= CompareExprA:b {: RESULT=b; :}; +NegExprA ::= NOT:o Bind:b {: RESULT=ExprUnary.Op.NOT.make(o, b); :}; +NegExprA ::= NOT:o NegExprA:b {: RESULT=ExprUnary.Op.NOT.make(o, b); :}; +NegExprB ::= CompareExprB:b {: RESULT=b; :}; +NegExprB ::= NOT:o NegExprB:b {: RESULT=ExprUnary.Op.NOT.make(o, b); :}; + +CompareExprA ::= CompareExprB:a IN:o ShiftExprA:b {: RESULT=ExprBinary.Op.IN .make(o, null, a, mult(b)); :}; +CompareExprA ::= CompareExprB:a EQUALS:o ShiftExprA:b {: RESULT=ExprBinary.Op.EQUALS .make(o, null, a, b); :}; +CompareExprA ::= CompareExprB:a LT:o ShiftExprA:b {: RESULT=ExprBinary.Op.LT .make(o, null, a, b); :}; +CompareExprA ::= CompareExprB:a GT:o ShiftExprA:b {: RESULT=ExprBinary.Op.GT .make(o, null, a, b); :}; +CompareExprA ::= CompareExprB:a LTE:o ShiftExprA:b {: RESULT=ExprBinary.Op.LTE .make(o, null, a, b); :}; +CompareExprA ::= CompareExprB:a GTE:o ShiftExprA:b {: RESULT=ExprBinary.Op.GTE .make(o, null, a, b); :}; +CompareExprA ::= CompareExprB:a NOTIN:o ShiftExprA:b {: RESULT=ExprBinary.Op.NOT_IN .make(o, null, a, mult(b)); :}; +CompareExprA ::= CompareExprB:a NOTEQUALS:o ShiftExprA:b {: RESULT=ExprBinary.Op.NOT_EQUALS.make(o, null, a, b); :}; +CompareExprA ::= CompareExprB:a NOTLT:o ShiftExprA:b {: RESULT=ExprBinary.Op.NOT_LT .make(o, null, a, b); :}; +CompareExprA ::= CompareExprB:a NOTGT:o ShiftExprA:b {: RESULT=ExprBinary.Op.NOT_GT .make(o, null, a, b); :}; +CompareExprA ::= CompareExprB:a NOTLTE:o ShiftExprA:b {: RESULT=ExprBinary.Op.NOT_LTE .make(o, null, a, b); :}; +CompareExprA ::= CompareExprB:a NOTGTE:o ShiftExprA:b {: RESULT=ExprBinary.Op.NOT_GTE .make(o, null, a, b); :}; +CompareExprA ::= ALL:o ShiftExprA {: if (1==1) throw new ErrorSyntax(o,"The \"all x\" construct is no longer supported. If you know the range of possible values of x, consider rewriting it as \"x == set_of_all_possible_values\"."); :}; +CompareExprA ::= NO:o ShiftExprA:b {: RESULT=ExprUnary.Op.NO .make(o, b); :}; +CompareExprA ::= SOME:o ShiftExprA:b {: RESULT=ExprUnary.Op.SOME .make(o, b); :}; +CompareExprA ::= LONE:o ShiftExprA:b {: RESULT=ExprUnary.Op.LONE .make(o, b); :}; +CompareExprA ::= ONE:o ShiftExprA:b {: RESULT=ExprUnary.Op.ONE .make(o, b); :}; +CompareExprA ::= SET:o ShiftExprA:b {: RESULT=ExprUnary.Op.SETOF.make(o, b); :}; +CompareExprA ::= SEQ:o ShiftExprA:b {: RESULT=ExprBinary.Op.ISSEQ_ARROW_LONE.make(o, null, ExprVar.make(o, "seq/Int"), b); parser.alloymodule.addSeq(o); :}; +CompareExprA ::= ShiftExprA:b {: RESULT=b; :}; + +CompareExprB ::= CompareExprB:a IN:o ShiftExprB:b {: RESULT=ExprBinary.Op.IN .make(o, null, a, mult(b)); :}; +CompareExprB ::= CompareExprB:a EQUALS:o ShiftExprB:b {: RESULT=ExprBinary.Op.EQUALS .make(o, null, a, b); :}; +CompareExprB ::= CompareExprB:a LT:o ShiftExprB:b {: RESULT=ExprBinary.Op.LT .make(o, null, a, b); :}; +CompareExprB ::= CompareExprB:a GT:o ShiftExprB:b {: RESULT=ExprBinary.Op.GT .make(o, null, a, b); :}; +CompareExprB ::= CompareExprB:a LTE:o ShiftExprB:b {: RESULT=ExprBinary.Op.LTE .make(o, null, a, b); :}; +CompareExprB ::= CompareExprB:a GTE:o ShiftExprB:b {: RESULT=ExprBinary.Op.GTE .make(o, null, a, b); :}; +CompareExprB ::= CompareExprB:a NOTIN:o ShiftExprB:b {: RESULT=ExprBinary.Op.NOT_IN .make(o, null, a, mult(b)); :}; +CompareExprB ::= CompareExprB:a NOTEQUALS:o ShiftExprB:b {: RESULT=ExprBinary.Op.NOT_EQUALS.make(o, null, a, b); :}; +CompareExprB ::= CompareExprB:a NOTLT:o ShiftExprB:b {: RESULT=ExprBinary.Op.NOT_LT .make(o, null, a, b); :}; +CompareExprB ::= CompareExprB:a NOTGT:o ShiftExprB:b {: RESULT=ExprBinary.Op.NOT_GT .make(o, null, a, b); :}; +CompareExprB ::= CompareExprB:a NOTLTE:o ShiftExprB:b {: RESULT=ExprBinary.Op.NOT_LTE .make(o, null, a, b); :}; +CompareExprB ::= CompareExprB:a NOTGTE:o ShiftExprB:b {: RESULT=ExprBinary.Op.NOT_GTE .make(o, null, a, b); :}; +CompareExprB ::= ALL:o ShiftExprB {: if (1==1) throw new ErrorSyntax(o,"The \"all x\" construct is no longer supported. If you know the range of possible values of x, consider rewriting it as \"x == set_of_all_possible_values\"."); :}; +CompareExprB ::= NO:o ShiftExprB:b {: RESULT=ExprUnary.Op.NO .make(o, b); :}; +CompareExprB ::= SOME:o ShiftExprB:b {: RESULT=ExprUnary.Op.SOME .make(o, b); :}; +CompareExprB ::= LONE:o ShiftExprB:b {: RESULT=ExprUnary.Op.LONE .make(o, b); :}; +CompareExprB ::= ONE:o ShiftExprB:b {: RESULT=ExprUnary.Op.ONE .make(o, b); :}; +CompareExprB ::= SET:o ShiftExprB:b {: RESULT=ExprUnary.Op.SETOF.make(o, b); :}; +CompareExprB ::= SEQ:o ShiftExprB:b {: RESULT=ExprBinary.Op.ISSEQ_ARROW_LONE.make(o, null, ExprVar.make(o,"seq/Int"), b); parser.alloymodule.addSeq(o); :}; +CompareExprB ::= ShiftExprB:b {: RESULT=b; :}; + +ShiftExprA ::= UnionDiffExprA:b {: RESULT=b; :}; +ShiftExprA ::= ShiftExprB:a SHL:o Bind:b {: RESULT=ExprBinary.Op.SHL.make(o, null, a, b); :}; +ShiftExprA ::= ShiftExprB:a SHR:o Bind:b {: RESULT=ExprBinary.Op.SHR.make(o, null, a, b); :}; +ShiftExprA ::= ShiftExprB:a SHA:o Bind:b {: RESULT=ExprBinary.Op.SHA.make(o, null, a, b); :}; +ShiftExprB ::= UnionDiffExprB:b {: RESULT=b; :}; +ShiftExprB ::= ShiftExprB:a SHL:o UnionDiffExprB:b {: RESULT=ExprBinary.Op.SHL.make(o, null, a, b); :}; +ShiftExprB ::= ShiftExprB:a SHR:o UnionDiffExprB:b {: RESULT=ExprBinary.Op.SHR.make(o, null, a, b); :}; +ShiftExprB ::= ShiftExprB:a SHA:o UnionDiffExprB:b {: RESULT=ExprBinary.Op.SHA.make(o, null, a, b); :}; + +UnionDiffExprA ::= MulExprA:b {: RESULT=b; :}; +UnionDiffExprA ::= UnionDiffExprB:a PLUS:o Bind:b {: RESULT=ExprBinary.Op.PLUS .make(o, null, a, b); :}; +UnionDiffExprA ::= UnionDiffExprB:a MINUS:o Bind:b {: RESULT=ExprBinary.Op.MINUS.make(o, null, a, b); :}; +UnionDiffExprA ::= UnionDiffExprB:a INTADD:o Bind:b {: RESULT=ExprBinary.Op.IPLUS.make(o, null, a, b); :}; +UnionDiffExprA ::= UnionDiffExprB:a INTSUB:o Bind:b {: RESULT=ExprBinary.Op.IMINUS.make(o, null, a, b); :}; +UnionDiffExprB ::= MulExprB:b {: RESULT=b; :}; +UnionDiffExprB ::= UnionDiffExprB:a PLUS:o MulExprB:b {: RESULT=ExprBinary.Op.PLUS .make(o, null, a, b); :}; +UnionDiffExprB ::= UnionDiffExprB:a MINUS:o MulExprB:b {: RESULT=ExprBinary.Op.MINUS.make(o, null, a, b); :}; +UnionDiffExprB ::= UnionDiffExprB:a INTADD:o MulExprB:b {: RESULT=ExprBinary.Op.IPLUS.make(o, null, a, b); :}; +UnionDiffExprB ::= UnionDiffExprB:a INTSUB:o MulExprB:b {: RESULT=ExprBinary.Op.IMINUS.make(o, null, a, b); :}; + +MulExprA ::= NumUnopExprA:b {: RESULT=b; :}; +MulExprA ::= MulExprB:a INTMUL:o Bind:b {: RESULT=ExprBinary.Op.MUL .make(o, null, a, b); :}; +MulExprA ::= MulExprB:a INTDIV:o Bind:b {: RESULT=ExprBinary.Op.DIV .make(o, null, a, b); :}; +MulExprA ::= MulExprB:a INTREM:o Bind:b {: RESULT=ExprBinary.Op.REM .make(o, null, a, b); :}; +MulExprB ::= NumUnopExprB:b {: RESULT=b; :}; +MulExprB ::= MulExprB:a INTMUL:o NumUnopExprB:b {: RESULT=ExprBinary.Op.MUL .make(o, null, a, b); :}; +MulExprB ::= MulExprB:a INTDIV:o NumUnopExprB:b {: RESULT=ExprBinary.Op.DIV .make(o, null, a, b); :}; +MulExprB ::= MulExprB:a INTREM:o NumUnopExprB:b {: RESULT=ExprBinary.Op.REM .make(o, null, a, b); :}; + +NumUnopExprA ::= OverrideExprA:b {: RESULT=b; :}; +NumUnopExprA ::= HASH:o Bind:b {: RESULT=ExprUnary.Op.CARDINALITY.make(o, b); :}; +NumUnopExprA ::= SUM:o Bind:b {: RESULT=ExprUnary.Op.CAST2SIGINT.make(o, ExprUnary.Op.CAST2INT.make(o, b)); :}; +//[AM]: INT->SIGINT +NumUnopExprA ::= INT:o Bind:b {: RESULT=ExprUnary.Op.CAST2SIGINT.make(o, ExprUnary.Op.CAST2INT.make(o, b)); :}; +NumUnopExprA ::= HASH:o NumUnopExprA:b {: RESULT=ExprUnary.Op.CARDINALITY.make(o, b); :}; +NumUnopExprA ::= SUM:o NumUnopExprA:b {: RESULT=ExprUnary.Op.CAST2SIGINT.make(o, ExprUnary.Op.CAST2INT.make(o, b)); :}; +//[AM]: INT->SIGINT +NumUnopExprA ::= INT:o NumUnopExprA:b {: RESULT=ExprUnary.Op.CAST2SIGINT.make(o, ExprUnary.Op.CAST2INT.make(o, b)); :}; +NumUnopExprB ::= OverrideExprB:b {: RESULT=b; :}; +NumUnopExprB ::= HASH:o NumUnopExprB:b {: RESULT=ExprUnary.Op.CARDINALITY.make(o, b); :}; +NumUnopExprB ::= SUM:o NumUnopExprB:b {: RESULT=ExprUnary.Op.CAST2SIGINT.make(o, ExprUnary.Op.CAST2INT.make(o, b)); :}; +//[AM]: INT->SIGINT +NumUnopExprB ::= INT:o NumUnopExprB:b {: RESULT=ExprUnary.Op.CAST2SIGINT.make(o, ExprUnary.Op.CAST2INT.make(o, b)); :}; + +OverrideExprA ::= IntersectExprA:b {: RESULT=b; :}; +OverrideExprA ::= OverrideExprB:a PLUSPLUS:o Bind:b {: RESULT=ExprBinary.Op.PLUSPLUS.make(o, null, a, b); :}; +OverrideExprB ::= IntersectExprB:b {: RESULT=b; :}; +OverrideExprB ::= OverrideExprB:a PLUSPLUS:o IntersectExprB:b {: RESULT=ExprBinary.Op.PLUSPLUS.make(o, null, a, b); :}; + +IntersectExprA ::= RelationExprA:b {: RESULT=b; :}; +IntersectExprA ::= IntersectExprB:a AMPERSAND:o Bind:b {: RESULT=ExprBinary.Op.INTERSECT.make(o, null, a, b); :}; +IntersectExprB ::= RelationExprB:b {: RESULT=b; :}; +IntersectExprB ::= IntersectExprB:a AMPERSAND:o RelationExprB:b {: RESULT=ExprBinary.Op.INTERSECT.make(o, null, a, b); :}; + +RelOp ::= ARROW:o {: RESULT=new Pair(o, ExprBinary.Op.ARROW ); :}; +RelOp ::= ANY_ARROW_SOME:o {: RESULT=new Pair(o, ExprBinary.Op.ANY_ARROW_SOME ); :}; +RelOp ::= ANY_ARROW_ONE:o {: RESULT=new Pair(o, ExprBinary.Op.ANY_ARROW_ONE ); :}; +RelOp ::= ANY_ARROW_LONE:o {: RESULT=new Pair(o, ExprBinary.Op.ANY_ARROW_LONE ); :}; +RelOp ::= SOME_ARROW_ANY:o {: RESULT=new Pair(o, ExprBinary.Op.SOME_ARROW_ANY ); :}; +RelOp ::= SOME_ARROW_SOME:o {: RESULT=new Pair(o, ExprBinary.Op.SOME_ARROW_SOME); :}; +RelOp ::= SOME_ARROW_ONE:o {: RESULT=new Pair(o, ExprBinary.Op.SOME_ARROW_ONE ); :}; +RelOp ::= SOME_ARROW_LONE:o {: RESULT=new Pair(o, ExprBinary.Op.SOME_ARROW_LONE); :}; +RelOp ::= ONE_ARROW_ANY:o {: RESULT=new Pair(o, ExprBinary.Op.ONE_ARROW_ANY ); :}; +RelOp ::= ONE_ARROW_SOME:o {: RESULT=new Pair(o, ExprBinary.Op.ONE_ARROW_SOME ); :}; +RelOp ::= ONE_ARROW_ONE:o {: RESULT=new Pair(o, ExprBinary.Op.ONE_ARROW_ONE ); :}; +RelOp ::= ONE_ARROW_LONE:o {: RESULT=new Pair(o, ExprBinary.Op.ONE_ARROW_LONE ); :}; +RelOp ::= LONE_ARROW_ANY:o {: RESULT=new Pair(o, ExprBinary.Op.LONE_ARROW_ANY ); :}; +RelOp ::= LONE_ARROW_SOME:o {: RESULT=new Pair(o, ExprBinary.Op.LONE_ARROW_SOME); :}; +RelOp ::= LONE_ARROW_ONE:o {: RESULT=new Pair(o, ExprBinary.Op.LONE_ARROW_ONE ); :}; +RelOp ::= LONE_ARROW_LONE:o {: RESULT=new Pair(o, ExprBinary.Op.LONE_ARROW_LONE); :}; + +RelationExprA ::= DomainExprA:a {: RESULT=a; :}; +RelationExprA ::= DomainExprB:a RelOp:o Bind:b {: RESULT=o.b.make(o.a, null, a, b); :}; +RelationExprB ::= DomainExprB:a {: RESULT=a; :}; +RelationExprB ::= DomainExprB:a RelOp:o RelationExprB:b {: RESULT=o.b.make(o.a, null, a, b); :}; + +DomainExprA ::= RangeExprA:b {: RESULT=b; :}; +DomainExprA ::= DomainExprB:a DOMAIN:o Bind:b {: RESULT=ExprBinary.Op.DOMAIN.make(o, null, a, b); :}; +DomainExprB ::= RangeExprB:b {: RESULT=b; :}; +DomainExprB ::= DomainExprB:a DOMAIN:o RangeExprB:b {: RESULT=ExprBinary.Op.DOMAIN.make(o, null, a, b); :}; + +RangeExprA ::= BracketExprA:b {: RESULT=b; :}; +RangeExprA ::= RangeExprB:a RANGE:o Bind:b {: RESULT=ExprBinary.Op.RANGE.make(o, null, a, b); :}; +RangeExprB ::= BracketExprB:b {: RESULT=b; :}; +RangeExprB ::= RangeExprB:a RANGE:o BracketExprB:b {: RESULT=ExprBinary.Op.RANGE.make(o, null, a, b); :}; + +BracketExprA ::= DotExprA:b {: RESULT=b; :}; +BracketExprB ::= DotExprB:b {: RESULT=b; :}; +BracketExprB ::= BracketExprB:a LBRACKET Exprs:b RBRACKET:c {: Expr aa=a; for(Expr bb:b) aa=t(aa.span().merge(bb.span()), c, bb, aa, c); RESULT=aa; :}; +BracketExprB ::= DISJ:a LBRACKET Exprs:b RBRACKET:c {: Expr aa=ExprVar.make(a, "disj"); for(Expr bb:b) aa=t(aa.span().merge(bb.span()), c, bb, aa, c); RESULT=aa; :}; +BracketExprB ::= TOTALORDER:a LBRACKET Exprs:b RBRACKET:c {: Expr aa=ExprVar.make(a, "pred/totalOrder"); for(Expr bb:b) aa=t(aa.span().merge(bb.span()), c, bb, aa, c); RESULT=aa; :}; +//[AM]: INT->SIGINT +BracketExprB ::= INT:a LBRACKET Exprs:b RBRACKET:c {: Expr aa=ExprVar.make(a, "int"); for(Expr bb:b) aa=t(aa.span().merge(bb.span()), c, bb, aa, c); RESULT=ExprUnary.Op.CAST2SIGINT.make(a, aa); :}; +BracketExprB ::= SUM:a LBRACKET Exprs:b RBRACKET:c {: Expr aa=ExprVar.make(a, "int"); for(Expr bb:b) aa=t(aa.span().merge(bb.span()), c, bb, aa, c); RESULT=ExprUnary.Op.CAST2SIGINT.make(a, aa); :}; + +DotExprA ::= UnopExprA:b {: RESULT=b; :}; +DotExprA ::= BracketExprB:a DOT:o Bind:b {: RESULT=t(o, null, a, b, null); :}; +DotExprB ::= UnopExprB:b {: RESULT=b; :}; +DotExprB ::= BracketExprB:a DOT:o UnopExprB:b {: RESULT=t(o, null, a, b, null); :}; +DotExprB ::= BracketExprB:a DOT:o DISJ:b {: RESULT=t(o, null, a, ExprVar.make(b, "disj"), null); :}; +DotExprB ::= BracketExprB:a DOT:o TOTALORDER:b {: RESULT=t(o, null, a, ExprVar.make(b, "pred/totalOrder"), null); :}; +//[AM]: INT->SIGINT +DotExprB ::= BracketExprB:a DOT:o INT {: RESULT=ExprUnary.Op.CAST2SIGINT.make(o, ExprUnary.Op.CAST2INT.make(o, a)); :}; +DotExprB ::= BracketExprB:a DOT:o SUM {: RESULT=ExprUnary.Op.CAST2SIGINT.make(o, ExprUnary.Op.CAST2INT.make(o, a)); :}; + +UnopExprA ::= TILDE:o Bind:b {: RESULT=ExprUnary.Op.TRANSPOSE.make(o,b); :}; +UnopExprA ::= STAR:o Bind:b {: RESULT=ExprUnary.Op.RCLOSURE .make(o,b); :}; +UnopExprA ::= CARET:o Bind:b {: RESULT=ExprUnary.Op.CLOSURE .make(o,b); :}; +UnopExprA ::= TILDE:o UnopExprA:b {: RESULT=ExprUnary.Op.TRANSPOSE.make(o,b); :}; +UnopExprA ::= STAR:o UnopExprA:b {: RESULT=ExprUnary.Op.RCLOSURE .make(o,b); :}; +UnopExprA ::= CARET:o UnopExprA:b {: RESULT=ExprUnary.Op.CLOSURE .make(o,b); :}; +UnopExprB ::= BaseExpr:b {: RESULT=b; :}; +UnopExprB ::= TILDE:o UnopExprB:b {: RESULT=ExprUnary.Op.TRANSPOSE.make(o,b); :}; +UnopExprB ::= STAR:o UnopExprB:b {: RESULT=ExprUnary.Op.RCLOSURE .make(o,b); :}; +UnopExprB ::= CARET:o UnopExprB:b {: RESULT=ExprUnary.Op.CLOSURE .make(o,b); :}; + +BaseExpr ::= NUMBER:x {: RESULT = x; :}; +BaseExpr ::= STR:x {: RESULT = x; :}; +BaseExpr ::= IDEN:o {: RESULT = ExprVar.make(o, "iden"); :}; +BaseExpr ::= THIS:o {: RESULT = ExprVar.make(o, "this"); :}; +BaseExpr ::= INTMIN:o {: RESULT = ExprConstant.Op.MIN.make(o, 0); :}; +BaseExpr ::= INTMAX:o {: RESULT = ExprConstant.Op.MAX.make(o, 0); :}; +BaseExpr ::= INTNEXT:o {: RESULT = ExprConstant.Op.NEXT.make(o, 0); :}; +BaseExpr ::= LPAREN Expr:x RPAREN {: RESULT = x; :}; +BaseExpr ::= SigRef:x {: RESULT = x; :}; +BaseExpr ::= AT:o Name:x {: nod(x); RESULT = ExprVar.make(o.merge(x.pos), "@"+x.label); :}; +BaseExpr ::= Super:x {: RESULT = x; :}; +BaseExpr ::= LBRACE:o Declz:a SuperOrBar:b RBRACE:c {: RESULT = ExprQt.Op.COMPREHENSION.make(o, c, a, b); :}; +BaseExpr ::= LBRACE:o Declz:a RBRACE:c {: RESULT = ExprQt.Op.COMPREHENSION.make(o, c, a, ExprConstant.TRUE); :}; + +//============================================================================= diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/parser/Alloy.lex b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/parser/Alloy.lex new file mode 100644 index 00000000..b6f2af29 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/parser/Alloy.lex @@ -0,0 +1,218 @@ +// Alloy Analyzer 4 -- Copyright (c) 2006-2008, Felix Chang +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files +// (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, +// merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF +// OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +package edu.mit.csail.sdg.alloy4compiler.parser; + +import edu.mit.csail.sdg.alloy4.Err; +import edu.mit.csail.sdg.alloy4.ErrorSyntax; +import edu.mit.csail.sdg.alloy4.Pos; +import edu.mit.csail.sdg.alloy4.Version; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprConstant; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprVar; +import java.util.List; +import java_cup.runtime.*; + +/** Autogenerated by JFlex 1.4.1 */ + +%% + +// There are 3 sets of "special tokens" that the lexer will not output. +// But the Parser expects them. +// So a special Filter class is written that sits between Lexer and Parser. +// The Filter class observes the stream of tokens, and intelligently +// merges or changes some primitive tokens into special tokens. +// For more details, refer to the main documentation. +// +// But, very briefly, here are the 3 groups: +// +// (1) The lexer will generate only ALL, NO, LONE, ONE, SUM, SOME. +// It will not output ALL2, NO2, LONE2, ONE2, SUM2, SOME2. +// (The Filter class will change some ONE into ONE2, etc) +// +// (2) The lexer won't output NOTEQUALS, NOTIN, NOTLT, NOTLTE, NOTGT, NOTGTE. +// Instead it outputs them as separate tokens (eg. "NOT" "EQUALS"). +// (The Filter class is used to merge them into a single "NOTEQUALS" token) +// +// (3) The lexer willn't output the 15 special arrows (eg. ONE_ARROW_ONE) +// Instead it outputs them as separate tokens (eg. "ONE", "ARROW", "ONE") +// (The Filter class is used to merge them into a single "ONE_ARROW_ONE" token) + +%class CompLexer // The ordering of these directives is important +%cupsym CompSym +%cup +%eofval{ + return new Symbol(CompSym.EOF, alloy_here(" "), alloy_here(" ")); +%eofval} +%public +%final +%unicode +%line +%column +%pack + +%{ + public String alloy_filename=""; + public int alloy_lineoffset=0; // If not zero, it is added to the current LINE NUMBER + public List alloy_seenDollar; + public CompModule alloy_module; + private final Pos alloy_here(String txt) { + return new Pos(alloy_filename,yycolumn+1,yyline+1+alloy_lineoffset,yycolumn+txt.length(),yyline+1); + } + private final Symbol alloy_sym(String txt, int type) { + Pos p = alloy_here(txt); return new Symbol(type, p, p); + } + private final Symbol alloy_string(String txt) throws Err { + Pos p = alloy_here(txt); + if (!Version.experimental) throw new ErrorSyntax(p, "String literal is not currently supported."); + StringBuilder sb = new StringBuilder(txt.length()); + for(int i=0; i=txt.length()) throw new ErrorSyntax(p, "String literal cannot end with a single \\"); + c = txt.charAt(i); + if (c=='n') c='\n'; else if (c!='\'' && c!='\"' && c!='\\') throw new ErrorSyntax(p, "String literal currenty only supports\nfour escape sequences: \\\\, \\n, \\\', and \\\""); + } + sb.append(c); + } + txt = sb.toString(); + if (txt.length()==2) throw new ErrorSyntax(p, "Empty string is not allowed; try rewriting your model to use an empty set instead."); + return new Symbol(CompSym.STR, p, ExprConstant.Op.STRING.make(p, txt)); + } + private final Symbol alloy_id(String txt) throws Err { + Pos p=alloy_here(txt); + if (alloy_seenDollar.size()==0 && txt.indexOf('$')>=0) alloy_seenDollar.add(null); + return new Symbol(CompSym.ID, p, ExprVar.make(p,txt)); + } + private final Symbol alloy_num(String txt) throws Err { + Pos p=alloy_here(txt); + int n=0; + try { + n=Integer.parseInt(txt); + } catch(NumberFormatException ex) { + throw new ErrorSyntax(p, "The number "+txt+" is too large to be stored in a Java integer"); + } + return new Symbol(CompSym.NUMBER, p, ExprConstant.Op.NUMBER.make(p, n)); + } +%} + +%% + +"!" { return alloy_sym(yytext(), CompSym.NOT );} +"#" { return alloy_sym(yytext(), CompSym.HASH );} +"&&" { return alloy_sym(yytext(), CompSym.AND );} +"&" { return alloy_sym(yytext(), CompSym.AMPERSAND );} +"(" { return alloy_sym(yytext(), CompSym.LPAREN );} +")" { return alloy_sym(yytext(), CompSym.RPAREN );} +"*" { return alloy_sym(yytext(), CompSym.STAR );} +"++" { return alloy_sym(yytext(), CompSym.PLUSPLUS );} +"+" { return alloy_sym(yytext(), CompSym.PLUS );} +"," { return alloy_sym(yytext(), CompSym.COMMA );} +"->" { return alloy_sym(yytext(), CompSym.ARROW );} +"-" { return alloy_sym(yytext(), CompSym.MINUS );} +"." { return alloy_sym(yytext(), CompSym.DOT );} +"/" { return alloy_sym(yytext(), CompSym.SLASH );} +"::" { return alloy_sym(yytext(), CompSym.DOT );} +":>" { return alloy_sym(yytext(), CompSym.RANGE );} +":" { return alloy_sym(yytext(), CompSym.COLON );} +"<=>" { return alloy_sym(yytext(), CompSym.IFF );} +"<=" { return alloy_sym(yytext(), CompSym.LTE );} +"<:" { return alloy_sym(yytext(), CompSym.DOMAIN );} +"<<" { return alloy_sym(yytext(), CompSym.SHL );} +"<" { return alloy_sym(yytext(), CompSym.LT );} +"=<" { return alloy_sym(yytext(), CompSym.LTE );} +"=>" { return alloy_sym(yytext(), CompSym.IMPLIES );} +"=" { return alloy_sym(yytext(), CompSym.EQUALS );} +">>>" { return alloy_sym(yytext(), CompSym.SHR );} +">>" { return alloy_sym(yytext(), CompSym.SHA );} +">=" { return alloy_sym(yytext(), CompSym.GTE );} +">" { return alloy_sym(yytext(), CompSym.GT );} +"@" { return alloy_sym(yytext(), CompSym.AT );} +"[" { return alloy_sym(yytext(), CompSym.LBRACKET );} +"]" { return alloy_sym(yytext(), CompSym.RBRACKET );} +"^" { return alloy_sym(yytext(), CompSym.CARET );} +"{" { return alloy_sym(yytext(), CompSym.LBRACE );} +"||" { return alloy_sym(yytext(), CompSym.OR );} +"|" { return alloy_sym(yytext(), CompSym.BAR );} +"}" { return alloy_sym(yytext(), CompSym.RBRACE );} +"~" { return alloy_sym(yytext(), CompSym.TILDE );} +"abstract" { return alloy_sym(yytext(), CompSym.ABSTRACT );} +"all" { return alloy_sym(yytext(), CompSym.ALL );} +"and" { return alloy_sym(yytext(), CompSym.AND );} +"assert" { return alloy_sym(yytext(), CompSym.ASSERT );} +"as" { return alloy_sym(yytext(), CompSym.AS );} +"but" { return alloy_sym(yytext(), CompSym.BUT );} +"check" { return alloy_sym(yytext(), CompSym.CHECK );} +"disjoint" { return alloy_sym(yytext(), CompSym.DISJ );} +"disj" { return alloy_sym(yytext(), CompSym.DISJ );} +"else" { return alloy_sym(yytext(), CompSym.ELSE );} +"enum" { return alloy_sym(yytext(), CompSym.ENUM );} +"exactly" { return alloy_sym(yytext(), CompSym.EXACTLY );} +"exhaustive" { return alloy_sym(yytext(), CompSym.EXH );} +"exh" { return alloy_sym(yytext(), CompSym.EXH );} +"expect" { return alloy_sym(yytext(), CompSym.EXPECT );} +"extends" { return alloy_sym(yytext(), CompSym.EXTENDS );} +"fact" { return alloy_sym(yytext(), CompSym.FACT );} +"for" { return alloy_sym(yytext(), CompSym.FOR );} +"fun" { return alloy_sym(yytext(), CompSym.FUN );} +"iden" { return alloy_sym(yytext(), CompSym.IDEN );} +"iff" { return alloy_sym(yytext(), CompSym.IFF );} +"implies" { return alloy_sym(yytext(), CompSym.IMPLIES );} +"Int" { return alloy_sym(yytext(), CompSym.SIGINT );} +"int" { return alloy_sym(yytext(), CompSym.INT );} +"in" { return alloy_sym(yytext(), CompSym.IN );} +"let" { return alloy_sym(yytext(), CompSym.LET );} +"lone" { return alloy_sym(yytext(), CompSym.LONE );} +"module" { return alloy_sym(yytext(), CompSym.MODULE );} +"none" { return alloy_sym(yytext(), CompSym.NONE );} +"not" { return alloy_sym(yytext(), CompSym.NOT );} +"no" { return alloy_sym(yytext(), CompSym.NO );} +"one" { return alloy_sym(yytext(), CompSym.ONE );} +"open" { return alloy_sym(yytext(), CompSym.OPEN );} +"or" { return alloy_sym(yytext(), CompSym.OR );} +"partition" { return alloy_sym(yytext(), CompSym.PART );} +"part" { return alloy_sym(yytext(), CompSym.PART );} +"pred" { return alloy_sym(yytext(), CompSym.PRED );} +"private" { return alloy_sym(yytext(), CompSym.PRIVATE );} +"run" { return alloy_sym(yytext(), CompSym.RUN );} +"seq" { return alloy_sym(yytext(), CompSym.SEQ );} +"set" { return alloy_sym(yytext(), CompSym.SET );} +"sig" { return alloy_sym(yytext(), CompSym.SIG );} +"some" { return alloy_sym(yytext(), CompSym.SOME );} +"String" { return alloy_sym(yytext(), CompSym.STRING );} +"sum" { return alloy_sym(yytext(), CompSym.SUM );} +"this" { return alloy_sym(yytext(), CompSym.THIS );} +"univ" { return alloy_sym(yytext(), CompSym.UNIV );} + +[\"] ([^\\\"] | ("\\" .))* [\"] [\$0-9a-zA-Z_\'\"] [\$0-9a-zA-Z_\'\"]* { throw new ErrorSyntax(alloy_here(yytext()),"String literal cannot be followed by a legal identifier character."); } +[\"] ([^\\\"] | ("\\" .))* [\"] { return alloy_string(yytext()); } +[\"] ([^\\\"] | ("\\" .))* { throw new ErrorSyntax(alloy_here(yytext()),"String literal is missing its closing \" character"); } +[0-9][0-9]*[\$a-zA-Z_\'\"][\$0-9a-zA-Z_\'\"]* { throw new ErrorSyntax(alloy_here(yytext()),"Name cannot start with a number."); } +[0-9][0-9]* { return alloy_num (yytext()); } +[:jletter:][[:jletterdigit:]\'\"]* { return alloy_id (yytext()); } +//[\$a-zA-Z][\$0-9a-zA-Z_\'\"]* { return alloy_id (yytext()); } + +"/**" ~"*/" { } + +"/*" ~"*/" { } + +("//"|"--") [^\r\n]* [\r\n] { } + +("//"|"--") [^\r\n]* { } // This rule is shorter than the previous rule, + // so it will only apply if the final line of a file is missing the \n or \r character. + +[ \t\f\r\n] { } + +. { throw new ErrorSyntax(alloy_here(" "), "Syntax error at the "+yytext()+" character."); } diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/parser/CompFilter.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/parser/CompFilter.java new file mode 100644 index 00000000..a7fd672b --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/parser/CompFilter.java @@ -0,0 +1,241 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4compiler.parser; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.io.Reader; +import java_cup.runtime.Scanner; +import java_cup.runtime.Symbol; +import edu.mit.csail.sdg.alloy4.Err; +import edu.mit.csail.sdg.alloy4.ErrorFatal; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprConstant; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprVar; +import static edu.mit.csail.sdg.alloy4compiler.parser.CompSym.*; + +/** This class sits between the lexer and the parser. + * + *

+ * Reason: there are 3 sets of "special tokens" that the lexer will not output. + * But the Parser expects them. So this filter class observes the stream of + * tokens, and intelligently merges or changes some primitive tokens into special tokens. + * + *

+ * For more details, refer to the main documentation. + * But, very briefly, here are the 3 groups: + * + *

+ * (1) The lexer will generate only ALL, NO, LONE, ONE, SUM, SOME. + * It will not output ALL2, NO2, LONE2, ONE2, SUM2, SOME2. + * (The Filter class will change ONE into ONE2 when appropriate) + * + *

+ * (2) The lexer won't output NOTEQUALS, NOTIN, NOTLT, NOTLTE, NOTGT, NOTGTE. + * Instead it outputs them as separate tokens (eg. "NOT" "EQUALS"). + * (The Filter class is used to merge them into a single "NOTEQUALS" token) + * + *

+ * (3) The lexer won't output the 15 special arrows (eg. ONE_ARROW_ONE) + * Instead it outputs them as separate tokens (eg. "ONE", "ARROW", "ONE") + * (The Filter class is used to merge them into a single "ONE_ARROW_ONE" token) + */ + +final class CompFilter implements Scanner { + + //===================== PHASE 1 ================================================================================== + + /** The underlying lexer. */ + private final Scanner r; + + /** A list of tokens that we prefetched from the underlying lexer. */ + private final LinkedList undo = new LinkedList(); + + /** Stores the latest token passed from phase 1 to phase 2. */ + private Symbol last = null; + + /** Reads a token from the underlying lexer; if the undo list is not empty, we take it from there instead. */ + private Symbol myread() throws Err { + if (!undo.isEmpty()) return undo.removeFirst(); + try { + return r.next_token(); + } catch(Exception ex) { + if (ex instanceof Err) throw (Err)ex; else throw new ErrorFatal("IO error: "+ex.getMessage(), ex); + } + } + + /** Reads one or more tokens from the underlying lexer, transform them if necessarly. */ + public Symbol next_token() throws Err { + Symbol a = myread(), b; + int c; + if (last==null || (last.sym!=COLON && last.sym!=DISJ)) { + if (a.sym==NO) c=NO2; + else if (a.sym==ALL) c=ALL2; + else if (a.sym==SUM) c=SUM2; + else if (a.sym==LONE) c=LONE2; + else if (a.sym==ONE) c=ONE2; + else if (a.sym==SOME) c=SOME2; + else return last=a; + final ArrayList temp = new ArrayList(); + temp.add(b = myread()); + if (b.sym==PRIVATE) temp.add(b = myread()); + if (b.sym==DISJ || b.sym==PART || b.sym==EXH) temp.add(b = myread()); + while(b.sym==ID) { + temp.add(b = myread()); + if (b.sym==COMMA) temp.add(b = myread()); else if (b.sym==COLON) { a.sym=c; break; } else { break; } + } + for(int i=temp.size()-1; i>=0; i--) undo.add(0, temp.get(i)); + } + return last=a; + } + + /** Change a.pos to be the merger of a.pos and b.pos, then change a.sym to be sym, then return a. */ + private static Symbol merge(Symbol a, Symbol b, int sym) { + a.pos = a.pos.merge(b.pos); + a.sym = sym; + return a; + } + + /** Construct a filter for the tokens from the given file. */ + public CompFilter(CompModule module, List seenDollar, String filename, int lineOffset, Reader i) throws Err { + final CompLexer L = new CompLexer(i); + L.alloy_module = module; + L.alloy_filename = filename; + L.alloy_lineoffset = lineOffset; + L.alloy_seenDollar = seenDollar; + // Replace "ID : RUN/CHECK ID" with "RUN/CHECK ID ID" + // Replace "ID : RUN/CHECK {" WITH "RUN/CHECK ID {" + final Scanner A = new Scanner() { + private Symbol a, b, c, d; + public final Symbol next_token() throws Exception { + if (a==null) a=L.next_token(); if (a.sym==EOF) { b=a; c=a; d=a; } + if (b==null) b=L.next_token(); if (b.sym==EOF) { c=b; d=b; } + if (c==null) c=L.next_token(); if (c.sym==EOF) { d=c; } + if (d==null) d=L.next_token(); + if (a.sym==ID && b.sym==COLON && (c.sym==RUN || c.sym==CHECK) && (d.sym==ID || d.sym==LBRACE)) { + Symbol x=c; b=d; c=null; d=null; return x; + } + Symbol x=a; a=b; b=c; c=d; d=null; return x; + } + }; + // Merges "pred" "/" "xxx" into the actual symbol + // Merges "fun" "/" "xxx" into the actual symbol + // Merges ! { in = < <= > >= } into a single symbol + // Merges {..}=>{..} into a single symbol + final Scanner B = new Scanner() { + private Symbol undo; + public final Symbol next_token() throws Exception { + Symbol x = undo; + undo = null; + if (x==null) x = A.next_token(); + if (x.sym==NOT) { + Symbol y = A.next_token(); + if (y.sym==IN) return merge(x, y, NOTIN); + if (y.sym==EQUALS) return merge(x, y, NOTEQUALS); + if (y.sym==LT) return merge(x, y, NOTLT); + if (y.sym==LTE) return merge(x, y, NOTLTE); + if (y.sym==GT) return merge(x, y, NOTGT); + if (y.sym==GTE) return merge(x, y, NOTGTE); + undo = y; + } else if (x.sym==PRED) { + Symbol y = A.next_token(); + if (y.sym!=SLASH) { undo=y; return x; } + Symbol z = A.next_token(); + if (z.sym==ID && ((ExprVar)(z.value)).label.equals("totalOrder")) return merge(x, z, TOTALORDER); + undo = z; + } else if (x.sym==FUN) { + Symbol y = A.next_token(); + if (y.sym!=SLASH) { undo=y; return x; } + Symbol z = A.next_token(); + if (z.sym==ID && ((ExprVar)(z.value)).label.equals("add")) return merge(x, z, INTADD); + if (z.sym==ID && ((ExprVar)(z.value)).label.equals("sub")) return merge(x, z, INTSUB); + if (z.sym==ID && ((ExprVar)(z.value)).label.equals("mul")) return merge(x, z, INTMUL); + if (z.sym==ID && ((ExprVar)(z.value)).label.equals("div")) return merge(x, z, INTDIV); + if (z.sym==ID && ((ExprVar)(z.value)).label.equals("rem")) return merge(x, z, INTREM); + if (z.sym==ID && ((ExprVar)(z.value)).label.equals("min")) return merge(x, z, INTMIN); + if (z.sym==ID && ((ExprVar)(z.value)).label.equals("max")) return merge(x, z, INTMAX); + if (z.sym==ID && ((ExprVar)(z.value)).label.equals("next")) return merge(x, z, INTNEXT); + } else if (x.sym==ONE) { + Symbol y = A.next_token(); + if (y.sym!=ARROW) { undo=y; return x; } + Symbol z = A.next_token(); + if (z.sym==ONE) return merge(x, z, ONE_ARROW_ONE); + if (z.sym==LONE) return merge(x, z, ONE_ARROW_LONE); + if (z.sym==SOME) return merge(x, z, ONE_ARROW_SOME); + if (z.sym==SET) return merge(x, z, ONE_ARROW_ANY); else { undo=z; return merge(x, y, ONE_ARROW_ANY); } + } else if (x.sym==LONE) { + Symbol y = A.next_token(); + if (y.sym!=ARROW) { undo=y; return x; } + Symbol z = A.next_token(); + if (z.sym==ONE) return merge(x, z, LONE_ARROW_ONE); + if (z.sym==LONE) return merge(x, z, LONE_ARROW_LONE); + if (z.sym==SOME) return merge(x, z, LONE_ARROW_SOME); + if (z.sym==SET) return merge(x, z, LONE_ARROW_ANY); else { undo=z; return merge(x, y, LONE_ARROW_ANY); } + } else if (x.sym==SOME) { + Symbol y = A.next_token(); + if (y.sym!=ARROW) { undo=y; return x; } + Symbol z = A.next_token(); + if (z.sym==ONE) return merge(x, z, SOME_ARROW_ONE); + if (z.sym==LONE) return merge(x, z, SOME_ARROW_LONE); + if (z.sym==SOME) return merge(x, z, SOME_ARROW_SOME); + if (z.sym==SET) return merge(x, z, SOME_ARROW_ANY); else { undo=z; return merge(x, y, SOME_ARROW_ANY); } + } else if (x.sym==SET) { + Symbol y = A.next_token(); + if (y.sym!=ARROW) { undo=y; return x; } + Symbol z = A.next_token(); + if (z.sym==ONE) return merge(x, z, ANY_ARROW_ONE); + if (z.sym==LONE) return merge(x, z, ANY_ARROW_LONE); + if (z.sym==SOME) return merge(x, z, ANY_ARROW_SOME); + if (z.sym==SET) return merge(x, z, ARROW); else { undo=z; return merge(x, y, ARROW); } + } else if (x.sym==ARROW) { + Symbol z = A.next_token(); + if (z.sym==ONE) return merge(x, z, ANY_ARROW_ONE); + if (z.sym==LONE) return merge(x, z, ANY_ARROW_LONE); + if (z.sym==SOME) return merge(x, z, ANY_ARROW_SOME); + if (z.sym==SET) return merge(x, z, ARROW); else { undo=z; } + } + return x; + } + }; + // Merge "- number" into "-number" whenever it is not immediately following ")" "]" "}" DISJ TOTALORDER INT SUM ID NUMBER STR IDEN THIS INTMIN INTMAX INTNEXT UNIV SIGINT NONE + final Scanner C = new Scanner() { + private Symbol last, undo; + public final Symbol next_token() throws Exception { + Symbol x = undo; + undo = null; + if (x==null) x = B.next_token(); + if (last!=null) { + if (last.sym==RPAREN || last.sym==RBRACKET || last.sym==RBRACE || last.sym==DISJ || last.sym==TOTALORDER || last.sym==INT) return last = x; + if (last.sym==SUM || last.sym==ID || last.sym==NUMBER || last.sym==STR || last.sym==IDEN || last.sym==THIS) return last = x; + if (last.sym==INTMIN || last.sym==INTMAX || last.sym==INTNEXT || last.sym==UNIV || last.sym==SIGINT || last.sym==NONE) return last = x; + } + if (x.sym==MINUS) { + Symbol y = B.next_token(); + if (y.sym==NUMBER) { + ExprConstant num = (ExprConstant)(y.value); + y.pos = x.pos.merge(y.pos); + y.value = ExprConstant.Op.NUMBER.make(y.pos, 0 - num.num); + return last = y; + } + undo = y; + return last = x; + } + return last = x; + } + }; + this.r = C; + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/parser/CompLexer.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/parser/CompLexer.java new file mode 100644 index 00000000..b3da5ca2 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/parser/CompLexer.java @@ -0,0 +1,1213 @@ +/* The following code was generated by JFlex 1.4.1 on 8/16/11 10:01 PM */ + +// Alloy Analyzer 4 -- Copyright (c) 2006-2008, Felix Chang +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files +// (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, +// merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF +// OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +package edu.mit.csail.sdg.alloy4compiler.parser; + +import edu.mit.csail.sdg.alloy4.Err; +import edu.mit.csail.sdg.alloy4.ErrorSyntax; +import edu.mit.csail.sdg.alloy4.Pos; +import edu.mit.csail.sdg.alloy4.Version; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprConstant; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprVar; +import java.util.List; +import java_cup.runtime.*; + +/** Autogenerated by JFlex 1.4.1 */ + + +public final class CompLexer implements java_cup.runtime.Scanner { + + /** This character denotes the end of file */ + public static final int YYEOF = -1; + + /** initial size of the lookahead buffer */ + private static final int ZZ_BUFFERSIZE = 16384; + + /** lexical states */ + public static final int YYINITIAL = 0; + + /** + * Translates characters to character classes + */ + private static final String ZZ_CMAP_PACKED = + "\11\71\1\73\1\64\1\0\1\73\1\72\16\71\4\0\1\73\1\1"+ + "\1\62\1\2\1\70\1\0\1\3\1\65\1\4\1\5\1\6\1\7"+ + "\1\10\1\11\1\13\1\14\12\66\1\15\1\0\1\16\1\17\1\12"+ + "\1\0\1\20\10\70\1\56\11\70\1\61\7\70\1\21\1\63\1\22"+ + "\1\23\1\70\1\0\1\30\1\31\1\35\1\40\1\41\1\55\1\60"+ + "\1\43\1\45\1\46\1\44\1\36\1\50\1\37\1\47\1\54\1\57"+ + "\1\34\1\32\1\33\1\42\1\53\1\70\1\51\1\52\1\70\1\24"+ + "\1\25\1\26\1\27\41\71\2\0\4\67\4\0\1\67\2\0\1\71"+ + "\7\0\1\67\4\0\1\67\5\0\27\67\1\0\37\67\1\0\u013f\67"+ + "\31\0\162\67\4\0\14\67\16\0\5\67\11\0\1\67\21\0\130\71"+ + "\5\0\23\71\12\0\1\67\13\0\1\67\1\0\3\67\1\0\1\67"+ + "\1\0\24\67\1\0\54\67\1\0\46\67\1\0\5\67\4\0\202\67"+ + "\1\0\4\71\3\0\105\67\1\0\46\67\2\0\2\67\6\0\20\67"+ + "\41\0\46\67\2\0\1\67\7\0\47\67\11\0\21\71\1\0\27\71"+ + "\1\0\3\71\1\0\1\71\1\0\2\71\1\0\1\71\13\0\33\67"+ + "\5\0\3\67\15\0\4\71\14\0\6\71\13\0\32\67\5\0\13\67"+ + "\16\71\7\0\12\71\4\0\2\67\1\71\143\67\1\0\1\67\10\71"+ + "\1\0\6\71\2\67\2\71\1\0\4\71\2\67\12\71\3\67\2\0"+ + "\1\67\17\0\1\71\1\67\1\71\36\67\33\71\2\0\3\67\60\0"+ + "\46\67\13\71\1\67\u014f\0\3\71\66\67\2\0\1\71\1\67\20\71"+ + "\2\0\1\67\4\71\3\0\12\67\2\71\2\0\12\71\21\0\3\71"+ + "\1\0\10\67\2\0\2\67\2\0\26\67\1\0\7\67\1\0\1\67"+ + "\3\0\4\67\2\0\1\71\1\67\7\71\2\0\2\71\2\0\3\71"+ + "\11\0\1\71\4\0\2\67\1\0\3\67\2\71\2\0\12\71\4\67"+ + "\15\0\3\71\1\0\6\67\4\0\2\67\2\0\26\67\1\0\7\67"+ + "\1\0\2\67\1\0\2\67\1\0\2\67\2\0\1\71\1\0\5\71"+ + "\4\0\2\71\2\0\3\71\13\0\4\67\1\0\1\67\7\0\14\71"+ + "\3\67\14\0\3\71\1\0\11\67\1\0\3\67\1\0\26\67\1\0"+ + "\7\67\1\0\2\67\1\0\5\67\2\0\1\71\1\67\10\71\1\0"+ + "\3\71\1\0\3\71\2\0\1\67\17\0\2\67\2\71\2\0\12\71"+ + "\1\0\1\67\17\0\3\71\1\0\10\67\2\0\2\67\2\0\26\67"+ + "\1\0\7\67\1\0\2\67\1\0\5\67\2\0\1\71\1\67\6\71"+ + "\3\0\2\71\2\0\3\71\10\0\2\71\4\0\2\67\1\0\3\67"+ + "\4\0\12\71\1\0\1\67\20\0\1\71\1\67\1\0\6\67\3\0"+ + "\3\67\1\0\4\67\3\0\2\67\1\0\1\67\1\0\2\67\3\0"+ + "\2\67\3\0\3\67\3\0\10\67\1\0\3\67\4\0\5\71\3\0"+ + "\3\71\1\0\4\71\11\0\1\71\17\0\11\71\11\0\1\67\7\0"+ + "\3\71\1\0\10\67\1\0\3\67\1\0\27\67\1\0\12\67\1\0"+ + "\5\67\4\0\7\71\1\0\3\71\1\0\4\71\7\0\2\71\11\0"+ + "\2\67\4\0\12\71\22\0\2\71\1\0\10\67\1\0\3\67\1\0"+ + "\27\67\1\0\12\67\1\0\5\67\2\0\1\71\1\67\7\71\1\0"+ + "\3\71\1\0\4\71\7\0\2\71\7\0\1\67\1\0\2\67\4\0"+ + "\12\71\22\0\2\71\1\0\10\67\1\0\3\67\1\0\27\67\1\0"+ + "\20\67\4\0\6\71\2\0\3\71\1\0\4\71\11\0\1\71\10\0"+ + "\2\67\4\0\12\71\22\0\2\71\1\0\22\67\3\0\30\67\1\0"+ + "\11\67\1\0\1\67\2\0\7\67\3\0\1\71\4\0\6\71\1\0"+ + "\1\71\1\0\10\71\22\0\2\71\15\0\60\67\1\71\2\67\7\71"+ + "\4\0\10\67\10\71\1\0\12\71\47\0\2\67\1\0\1\67\2\0"+ + "\2\67\1\0\1\67\2\0\1\67\6\0\4\67\1\0\7\67\1\0"+ + "\3\67\1\0\1\67\1\0\1\67\2\0\2\67\1\0\4\67\1\71"+ + "\2\67\6\71\1\0\2\71\1\67\2\0\5\67\1\0\1\67\1\0"+ + "\6\71\2\0\12\71\2\0\2\67\42\0\1\67\27\0\2\71\6\0"+ + "\12\71\13\0\1\71\1\0\1\71\1\0\1\71\4\0\2\71\10\67"+ + "\1\0\42\67\6\0\24\71\1\0\2\71\4\67\4\0\10\71\1\0"+ + "\44\71\11\0\1\71\71\0\42\67\1\0\5\67\1\0\2\67\1\0"+ + "\7\71\3\0\4\71\6\0\12\71\6\0\6\67\4\71\106\0\46\67"+ + "\12\0\51\67\7\0\132\67\5\0\104\67\5\0\122\67\6\0\7\67"+ + "\1\0\77\67\1\0\1\67\1\0\4\67\2\0\7\67\1\0\1\67"+ + "\1\0\4\67\2\0\47\67\1\0\1\67\1\0\4\67\2\0\37\67"+ + "\1\0\1\67\1\0\4\67\2\0\7\67\1\0\1\67\1\0\4\67"+ + "\2\0\7\67\1\0\7\67\1\0\27\67\1\0\37\67\1\0\1\67"+ + "\1\0\4\67\2\0\7\67\1\0\47\67\1\0\23\67\16\0\11\71"+ + "\56\0\125\67\14\0\u026c\67\2\0\10\67\12\0\32\67\5\0\113\67"+ + "\3\0\3\67\17\0\15\67\1\0\4\67\3\71\13\0\22\67\3\71"+ + "\13\0\22\67\2\71\14\0\15\67\1\0\3\67\1\0\2\71\14\0"+ + "\64\67\40\71\3\0\1\67\3\0\2\67\1\71\2\0\12\71\41\0"+ + "\3\71\2\0\12\71\6\0\130\67\10\0\51\67\1\71\126\0\35\67"+ + "\3\0\14\71\4\0\14\71\12\0\12\71\36\67\2\0\5\67\u038b\0"+ + "\154\67\224\0\234\67\4\0\132\67\6\0\26\67\2\0\6\67\2\0"+ + "\46\67\2\0\6\67\2\0\10\67\1\0\1\67\1\0\1\67\1\0"+ + "\1\67\1\0\37\67\2\0\65\67\1\0\7\67\1\0\1\67\3\0"+ + "\3\67\1\0\7\67\3\0\4\67\2\0\6\67\4\0\15\67\5\0"+ + "\3\67\1\0\7\67\17\0\4\71\32\0\5\71\20\0\2\67\23\0"+ + "\1\67\13\0\4\71\6\0\6\71\1\0\1\67\15\0\1\67\40\0"+ + "\22\67\36\0\15\71\4\0\1\71\3\0\6\71\27\0\1\67\4\0"+ + "\1\67\2\0\12\67\1\0\1\67\3\0\5\67\6\0\1\67\1\0"+ + "\1\67\1\0\1\67\1\0\4\67\1\0\3\67\1\0\7\67\3\0"+ + "\3\67\5\0\5\67\26\0\44\67\u0e81\0\3\67\31\0\11\67\6\71"+ + "\1\0\5\67\2\0\5\67\4\0\126\67\2\0\2\71\2\0\3\67"+ + "\1\0\137\67\5\0\50\67\4\0\136\67\21\0\30\67\70\0\20\67"+ + "\u0200\0\u19b6\67\112\0\u51a6\67\132\0\u048d\67\u0773\0\u2ba4\67\u215c\0\u012e\67"+ + "\2\0\73\67\225\0\7\67\14\0\5\67\5\0\1\67\1\71\12\67"+ + "\1\0\15\67\1\0\5\67\1\0\1\67\1\0\2\67\1\0\2\67"+ + "\1\0\154\67\41\0\u016b\67\22\0\100\67\2\0\66\67\50\0\15\67"+ + "\3\0\20\71\20\0\4\71\17\0\2\67\30\0\3\67\31\0\1\67"+ + "\6\0\5\67\1\0\207\67\2\0\1\71\4\0\1\67\13\0\12\71"+ + "\7\0\32\67\4\0\1\67\1\0\32\67\12\0\132\67\3\0\6\67"+ + "\2\0\6\67\2\0\6\67\2\0\3\67\3\0\2\67\3\0\2\67"+ + "\22\0\3\71\4\0"; + + /** + * Translates characters to character classes + */ + private static final char [] ZZ_CMAP = zzUnpackCMap(ZZ_CMAP_PACKED); + + /** + * Translates DFA states to action switch labels. + */ + private static final int [] ZZ_ACTION = zzUnpackAction(); + + private static final String ZZ_ACTION_PACKED_0 = + "\1\0\1\1\1\2\1\3\1\4\1\5\1\6\1\7"+ + "\1\10\1\11\1\12\1\13\1\14\1\15\1\16\1\17"+ + "\1\20\1\21\1\22\1\23\1\24\1\25\1\26\1\27"+ + "\1\30\23\31\1\32\1\33\1\34\1\35\1\36\1\33"+ + "\1\37\1\40\1\41\1\0\1\42\1\43\1\44\1\45"+ + "\1\46\1\45\1\47\1\31\1\50\14\31\1\51\5\31"+ + "\1\52\3\31\1\47\12\31\1\53\1\0\1\54\1\55"+ + "\2\0\1\56\2\31\1\57\1\35\1\60\1\61\1\62"+ + "\1\63\1\64\2\31\1\65\1\31\1\66\1\31\1\2"+ + "\6\31\1\67\2\31\1\70\2\31\1\56\1\71\6\31"+ + "\1\72\1\73\1\74\1\31\1\75\1\0\1\33\2\31"+ + "\1\76\1\77\1\31\1\100\1\101\1\102\1\103\1\104"+ + "\4\31\1\105\1\106\1\31\1\107\1\31\1\110\1\111"+ + "\1\31\1\112\3\31\1\113\13\31\1\114\4\31\1\115"+ + "\1\31\1\116\2\31\1\117\2\31\1\120\1\121\1\31"+ + "\1\46\1\31\1\122\1\123\1\102\3\31\1\110\1\67"; + + private static int [] zzUnpackAction() { + int [] result = new int[210]; + int offset = 0; + offset = zzUnpackAction(ZZ_ACTION_PACKED_0, offset, result); + return result; + } + + private static int zzUnpackAction(String packed, int offset, int [] result) { + int i = 0; /* index in packed string */ + int j = offset; /* index in unpacked array */ + int l = packed.length(); + while (i < l) { + int count = packed.charAt(i++); + int value = packed.charAt(i++); + do result[j++] = value; while (--count > 0); + } + return j; + } + + + /** + * Translates a state to a row index in the transition table + */ + private static final int [] ZZ_ROWMAP = zzUnpackRowMap(); + + private static final String ZZ_ROWMAP_PACKED_0 = + "\0\0\0\74\0\74\0\74\0\170\0\74\0\74\0\74"+ + "\0\264\0\74\0\360\0\u012c\0\74\0\u0168\0\u01a4\0\u01e0"+ + "\0\u021c\0\74\0\74\0\74\0\74\0\74\0\u0258\0\74"+ + "\0\74\0\u0294\0\u02d0\0\u030c\0\u0348\0\u0384\0\u03c0\0\u03fc"+ + "\0\u0438\0\u0474\0\u04b0\0\u04ec\0\u0528\0\u0564\0\u05a0\0\u05dc"+ + "\0\u0618\0\u0654\0\u0690\0\u06cc\0\u0708\0\74\0\u0744\0\74"+ + "\0\74\0\u0780\0\74\0\u07bc\0\74\0\u07f8\0\74\0\74"+ + "\0\74\0\u0834\0\74\0\74\0\74\0\u0870\0\u08ac\0\u08e8"+ + "\0\u0924\0\u0960\0\u099c\0\u09d8\0\u0a14\0\u0a50\0\u0a8c\0\u0ac8"+ + "\0\u0b04\0\u0b40\0\u0b7c\0\u0bb8\0\u0bf4\0\u0c30\0\u0c6c\0\u0ca8"+ + "\0\u0ce4\0\u0d20\0\u0d5c\0\u0d98\0\u0dd4\0\u0528\0\u0e10\0\u0e4c"+ + "\0\u0e88\0\u0ec4\0\u0f00\0\u0f3c\0\u0f78\0\u0fb4\0\u0ff0\0\u102c"+ + "\0\u1068\0\u10a4\0\u10e0\0\74\0\u111c\0\u1158\0\74\0\u1194"+ + "\0\u11d0\0\u0528\0\u0528\0\u0528\0\u0528\0\u0528\0\u0528\0\u0528"+ + "\0\u120c\0\u1248\0\u0528\0\u1284\0\u0528\0\u12c0\0\u0528\0\u12fc"+ + "\0\u1338\0\u1374\0\u13b0\0\u13ec\0\u1428\0\u1464\0\u14a0\0\u14dc"+ + "\0\u0528\0\u1518\0\u1554\0\u0528\0\u0528\0\u1590\0\u15cc\0\u1608"+ + "\0\u1644\0\u1680\0\u16bc\0\u0528\0\u0528\0\u0528\0\u16f8\0\u1068"+ + "\0\u1734\0\u111c\0\u1770\0\u17ac\0\u0528\0\u0528\0\u17e8\0\u0528"+ + "\0\u0528\0\u1824\0\u0528\0\u0528\0\u1860\0\u189c\0\u18d8\0\u1914"+ + "\0\u0528\0\u0528\0\u1950\0\u0528\0\u198c\0\u19c8\0\u0528\0\u1a04"+ + "\0\u0528\0\u1a40\0\u1a7c\0\u1ab8\0\u0528\0\u1af4\0\u1b30\0\u1b6c"+ + "\0\u1ba8\0\u1be4\0\u1c20\0\u1c5c\0\u1c98\0\u1cd4\0\u1d10\0\u1d4c"+ + "\0\u0528\0\u1d88\0\u1dc4\0\u1e00\0\u1e3c\0\u0528\0\u1e78\0\u0528"+ + "\0\u1eb4\0\u1ef0\0\u0528\0\u1f2c\0\u1f68\0\u0528\0\u0528\0\u1fa4"+ + "\0\u0528\0\u1fe0\0\u0528\0\u0528\0\u0528\0\u201c\0\u2058\0\u2094"+ + "\0\u0528\0\u0528"; + + private static int [] zzUnpackRowMap() { + int [] result = new int[210]; + int offset = 0; + offset = zzUnpackRowMap(ZZ_ROWMAP_PACKED_0, offset, result); + return result; + } + + private static int zzUnpackRowMap(String packed, int offset, int [] result) { + int i = 0; /* index in packed string */ + int j = offset; /* index in unpacked array */ + int l = packed.length(); + while (i < l) { + int high = packed.charAt(i++) << 16; + result[j++] = high | packed.charAt(i++); + } + return j; + } + + /** + * The transition table of the DFA + */ + private static final int [] ZZ_TRANS = zzUnpackTrans(); + + private static final String ZZ_TRANS_PACKED_0 = + "\1\2\1\3\1\4\1\5\1\6\1\7\1\10\1\11"+ + "\1\12\1\13\1\14\1\15\1\16\1\17\1\20\1\21"+ + "\1\22\1\23\1\24\1\25\1\26\1\27\1\30\1\31"+ + "\1\32\1\33\1\34\1\35\1\36\1\37\1\40\1\41"+ + "\1\42\1\43\1\44\2\45\1\46\1\45\1\47\1\50"+ + "\3\45\1\51\1\52\1\53\2\45\1\54\1\55\1\2"+ + "\1\56\1\2\1\57\2\45\1\2\2\56\77\0\1\60"+ + "\77\0\1\61\75\0\1\62\1\63\73\0\1\64\4\0"+ + "\1\65\62\0\1\66\5\0\1\62\71\0\1\67\2\0"+ + "\1\15\73\0\1\70\1\71\1\72\66\0\1\73\3\0"+ + "\1\74\102\0\1\75\76\0\1\45\1\76\1\77\3\45"+ + "\1\100\1\101\23\45\2\0\5\45\32\0\12\45\1\102"+ + "\20\45\2\0\5\45\32\0\11\45\1\103\1\104\2\45"+ + "\1\105\1\45\1\106\13\45\2\0\5\45\32\0\13\45"+ + "\1\107\17\45\2\0\5\45\32\0\12\45\1\110\20\45"+ + "\2\0\5\45\32\0\13\45\1\111\17\45\2\0\5\45"+ + "\32\0\11\45\1\112\5\45\1\113\13\45\2\0\5\45"+ + "\32\0\17\45\1\114\13\45\2\0\5\45\32\0\15\45"+ + "\1\115\15\45\2\0\5\45\32\0\6\45\1\116\1\117"+ + "\11\45\1\120\11\45\2\0\5\45\32\0\7\45\1\121"+ + "\23\45\2\0\5\45\32\0\33\45\2\0\5\45\32\0"+ + "\7\45\1\122\1\123\7\45\1\124\4\45\1\125\5\45"+ + "\2\0\5\45\32\0\4\45\1\126\2\45\1\127\14\45"+ + "\1\130\6\45\2\0\5\45\32\0\17\45\1\131\13\45"+ + "\2\0\5\45\32\0\1\132\3\45\1\133\26\45\2\0"+ + "\5\45\32\0\1\134\11\45\1\135\4\45\1\136\13\45"+ + "\2\0\5\45\32\0\7\45\1\137\23\45\2\0\5\45"+ + "\32\0\3\45\1\140\27\45\2\0\5\45\2\0\62\55"+ + "\1\141\1\142\10\55\30\0\33\143\2\0\1\143\1\57"+ + "\1\0\1\143\3\0\64\62\1\56\5\62\1\56\1\62"+ + "\12\0\1\144\61\0\6\145\1\146\65\145\12\0\1\147"+ + "\111\0\2\45\1\150\30\45\2\0\5\45\32\0\2\45"+ + "\1\151\30\45\2\0\5\45\32\0\6\45\1\152\24\45"+ + "\2\0\5\45\32\0\10\45\1\153\22\45\2\0\5\45"+ + "\32\0\3\45\1\154\27\45\2\0\5\45\32\0\3\45"+ + "\1\155\23\45\1\156\3\45\2\0\5\45\32\0\20\45"+ + "\1\157\12\45\2\0\5\45\32\0\30\45\1\160\2\45"+ + "\2\0\5\45\32\0\20\45\1\161\12\45\2\0\5\45"+ + "\32\0\15\45\1\162\15\45\2\0\5\45\32\0\7\45"+ + "\1\163\23\45\2\0\5\45\32\0\11\45\1\164\21\45"+ + "\2\0\5\45\32\0\3\45\1\165\27\45\2\0\5\45"+ + "\32\0\7\45\1\166\23\45\2\0\5\45\32\0\3\45"+ + "\1\167\3\45\1\170\23\45\2\0\5\45\32\0\2\45"+ + "\1\171\30\45\2\0\5\45\32\0\2\45\1\172\30\45"+ + "\2\0\5\45\32\0\12\45\1\173\20\45\2\0\5\45"+ + "\32\0\1\174\2\45\1\175\7\45\1\176\10\45\1\177"+ + "\6\45\2\0\5\45\32\0\15\45\1\200\15\45\2\0"+ + "\5\45\32\0\3\45\1\201\27\45\2\0\5\45\32\0"+ + "\11\45\1\202\21\45\2\0\5\45\32\0\24\45\1\203"+ + "\6\45\2\0\5\45\32\0\25\45\1\204\5\45\2\0"+ + "\5\45\32\0\11\45\1\205\21\45\2\0\5\45\32\0"+ + "\11\45\1\206\21\45\2\0\5\45\32\0\10\45\1\207"+ + "\22\45\2\0\5\45\32\0\4\45\1\210\26\45\2\0"+ + "\5\45\32\0\11\45\1\211\3\45\1\212\15\45\2\0"+ + "\5\45\32\0\5\45\1\213\25\45\2\0\5\45\32\0"+ + "\7\45\1\214\23\45\2\0\5\45\32\0\4\45\1\215"+ + "\26\45\2\0\5\45\32\0\3\45\1\216\27\45\2\0"+ + "\5\45\32\0\4\45\1\217\26\45\2\0\5\45\32\0"+ + "\33\220\2\0\2\220\1\0\1\220\3\0\64\55\1\0"+ + "\7\55\30\0\33\143\2\0\2\143\1\0\1\143\3\0"+ + "\6\145\1\221\73\145\1\221\5\145\1\222\57\145\30\0"+ + "\3\45\1\223\27\45\2\0\5\45\32\0\11\45\1\224"+ + "\21\45\2\0\5\45\32\0\11\45\1\225\21\45\2\0"+ + "\5\45\32\0\2\45\1\226\30\45\2\0\5\45\32\0"+ + "\5\45\1\227\25\45\2\0\5\45\32\0\11\45\1\230"+ + "\21\45\2\0\5\45\32\0\11\45\1\231\21\45\2\0"+ + "\5\45\32\0\16\45\1\232\14\45\2\0\5\45\32\0"+ + "\11\45\1\233\21\45\2\0\5\45\32\0\20\45\1\234"+ + "\12\45\2\0\5\45\32\0\5\45\1\235\25\45\2\0"+ + "\5\45\32\0\11\45\1\236\21\45\2\0\5\45\32\0"+ + "\1\237\32\45\2\0\5\45\32\0\11\45\1\240\21\45"+ + "\2\0\5\45\32\0\23\45\1\241\7\45\2\0\5\45"+ + "\32\0\7\45\1\242\23\45\2\0\5\45\32\0\6\45"+ + "\1\243\24\45\2\0\5\45\32\0\7\45\1\244\23\45"+ + "\2\0\5\45\32\0\12\45\1\245\20\45\2\0\5\45"+ + "\32\0\3\45\1\246\27\45\2\0\5\45\32\0\10\45"+ + "\1\247\22\45\2\0\5\45\32\0\23\45\1\250\7\45"+ + "\2\0\5\45\32\0\3\45\1\251\27\45\2\0\5\45"+ + "\32\0\15\45\1\252\15\45\2\0\5\45\2\0\6\145"+ + "\1\221\5\145\1\56\57\145\30\0\4\45\1\253\26\45"+ + "\2\0\5\45\32\0\4\45\1\254\26\45\2\0\5\45"+ + "\32\0\14\45\1\255\16\45\2\0\5\45\32\0\17\45"+ + "\1\256\13\45\2\0\5\45\32\0\3\45\1\257\27\45"+ + "\2\0\5\45\32\0\7\45\1\260\23\45\2\0\5\45"+ + "\32\0\12\45\1\261\20\45\2\0\5\45\32\0\5\45"+ + "\1\262\25\45\2\0\5\45\32\0\15\45\1\263\15\45"+ + "\2\0\5\45\32\0\6\45\1\264\24\45\2\0\5\45"+ + "\32\0\15\45\1\265\15\45\2\0\5\45\32\0\1\266"+ + "\32\45\2\0\5\45\32\0\7\45\1\267\23\45\2\0"+ + "\5\45\32\0\1\270\32\45\2\0\5\45\32\0\3\45"+ + "\1\271\27\45\2\0\5\45\32\0\15\45\1\272\15\45"+ + "\2\0\5\45\32\0\6\45\1\273\24\45\2\0\5\45"+ + "\32\0\10\45\1\274\22\45\2\0\5\45\32\0\2\45"+ + "\1\275\30\45\2\0\5\45\32\0\3\45\1\276\27\45"+ + "\2\0\5\45\32\0\11\45\1\277\21\45\2\0\5\45"+ + "\32\0\11\45\1\300\21\45\2\0\5\45\32\0\3\45"+ + "\1\301\27\45\2\0\5\45\32\0\3\45\1\302\27\45"+ + "\2\0\5\45\32\0\30\45\1\303\2\45\2\0\5\45"+ + "\32\0\5\45\1\304\25\45\2\0\5\45\32\0\7\45"+ + "\1\305\23\45\2\0\5\45\32\0\22\45\1\306\10\45"+ + "\2\0\5\45\32\0\2\45\1\307\30\45\2\0\5\45"+ + "\32\0\3\45\1\310\27\45\2\0\5\45\32\0\2\45"+ + "\1\311\30\45\2\0\5\45\32\0\15\45\1\312\15\45"+ + "\2\0\5\45\32\0\11\45\1\313\21\45\2\0\5\45"+ + "\32\0\3\45\1\314\27\45\2\0\5\45\32\0\3\45"+ + "\1\315\27\45\2\0\5\45\32\0\15\45\1\316\15\45"+ + "\2\0\5\45\32\0\17\45\1\317\13\45\2\0\5\45"+ + "\32\0\23\45\1\320\7\45\2\0\5\45\32\0\7\45"+ + "\1\321\23\45\2\0\5\45\32\0\11\45\1\322\21\45"+ + "\2\0\5\45\2\0"; + + private static int [] zzUnpackTrans() { + int [] result = new int[8400]; + int offset = 0; + offset = zzUnpackTrans(ZZ_TRANS_PACKED_0, offset, result); + return result; + } + + private static int zzUnpackTrans(String packed, int offset, int [] result) { + int i = 0; /* index in packed string */ + int j = offset; /* index in unpacked array */ + int l = packed.length(); + while (i < l) { + int count = packed.charAt(i++); + int value = packed.charAt(i++); + value--; + do result[j++] = value; while (--count > 0); + } + return j; + } + + + /* error codes */ + private static final int ZZ_UNKNOWN_ERROR = 0; + private static final int ZZ_NO_MATCH = 1; + private static final int ZZ_PUSHBACK_2BIG = 2; + + /* error messages for the codes above */ + private static final String ZZ_ERROR_MSG[] = { + "Unkown internal scanner error", + "Error: could not match input", + "Error: pushback value was too large" + }; + + /** + * ZZ_ATTRIBUTE[aState] contains the attributes of state aState + */ + private static final int [] ZZ_ATTRIBUTE = zzUnpackAttribute(); + + private static final String ZZ_ATTRIBUTE_PACKED_0 = + "\1\0\3\11\1\1\3\11\1\1\1\11\2\1\1\11"+ + "\4\1\5\11\1\1\2\11\24\1\1\11\1\1\2\11"+ + "\1\1\1\11\1\1\1\11\1\0\3\11\1\1\3\11"+ + "\44\1\1\0\1\1\1\11\2\0\1\11\51\1\1\0"+ + "\101\1"; + + private static int [] zzUnpackAttribute() { + int [] result = new int[210]; + int offset = 0; + offset = zzUnpackAttribute(ZZ_ATTRIBUTE_PACKED_0, offset, result); + return result; + } + + private static int zzUnpackAttribute(String packed, int offset, int [] result) { + int i = 0; /* index in packed string */ + int j = offset; /* index in unpacked array */ + int l = packed.length(); + while (i < l) { + int count = packed.charAt(i++); + int value = packed.charAt(i++); + do result[j++] = value; while (--count > 0); + } + return j; + } + + /** the input device */ + private java.io.Reader zzReader; + + /** the current state of the DFA */ + private int zzState; + + /** the current lexical state */ + private int zzLexicalState = YYINITIAL; + + /** this buffer contains the current text to be matched and is + the source of the yytext() string */ + private char zzBuffer[] = new char[ZZ_BUFFERSIZE]; + + /** the textposition at the last accepting state */ + private int zzMarkedPos; + + /** the textposition at the last state to be included in yytext */ + private int zzPushbackPos; + + /** the current text position in the buffer */ + private int zzCurrentPos; + + /** startRead marks the beginning of the yytext() string in the buffer */ + private int zzStartRead; + + /** endRead marks the last character in the buffer, that has been read + from input */ + private int zzEndRead; + + /** number of newlines encountered up to the start of the matched text */ + private int yyline; + + /** the number of characters up to the start of the matched text */ + private int yychar; + + /** + * the number of characters from the last newline up to the start of the + * matched text + */ + private int yycolumn; + + /** + * zzAtBOL == true <=> the scanner is currently at the beginning of a line + */ + private boolean zzAtBOL = true; + + /** zzAtEOF == true <=> the scanner is at the EOF */ + private boolean zzAtEOF; + + /** denotes if the user-EOF-code has already been executed */ + private boolean zzEOFDone; + + /* user code: */ + public String alloy_filename=""; + public int alloy_lineoffset=0; // If not zero, it is added to the current LINE NUMBER + public List alloy_seenDollar; + public CompModule alloy_module; + private final Pos alloy_here(String txt) { + return new Pos(alloy_filename,yycolumn+1,yyline+1+alloy_lineoffset,yycolumn+txt.length(),yyline+1); + } + private final Symbol alloy_sym(String txt, int type) { + Pos p = alloy_here(txt); return new Symbol(type, p, p); + } + private final Symbol alloy_string(String txt) throws Err { + Pos p = alloy_here(txt); + if (!Version.experimental) throw new ErrorSyntax(p, "String literal is not currently supported."); + StringBuilder sb = new StringBuilder(txt.length()); + for(int i=0; i=txt.length()) throw new ErrorSyntax(p, "String literal cannot end with a single \\"); + c = txt.charAt(i); + if (c=='n') c='\n'; else if (c!='\'' && c!='\"' && c!='\\') throw new ErrorSyntax(p, "String literal currenty only supports\nfour escape sequences: \\\\, \\n, \\\', and \\\""); + } + sb.append(c); + } + txt = sb.toString(); + if (txt.length()==2) throw new ErrorSyntax(p, "Empty string is not allowed; try rewriting your model to use an empty set instead."); + return new Symbol(CompSym.STR, p, ExprConstant.Op.STRING.make(p, txt)); + } + private final Symbol alloy_id(String txt) throws Err { + Pos p=alloy_here(txt); + if (alloy_seenDollar.size()==0 && txt.indexOf('$')>=0) alloy_seenDollar.add(null); + return new Symbol(CompSym.ID, p, ExprVar.make(p,txt)); + } + private final Symbol alloy_num(String txt) throws Err { + Pos p=alloy_here(txt); + int n=0; + try { + n=Integer.parseInt(txt); + } catch(NumberFormatException ex) { + throw new ErrorSyntax(p, "The number "+txt+" is too large to be stored in a Java integer"); + } + return new Symbol(CompSym.NUMBER, p, ExprConstant.Op.NUMBER.make(p, n)); + } + + + /** + * Creates a new scanner + * There is also a java.io.InputStream version of this constructor. + * + * @param in the java.io.Reader to read input from. + */ + public CompLexer(java.io.Reader in) { + this.zzReader = in; + } + + /** + * Creates a new scanner. + * There is also java.io.Reader version of this constructor. + * + * @param in the java.io.Inputstream to read input from. + */ + public CompLexer(java.io.InputStream in) { + this(new java.io.InputStreamReader(in)); + } + + /** + * Unpacks the compressed character translation table. + * + * @param packed the packed character translation table + * @return the unpacked character translation table + */ + private static char [] zzUnpackCMap(String packed) { + char [] map = new char[0x10000]; + int i = 0; /* index in packed string */ + int j = 0; /* index in unpacked array */ + while (i < 1766) { + int count = packed.charAt(i++); + char value = packed.charAt(i++); + do map[j++] = value; while (--count > 0); + } + return map; + } + + + /** + * Refills the input buffer. + * + * @return false, iff there was new input. + * + * @exception java.io.IOException if any I/O-Error occurs + */ + private boolean zzRefill() throws java.io.IOException { + + /* first: make room (if you can) */ + if (zzStartRead > 0) { + System.arraycopy(zzBuffer, zzStartRead, + zzBuffer, 0, + zzEndRead-zzStartRead); + + /* translate stored positions */ + zzEndRead-= zzStartRead; + zzCurrentPos-= zzStartRead; + zzMarkedPos-= zzStartRead; + zzPushbackPos-= zzStartRead; + zzStartRead = 0; + } + + /* is the buffer big enough? */ + if (zzCurrentPos >= zzBuffer.length) { + /* if not: blow it up */ + char newBuffer[] = new char[zzCurrentPos*2]; + System.arraycopy(zzBuffer, 0, newBuffer, 0, zzBuffer.length); + zzBuffer = newBuffer; + } + + /* finally: fill the buffer with new input */ + int numRead = zzReader.read(zzBuffer, zzEndRead, + zzBuffer.length-zzEndRead); + + if (numRead < 0) { + return true; + } + else { + zzEndRead+= numRead; + return false; + } + } + + + /** + * Closes the input stream. + */ + public final void yyclose() throws java.io.IOException { + zzAtEOF = true; /* indicate end of file */ + zzEndRead = zzStartRead; /* invalidate buffer */ + + if (zzReader != null) + zzReader.close(); + } + + + /** + * Resets the scanner to read from a new input stream. + * Does not close the old reader. + * + * All internal variables are reset, the old input stream + * cannot be reused (internal buffer is discarded and lost). + * Lexical state is set to ZZ_INITIAL. + * + * @param reader the new input stream + */ + public final void yyreset(java.io.Reader reader) { + zzReader = reader; + zzAtBOL = true; + zzAtEOF = false; + zzEndRead = zzStartRead = 0; + zzCurrentPos = zzMarkedPos = zzPushbackPos = 0; + yyline = yychar = yycolumn = 0; + zzLexicalState = YYINITIAL; + } + + + /** + * Returns the current lexical state. + */ + public final int yystate() { + return zzLexicalState; + } + + + /** + * Enters a new lexical state + * + * @param newState the new lexical state + */ + public final void yybegin(int newState) { + zzLexicalState = newState; + } + + + /** + * Returns the text matched by the current regular expression. + */ + public final String yytext() { + return new String( zzBuffer, zzStartRead, zzMarkedPos-zzStartRead ); + } + + + /** + * Returns the character at position pos from the + * matched text. + * + * It is equivalent to yytext().charAt(pos), but faster + * + * @param pos the position of the character to fetch. + * A value from 0 to yylength()-1. + * + * @return the character at position pos + */ + public final char yycharat(int pos) { + return zzBuffer[zzStartRead+pos]; + } + + + /** + * Returns the length of the matched text region. + */ + public final int yylength() { + return zzMarkedPos-zzStartRead; + } + + + /** + * Reports an error that occured while scanning. + * + * In a wellformed scanner (no or only correct usage of + * yypushback(int) and a match-all fallback rule) this method + * will only be called with things that "Can't Possibly Happen". + * If this method is called, something is seriously wrong + * (e.g. a JFlex bug producing a faulty scanner etc.). + * + * Usual syntax/scanner level error handling should be done + * in error fallback rules. + * + * @param errorCode the code of the errormessage to display + */ + private void zzScanError(int errorCode) { + String message; + try { + message = ZZ_ERROR_MSG[errorCode]; + } + catch (ArrayIndexOutOfBoundsException e) { + message = ZZ_ERROR_MSG[ZZ_UNKNOWN_ERROR]; + } + + throw new Error(message); + } + + + /** + * Pushes the specified amount of characters back into the input stream. + * + * They will be read again by then next call of the scanning method + * + * @param number the number of characters to be read again. + * This number must not be greater than yylength()! + */ + public void yypushback(int number) { + if ( number > yylength() ) + zzScanError(ZZ_PUSHBACK_2BIG); + + zzMarkedPos -= number; + } + + + /** + * Contains user EOF-code, which will be executed exactly once, + * when the end of file is reached + */ + private void zzDoEOF() throws java.io.IOException { + if (!zzEOFDone) { + zzEOFDone = true; + yyclose(); + } + } + + + /** + * Resumes scanning until the next regular expression is matched, + * the end of input is encountered or an I/O-Error occurs. + * + * @return the next token + * @exception java.io.IOException if any I/O-Error occurs + */ + public java_cup.runtime.Symbol next_token() throws java.io.IOException, Err { + int zzInput; + int zzAction; + + // cached fields: + int zzCurrentPosL; + int zzMarkedPosL; + int zzEndReadL = zzEndRead; + char [] zzBufferL = zzBuffer; + char [] zzCMapL = ZZ_CMAP; + + int [] zzTransL = ZZ_TRANS; + int [] zzRowMapL = ZZ_ROWMAP; + int [] zzAttrL = ZZ_ATTRIBUTE; + + while (true) { + zzMarkedPosL = zzMarkedPos; + + boolean zzR = false; + for (zzCurrentPosL = zzStartRead; zzCurrentPosL < zzMarkedPosL; + zzCurrentPosL++) { + switch (zzBufferL[zzCurrentPosL]) { + case '\u000B': + case '\u000C': + case '\u0085': + case '\u2028': + case '\u2029': + yyline++; + yycolumn = 0; + zzR = false; + break; + case '\r': + yyline++; + yycolumn = 0; + zzR = true; + break; + case '\n': + if (zzR) + zzR = false; + else { + yyline++; + yycolumn = 0; + } + break; + default: + zzR = false; + yycolumn++; + } + } + + if (zzR) { + // peek one character ahead if it is \n (if we have counted one line too much) + boolean zzPeek; + if (zzMarkedPosL < zzEndReadL) + zzPeek = zzBufferL[zzMarkedPosL] == '\n'; + else if (zzAtEOF) + zzPeek = false; + else { + boolean eof = zzRefill(); + zzEndReadL = zzEndRead; + zzMarkedPosL = zzMarkedPos; + zzBufferL = zzBuffer; + if (eof) + zzPeek = false; + else + zzPeek = zzBufferL[zzMarkedPosL] == '\n'; + } + if (zzPeek) yyline--; + } + zzAction = -1; + + zzCurrentPosL = zzCurrentPos = zzStartRead = zzMarkedPosL; + + zzState = zzLexicalState; + + + zzForAction: { + while (true) { + + if (zzCurrentPosL < zzEndReadL) + zzInput = zzBufferL[zzCurrentPosL++]; + else if (zzAtEOF) { + zzInput = YYEOF; + break zzForAction; + } + else { + // store back cached positions + zzCurrentPos = zzCurrentPosL; + zzMarkedPos = zzMarkedPosL; + boolean eof = zzRefill(); + // get translated positions and possibly new buffer + zzCurrentPosL = zzCurrentPos; + zzMarkedPosL = zzMarkedPos; + zzBufferL = zzBuffer; + zzEndReadL = zzEndRead; + if (eof) { + zzInput = YYEOF; + break zzForAction; + } + else { + zzInput = zzBufferL[zzCurrentPosL++]; + } + } + int zzNext = zzTransL[ zzRowMapL[zzState] + zzCMapL[zzInput] ]; + if (zzNext == -1) break zzForAction; + zzState = zzNext; + + int zzAttributes = zzAttrL[zzState]; + if ( (zzAttributes & 1) == 1 ) { + zzAction = zzState; + zzMarkedPosL = zzCurrentPosL; + if ( (zzAttributes & 8) == 8 ) break zzForAction; + } + + } + } + + // store back cached position + zzMarkedPos = zzMarkedPosL; + + switch (zzAction < 0 ? zzAction : ZZ_ACTION[zzAction]) { + case 4: + { return alloy_sym(yytext(), CompSym.AMPERSAND ); + } + case 84: break; + case 50: + { return alloy_sym(yytext(), CompSym.SEQ ); + } + case 85: break; + case 38: + { return alloy_sym(yytext(), CompSym.IMPLIES ); + } + case 86: break; + case 23: + { return alloy_sym(yytext(), CompSym.RBRACE ); + } + case 87: break; + case 80: + { return alloy_sym(yytext(), CompSym.EXACTLY ); + } + case 88: break; + case 53: + { return alloy_sym(yytext(), CompSym.RUN ); + } + case 89: break; + case 36: + { return alloy_sym(yytext(), CompSym.SHL ); + } + case 90: break; + case 35: + { return alloy_sym(yytext(), CompSym.DOMAIN ); + } + case 91: break; + case 60: + { return alloy_sym(yytext(), CompSym.SIGINT ); + } + case 92: break; + case 24: + { return alloy_sym(yytext(), CompSym.TILDE ); + } + case 93: break; + case 21: + { return alloy_sym(yytext(), CompSym.LBRACE ); + } + case 94: break; + case 7: + { return alloy_sym(yytext(), CompSym.STAR ); + } + case 95: break; + case 30: + { return alloy_sym(yytext(), CompSym.PLUSPLUS ); + } + case 96: break; + case 58: + { return alloy_sym(yytext(), CompSym.FUN ); + } + case 97: break; + case 79: + { return alloy_sym(yytext(), CompSym.STRING ); + } + case 98: break; + case 55: + { return alloy_sym(yytext(), CompSym.EXH ); + } + case 99: break; + case 56: + { return alloy_sym(yytext(), CompSym.INT ); + } + case 100: break; + case 52: + { return alloy_sym(yytext(), CompSym.SIG ); + } + case 101: break; + case 73: + { return alloy_sym(yytext(), CompSym.PRED ); + } + case 102: break; + case 25: + { return alloy_id (yytext()); + } + case 103: break; + case 64: + { return alloy_sym(yytext(), CompSym.LONE ); + } + case 104: break; + case 41: + { return alloy_sym(yytext(), CompSym.NO ); + } + case 105: break; + case 9: + { return alloy_sym(yytext(), CompSym.COMMA ); + } + case 106: break; + case 40: + { return alloy_sym(yytext(), CompSym.AS ); + } + case 107: break; + case 63: + { return alloy_sym(yytext(), CompSym.THIS ); + } + case 108: break; + case 13: + { return alloy_sym(yytext(), CompSym.SLASH ); + } + case 109: break; + case 48: + { return alloy_sym(yytext(), CompSym.BUT ); + } + case 110: break; + case 43: + { return alloy_string(yytext()); + } + case 111: break; + case 66: + { return alloy_sym(yytext(), CompSym.DISJ ); + } + case 112: break; + case 32: + { return alloy_sym(yytext(), CompSym.SHA ); + } + case 113: break; + case 39: + { return alloy_sym(yytext(), CompSym.OR ); + } + case 114: break; + case 45: + { return alloy_sym(yytext(), CompSym.SHR ); + } + case 115: break; + case 82: + { return alloy_sym(yytext(), CompSym.PRIVATE ); + } + case 116: break; + case 31: + { return alloy_sym(yytext(), CompSym.ARROW ); + } + case 117: break; + case 33: + { return alloy_sym(yytext(), CompSym.GTE ); + } + case 118: break; + case 20: + { return alloy_sym(yytext(), CompSym.CARET ); + } + case 119: break; + case 46: + { return alloy_sym(yytext(), CompSym.IFF ); + } + case 120: break; + case 67: + { return alloy_sym(yytext(), CompSym.ELSE ); + } + case 121: break; + case 3: + { return alloy_sym(yytext(), CompSym.HASH ); + } + case 122: break; + case 12: + { return alloy_sym(yytext(), CompSym.DOT ); + } + case 123: break; + case 22: + { return alloy_sym(yytext(), CompSym.BAR ); + } + case 124: break; + case 77: + { return alloy_sym(yytext(), CompSym.EXPECT ); + } + case 125: break; + case 8: + { return alloy_sym(yytext(), CompSym.PLUS ); + } + case 126: break; + case 37: + { return alloy_sym(yytext(), CompSym.LTE ); + } + case 127: break; + case 57: + { return alloy_sym(yytext(), CompSym.ONE ); + } + case 128: break; + case 71: + { return alloy_sym(yytext(), CompSym.OPEN ); + } + case 129: break; + case 11: + { return alloy_sym(yytext(), CompSym.GT ); + } + case 130: break; + case 78: + { return alloy_sym(yytext(), CompSym.MODULE ); + } + case 131: break; + case 14: + { return alloy_sym(yytext(), CompSym.COLON ); + } + case 132: break; + case 51: + { return alloy_sym(yytext(), CompSym.SUM ); + } + case 133: break; + case 72: + { return alloy_sym(yytext(), CompSym.PART ); + } + case 134: break; + case 16: + { return alloy_sym(yytext(), CompSym.EQUALS ); + } + case 135: break; + case 81: + { return alloy_sym(yytext(), CompSym.EXTENDS ); + } + case 136: break; + case 1: + { throw new ErrorSyntax(alloy_here(" "), "Syntax error at the "+yytext()+" character."); + } + case 137: break; + case 26: + { throw new ErrorSyntax(alloy_here(yytext()),"String literal is missing its closing \" character"); + } + case 138: break; + case 59: + { return alloy_sym(yytext(), CompSym.FOR ); + } + case 139: break; + case 28: + { return alloy_num (yytext()); + } + case 140: break; + case 70: + { return alloy_sym(yytext(), CompSym.IDEN ); + } + case 141: break; + case 15: + { return alloy_sym(yytext(), CompSym.LT ); + } + case 142: break; + case 17: + { return alloy_sym(yytext(), CompSym.AT ); + } + case 143: break; + case 34: + { return alloy_sym(yytext(), CompSym.RANGE ); + } + case 144: break; + case 69: + { return alloy_sym(yytext(), CompSym.UNIV ); + } + case 145: break; + case 2: + { return alloy_sym(yytext(), CompSym.NOT ); + } + case 146: break; + case 49: + { return alloy_sym(yytext(), CompSym.SET ); + } + case 147: break; + case 6: + { return alloy_sym(yytext(), CompSym.RPAREN ); + } + case 148: break; + case 42: + { return alloy_sym(yytext(), CompSym.IN ); + } + case 149: break; + case 75: + { return alloy_sym(yytext(), CompSym.CHECK ); + } + case 150: break; + case 29: + { return alloy_sym(yytext(), CompSym.AND ); + } + case 151: break; + case 62: + { return alloy_sym(yytext(), CompSym.SOME ); + } + case 152: break; + case 74: + { return alloy_sym(yytext(), CompSym.FACT ); + } + case 153: break; + case 10: + { return alloy_sym(yytext(), CompSym.MINUS ); + } + case 154: break; + case 5: + { return alloy_sym(yytext(), CompSym.LPAREN ); + } + case 155: break; + case 65: + { return alloy_sym(yytext(), CompSym.NONE ); + } + case 156: break; + case 18: + { return alloy_sym(yytext(), CompSym.LBRACKET ); + } + case 157: break; + case 19: + { return alloy_sym(yytext(), CompSym.RBRACKET ); + } + case 158: break; + case 68: + { return alloy_sym(yytext(), CompSym.ENUM ); + } + case 159: break; + case 47: + { return alloy_sym(yytext(), CompSym.ALL ); + } + case 160: break; + case 83: + { return alloy_sym(yytext(), CompSym.ABSTRACT ); + } + case 161: break; + case 76: + { return alloy_sym(yytext(), CompSym.ASSERT ); + } + case 162: break; + case 61: + { throw new ErrorSyntax(alloy_here(yytext()),"String literal cannot be followed by a legal identifier character."); + } + case 163: break; + case 54: + { return alloy_sym(yytext(), CompSym.LET ); + } + case 164: break; + case 27: + { + } + case 165: break; + case 44: + { throw new ErrorSyntax(alloy_here(yytext()),"Name cannot start with a number."); + } + case 166: break; + default: + if (zzInput == YYEOF && zzStartRead == zzCurrentPos) { + zzAtEOF = true; + zzDoEOF(); + { return new Symbol(CompSym.EOF, alloy_here(" "), alloy_here(" ")); + } + } + else { + zzScanError(ZZ_NO_MATCH); + } + } + } + } + + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/parser/CompModule.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/parser/CompModule.java new file mode 100644 index 00000000..8af0984f --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/parser/CompModule.java @@ -0,0 +1,1598 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4compiler.parser; + +import static edu.mit.csail.sdg.alloy4.Util.asList; +import static edu.mit.csail.sdg.alloy4compiler.ast.Attr.AttrType.ABSTRACT; +import static edu.mit.csail.sdg.alloy4compiler.ast.Attr.AttrType.ONE; +import static edu.mit.csail.sdg.alloy4compiler.ast.Attr.AttrType.PRIVATE; +import static edu.mit.csail.sdg.alloy4compiler.ast.Attr.AttrType.SUBSET; +import static edu.mit.csail.sdg.alloy4compiler.ast.Attr.AttrType.SUBSIG; +import static edu.mit.csail.sdg.alloy4compiler.ast.Attr.AttrType.WHERE; +import static edu.mit.csail.sdg.alloy4compiler.ast.Sig.NONE; +import static edu.mit.csail.sdg.alloy4compiler.ast.Sig.SEQIDX; +import static edu.mit.csail.sdg.alloy4compiler.ast.Sig.SIGINT; +import static edu.mit.csail.sdg.alloy4compiler.ast.Sig.STRING; +import static edu.mit.csail.sdg.alloy4compiler.ast.Sig.UNIV; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import edu.mit.csail.sdg.alloy4.A4Reporter; +import edu.mit.csail.sdg.alloy4.ConstList; +import edu.mit.csail.sdg.alloy4.Env; +import edu.mit.csail.sdg.alloy4.Err; +import edu.mit.csail.sdg.alloy4.ErrorFatal; +import edu.mit.csail.sdg.alloy4.ErrorSyntax; +import edu.mit.csail.sdg.alloy4.ErrorType; +import edu.mit.csail.sdg.alloy4.ErrorWarning; +import edu.mit.csail.sdg.alloy4.JoinableList; +import edu.mit.csail.sdg.alloy4.Pair; +import edu.mit.csail.sdg.alloy4.Pos; +import edu.mit.csail.sdg.alloy4.SafeList; +import edu.mit.csail.sdg.alloy4.Util; +import edu.mit.csail.sdg.alloy4.Version; +import edu.mit.csail.sdg.alloy4.ConstList.TempList; +import edu.mit.csail.sdg.alloy4compiler.ast.Attr; +import edu.mit.csail.sdg.alloy4compiler.ast.Browsable; +import edu.mit.csail.sdg.alloy4compiler.ast.Command; +import edu.mit.csail.sdg.alloy4compiler.ast.CommandScope; +import edu.mit.csail.sdg.alloy4compiler.ast.Decl; +import edu.mit.csail.sdg.alloy4compiler.ast.Expr; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprBad; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprBadCall; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprBadJoin; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprBinary; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprCall; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprChoice; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprConstant; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprHasName; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprITE; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprLet; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprList; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprQt; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprUnary; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprVar; +import edu.mit.csail.sdg.alloy4compiler.ast.Func; +import edu.mit.csail.sdg.alloy4compiler.ast.Module; +import edu.mit.csail.sdg.alloy4compiler.ast.Sig; +import edu.mit.csail.sdg.alloy4compiler.ast.Type; +import edu.mit.csail.sdg.alloy4compiler.ast.VisitReturn; +import edu.mit.csail.sdg.alloy4compiler.ast.Sig.Field; +import edu.mit.csail.sdg.alloy4compiler.ast.Sig.PrimSig; +import edu.mit.csail.sdg.alloy4compiler.ast.Sig.SubsetSig; + +/** Mutable; this class represents an Alloy module; equals() uses object identity. */ + +public final class CompModule extends Browsable implements Module { + + // These fields are shared by all Modules that point to each other + + /** Maps each actual Sig to the original parser-generated Sig that it came from. */ + private final LinkedHashMap new2old; + + /** Maps each parser-generated Sig to its original list of field declarations. */ + private final LinkedHashMap> old2fields; + + /** Maps each parser-generated Sig to its original appended facts. */ + private final LinkedHashMap old2appendedfacts; + + /** Maps each Sig to the CompModule it belongs to. */ + private final HashMap sig2module; + + /** The list of CompModules. */ + private final List allModules; + + /** The list of sigs in the entire world whose scope shall be deemed "exact" */ + private final Set exactSigs; + + /** This stores a set of global values; given a unresolved name, we query this map first before all else. */ + private final Map globals; + + /** This stores the meta signature "sig$" */ + private final PrimSig metaSig; + + /** This stores the meta signature "field$" */ + private final PrimSig metaField; + + //============================================================================================================================// + + /** This field is used during a depth-first search of the dag-of-module(s) to mark which modules have been visited. */ + private Object visitedBy = null; + + /** The world that this CompModule belongs to. */ + private final CompModule world; + + /** The simplest path pointing to this Module ("" if this is the main module) */ + public final String path; + + /** Return the simplest path pointing to this Module ("" if this is the main module) */ + public String path() { return path; } + + /** 1: has seen the "module" line + * 3: has seen the "sig/pred/fun/fact/assert/check/run" commands + */ + private int status = 0; + + /** The position of the "MODULE" line at the top of the file; Pos.UNKNOWN if the line has not been parsed from the file yet. */ + private Pos modulePos = Pos.UNKNOWN; + + /** The text of the "MODULE" line at the top of the file; "unknown" if the line has not be parsed from the file yet. */ + private String moduleName = "unknown"; + + /** Whether we have seen a name containing a dollar sign or not. */ + boolean seenDollar = false; + + /** Each param is mapped to its corresponding Sig (or null if we have not resolved it). */ + private final Map params = new LinkedHashMap(); // Must be LinkedHashMap since the order matters + + /** Each alias is mapped to its corresponding "open" statement. */ + private final Map opens = new LinkedHashMap(); + + /** Each sig name is mapped to its corresponding SigAST. */ + private final Map sigs = new LinkedHashMap(); + + /** The list of params in this module whose scope shall be deemed "exact" */ + private final List exactParams = new ArrayList(); + + /** The current name resolution mode (0=pure) (1=Alloy 4.1.3 and older) (2=new) */ + int resolution = 1; + + /** Each func name is mapped to a nonempty list of FunAST objects. */ + private final Map> funcs = new LinkedHashMap>(); + + /** Each macro name is mapped to a MacroAST object. */ + private final Map macros = new LinkedHashMap(); + + /** Each assertion name is mapped to its Expr. */ + private final Map asserts = new LinkedHashMap(); + + /** The list of facts; each fact is either an untypechecked Exp or a typechecked Expr. */ + private final List> facts = new ArrayList>(); + + /** The list of (CommandName,Command,Expr) triples; NOTE: duplicate command names are allowed. */ + private final List commands = new ArrayList(); + + //============================================================================================================================// + + /** Mutable; this class represents the current typechecking context. */ + static final class Context extends VisitReturn { + + /** The place where warnings should go; can be null if we don't care about storing the warnings. */ + private List warns; + + /** The module that the current context is in. */ + final CompModule rootmodule; + + /** Nonnull if we are typechecking a field declaration or a sig appended facts. */ + Sig rootsig = null; + + /** Nonnull if we are typechecking a field declaration. */ + private Decl rootfield = null; + + /** True if we are typechecking a function's parameter declarations or return declaration. */ + private boolean rootfunparam = false; + + /** Nonnull if we are typechecking a function's body. */ + private Func rootfunbody = null; + + /** This maps local names (eg. let/quantification variables and function parameters) to the objects they refer to. */ + private final Env env = new Env(); + + /** The level of macro substitution recursion. */ + public final int unrolls; + + /** Associates the given name with the given expression in the current lexical scope. */ + final void put(String name, Expr value) { + env.put(name,value); + } + + /** Removes the latest binding for the given name from the current lexical scope. */ + final void remove(String name) { + env.remove(name); + } + + /** Construct a new Context with an empty lexical scope. */ + Context(CompModule rootModule, List warns) { + this(rootModule, warns, 20); // 20 is a reasonable threshold; deeper than this would likely be too big to solve anyway + } + + /** Construct a new Context with an empty lexical scope. */ + Context(CompModule rootModule, List warns, int unrolls) { + this.rootmodule = rootModule; + this.unrolls = unrolls; + this.warns = warns; + } + + /** Resolve the given name to get a collection of Expr and Func objects. */ + public Expr resolve(final Pos pos, final String name) { + if (name.indexOf('/') >= 0) { + String n = name.startsWith("this/") ? name.substring(5) : name; + CompModule mod = rootmodule; + while(true) { + int i = n.indexOf('/'); + if (i<0) { + Macro m = mod.macros.get(n); + if (m==null || (m.isPrivate!=null && mod!=rootmodule)) break; else return m.changePos(pos); + } + String alias = n.substring(0,i); + Open uu = mod.opens.get(alias); + if (uu==null || uu.realModule==null || uu.isPrivate) break; + n = n.substring(i+1); + mod = uu.realModule; + } + } + Expr match = env.get(name); + if (match==null) { + boolean ambiguous = false; + StringBuilder sb = new StringBuilder(); + for(CompModule m: rootmodule.getAllNameableModules()) { + Macro mac = m.macros.get(name); + if (mac==null) continue; + if (match!=null) ambiguous=true; else match=mac; + sb.append("\n").append(m.path.length()==0 ? "this" : m.path).append("/").append(name); + } + if (ambiguous) return new ExprBad(pos, name, new ErrorType(pos, "There are multiple macros with the same name:"+sb)); + } + if (match==null) match = rootmodule.globals.get(name); + if (match!=null) { + if (match instanceof Macro) return ((Macro)match).changePos(pos); + match = ExprUnary.Op.NOOP.make(pos, match); + return ExprChoice.make(pos, asList(match), asList(name)); + } + Expr th = env.get("this"); + if (th!=null) th = ExprUnary.Op.NOOP.make(pos, th); + TempList ch = new TempList(); + TempList re = new TempList(); + Expr ans = rootmodule.populate(ch, re, rootfield, rootsig, rootfunparam, rootfunbody, pos, name, th); + if (ans!=null) return ans; + if (ch.size()==0) return new ExprBad(pos, name, hint(pos, name)); else return ExprChoice.make(pos, ch.makeConst(), re.makeConst()); + } + + Expr check(Expr x) throws Err { + return visitThis(x); + } + + /** Returns true if the function's parameters have reasonable intersection with the list of arguments. + *
If args.length() > f.params.size(), the extra arguments are ignored by this check + */ + private static boolean applicable(Func f, List args) { + if (f.count() > args.size()) return false; + int i=0; + for(ExprVar d: f.params()) { + Type param=d.type(), arg=args.get(i).type(); + i++; + // The reason we don't directly compare "arg.arity()" with "param.arity()" + // is because the arguments may not be fully resolved yet. + if (!arg.hasCommonArity(param)) return false; + if (arg.hasTuple() && param.hasTuple() && !arg.intersects(param)) return false; + } + return true; + } + + private Expr process(Pos pos, Pos closingBracket, Pos rightPos, List choices, List oldReasons, Expr arg) { + TempList list = new TempList(choices.size()); + TempList reasons = new TempList(choices.size()); + for(int i=0; i newargs = Util.append(bc.args, arg); + if (applicable(bc.fun, newargs)) + y=ExprCall.make(bc.pos, bc.closingBracket, bc.fun, newargs, bc.extraWeight); + else + y=ExprBadCall.make(bc.pos, bc.closingBracket, bc.fun, newargs, bc.extraWeight); + } else { + y=ExprBinary.Op.JOIN.make(pos, closingBracket, arg, y); + } + } else { + y=ExprBinary.Op.JOIN.make(pos, closingBracket, arg, x); + } + list.add(y); + reasons.add(oldReasons.get(i)); + } + return ExprChoice.make(rightPos, list.makeConst(), reasons.makeConst()); + } + + /** {@inheritDoc} */ + @Override public Expr visit(ExprList x) throws Err { + TempList temp = new TempList(x.args.size()); + for(int i=0; i decls = new TempList(x.decls.size()); + boolean isMetaSig=false, isMetaField=false; + for(Decl d: x.decls) { + Expr exp = visitThis(d.expr).resolve_as_set(warns); + if (exp.mult==0 && exp.type().arity()==1) exp = ExprUnary.Op.ONEOF.make(null, exp); + if (exp.errors.isEmpty()) { + if (exp.type().isSubtypeOf(rootmodule.metaSig().plus(rootmodule.metaField()).type())) { + isMetaSig = exp.type().intersects(rootmodule.metaSig().type()); + isMetaField = exp.type().intersects(rootmodule.metaField().type()); + } + } + // Below is a special case to allow more fine-grained typechecking when we see "all x:field$" or "some x:field$" + boolean some = (x.op==ExprQt.Op.SOME), compre = (x.op==ExprQt.Op.COMPREHENSION); + if (x.decls.size()==1 && d.names.size()==1 && isOneOf(exp) && (x.op==ExprQt.Op.ALL || some || compre) && (isMetaSig || isMetaField)) { + ExprVar v = (ExprVar)(d.names.get(0)); + // Prevent warnings + List saved = warns; + warns = null; + // Now duplicate the body for each possible Meta Atom binding + Expr answer = null; + if (isMetaSig) for(PrimSig child: rootmodule.metaSig().children()) if (child.type().intersects(exp.type())) { + put(v.label, child); + Expr sub = visitThis(x.sub); + remove(v.label); + if (compre) answer = child.in(exp).and(sub).ite(child, Sig.NONE).plus(answer); + else if (some) answer = child.in(exp).and(sub).or(answer); + else answer = child.in(exp).implies(sub).and(answer); + } + if (isMetaField) for(PrimSig child: rootmodule.metaField().children()) if (child.type().intersects(exp.type())) { + put(v.label, child); + Expr sub = visitThis(x.sub); + remove(v.label); + if (compre) answer = child.in(exp).and(sub).ite(child, Sig.NONE).plus(answer); + else if (some) answer = child.in(exp).and(sub).or(answer); + else answer = child.in(exp).implies(sub).and(answer); + } + if (answer==null) answer = (compre ? Sig.NONE : (some ? ExprConstant.FALSE : ExprConstant.TRUE)); else answer = answer.resolve(answer.type(), null); + // Now, wrap the body in an ExprLet expression to prevent any more warnings by outer code + ExprVar combinedAnswer = ExprVar.make(Pos.UNKNOWN, "", answer.type()); + Expr returnValue = ExprLet.make(Pos.UNKNOWN, combinedAnswer, answer, combinedAnswer); + // Restore the "warns" array, then return the answer + warns = saved; + return returnValue; + } + // Above is a special case to allow more fine-grained typechecking when we see "all x:field$" or "some x:field$" + TempList n = new TempList(d.names.size()); + for(ExprHasName v: d.names) n.add(ExprVar.make(v.pos, v.label, exp.type())); + Decl dd = new Decl(d.isPrivate, d.disjoint, d.disjoint2, n.makeConst(), exp); + for(ExprHasName newname: dd.names) put(newname.label, newname); + decls.add(dd); + } + Expr sub = visitThis(x.sub); + if (x.op==ExprQt.Op.SUM) sub=sub.resolve_as_int(warns); else sub=sub.resolve_as_formula(warns); + for(Decl d: decls.makeConst()) for(ExprHasName v: d.names) remove(v.label); + return x.op.make(x.pos, x.closingBracket, decls.makeConst(), sub); + } + + /** {@inheritDoc} */ + @Override public Expr visit(ExprVar x) throws Err { + Expr obj = resolve(x.pos, x.label); + if (obj instanceof Macro) return ((Macro)obj).instantiate(this, warns); else return obj; + } + + /** {@inheritDoc} */ + @Override public Expr visit(ExprUnary x) throws Err { + return x.op.make(x.pos, visitThis(x.sub)); + } + + /** {@inheritDoc} */ + @Override public Expr visit(ExprCall x) { return x; } + + /** {@inheritDoc} */ + @Override public Expr visit(ExprConstant x) { return x; } + + /** {@inheritDoc} */ + @Override public Expr visit(Sig x) { return x; } + + /** {@inheritDoc} */ + @Override public Expr visit(Field x) { return x; } + + } + + //============================================================================================================================// + + /** Mutable; this class represents an untypechecked Alloy module import statement; equals() uses object identity. */ + public static final class Open { + + /** The position in the original model where this "open" statement was declared; never null. */ + public final Pos pos; + + /** The alias for this open declaration; always a nonempty string. */ + public final String alias; + + /** The unmodifiable list of instantiating arguments. */ + public final ConstList args; + + /** The relative filename for the file being imported, without the final ".als" part; always a nonempty string. */ + public final String filename; + + /** Whether this is a private open or not. */ + final boolean isPrivate; + + /** The actual Module object that it points to; null until we resolve it. */ + private CompModule realModule = null; + + /** Returns the actual Module object that it points to; null if we have not resolved it. */ + public CompModule getRealModule() { return realModule; } + + /** Constructs an Open object. */ + private Open(Pos pos, boolean isPrivate, String alias, ConstList args, String filename) { + this.pos=pos; this.isPrivate=isPrivate; this.alias=alias; this.args=args; this.filename=filename; + } + + /** Connect this OPEN statement to a module that it points to. */ + void connect(CompModule realModule) throws Err { + if (this.realModule!=null && this.realModule!=realModule) throw new ErrorFatal("Internal error (import mismatch)"); + this.realModule=realModule; + } + } + + //============================================================================================================================// + + /** Constructs a new CompModule object + * @param world - the world that this CompModule belongs to (null if this is the beginning of a new World) + * @param filename - the filename corresponding to this module + * @param path - one of the path pointing to this module + */ + CompModule(CompModule world, String filename, String path) throws Err { + if (world==null) { + if (path.length()>0) throw new ErrorFatal("Root module misparsed by parser."); + this.world = this; + new2old = new LinkedHashMap(); + old2fields = new LinkedHashMap>(); + old2appendedfacts = new LinkedHashMap(); + sig2module = new HashMap(); + allModules = new ArrayList(); + exactSigs = new LinkedHashSet(); + globals = new LinkedHashMap(); + metaSig = new PrimSig("this/sig$", Attr.ABSTRACT, Attr.META); + metaField = new PrimSig("this/field$", Attr.ABSTRACT, Attr.META); + } else { + this.world = world; + new2old = world.new2old; + old2fields = world.old2fields; + old2appendedfacts = world.old2appendedfacts; + sig2module = world.sig2module; + allModules = world.allModules; + exactSigs = world.exactSigs; + globals = world.globals; + metaSig = world.metaSig; + metaField = world.metaField; + } + this.path = path; + if (filename!=null && filename.length()>0) this.modulePos=new Pos(filename,1,1); + } + + /** {@inheritDoc} */ + @Override public final Pos pos() { return modulePos; } + + /** {@inheritDoc} */ + @Override public final Pos span() { return modulePos; } + + /** {@inheritDoc} */ + @Override public String getHTML() { + StringBuilder sb = new StringBuilder("module ").append(path).append(" "); + Util.encodeXML(sb, modulePos.filename); + return sb.append("").toString(); + } + + /** {@inheritDoc} */ + @Override public List getSubnodes() { + ArrayList ans = new ArrayList(); + ArrayList x; + if (opens.size()>0) { + x = new ArrayList(opens.size()); + for(Open e: opens.values()) if (!x.contains(e.realModule)) x.add(e.realModule); + ans.add(make(""+x.size()+(x.size()>1?" opens":" open"), x)); + } + if (sigs.size()>0) { + x = new ArrayList(sigs.values()); + ans.add(make(""+x.size()+(x.size()>1?" sigs":" sig"), x)); + } + if (funcs.size()>0) { + ArrayList x2 = new ArrayList(funcs.size()); + x = new ArrayList(funcs.size()); + for(ArrayList e: funcs.values()) for(Func f: e) if (f.isPred) x.add(f); else x2.add(f); + if (x.size()>0) ans.add(make(""+x.size()+(x.size()>1?" preds":" pred"), x)); + if (x2.size()>0) ans.add(make(""+x2.size()+(x2.size()>1?" funs":" fun"), x2)); + } + if (commands.size()>0) { + ArrayList x2 = new ArrayList(commands.size()); + x = new ArrayList(commands.size()); + for(Command e: commands) if (e.check) x.add(e); else x2.add(e); + if (x.size()>0) ans.add(make(""+x.size()+(x.size()>1?" checks":" check"), x)); + if (x2.size()>0) ans.add(make(""+x2.size()+(x2.size()>1?" runs":" run"), x2)); + } + if (facts.size()>0) { + x = new ArrayList(facts.size()); + for(Pair e: facts) x.add(make(e.b.span(), e.b.span(), "fact " + e.a + "", e.b)); + ans.add(make(""+x.size()+(x.size()>1?" facts":" fact"), x)); + } + if (asserts.size()>0) { + x = new ArrayList(asserts.size()); + for(Map.Entry e: asserts.entrySet()) { + Pos sp = e.getValue().span(); + x.add(make(sp, sp, "assert "+e.getKey(), e.getValue())); + } + ans.add(make(""+x.size()+(x.size()>1?" asserts":" assert"), x)); + } + return ans; + } + + /** Returns the meta signature "sig$" (or null if such a sig does not exist) */ + public PrimSig metaSig() { return world.metaSig; } + + /** Returns the meta signature "field$" (or null if such a sig does not exist) */ + public PrimSig metaField() { return world.metaField; } + + private static String base(String string) { + int i = string.lastIndexOf('/'); + return i<0 ? string : string.substring(i+1); + } + + private static String base(Sig sig) { return base(sig.label); } + + /** Generate an error message saying the given keyword is no longer supported. */ + static ErrorSyntax hint (Pos pos, String name) { + String msg="The name \""+name+"\" cannot be found."; + if ("exh".equals(name) || "exhaustive".equals(name) || "part".equals(name) || "partition".equals(name)) + msg=msg+" If you are migrating from Alloy 3, please see Help->QuickGuide on how to translate models that use the \"" + +name+"\" keyword."; + return new ErrorSyntax(pos, msg); + } + + /** Parse one expression by starting fromt this module as the root module. */ + public Expr parseOneExpressionFromString(String input) throws Err, FileNotFoundException, IOException { + Map fc = new LinkedHashMap(); + fc.put("", "run {\n"+input+"}"); // We prepend the line "run{" + CompModule m = CompParser.alloy_parseStream(new ArrayList(), null, fc, null, -1, "", "", 1); + if (m.funcs.size()==0) throw new ErrorSyntax("The input does not correspond to an Alloy expression."); + Expr body = m.funcs.values().iterator().next().get(0).getBody(); + Context cx = new Context(this, null); + body = cx.check(body); + body = body.resolve(body.type(), null); + if (body.errors.size()>0) throw body.errors.pick(); else return body; + } + + /** Throw an exception if the name is already used, or has @ or /, or is univ/Int/none. */ + private void dup(Pos pos, String name, boolean checkSig) throws Err { + if (name.length()==0) throw new ErrorSyntax(pos, "Name cannot be empty"); + if (name.indexOf('@')>=0) throw new ErrorSyntax(pos, "Name cannot contain the \'@\' character"); + if (name.indexOf('/')>=0) throw new ErrorSyntax(pos, "Name cannot contain the \'/\' character"); + if (name.equals("univ")) throw new ErrorSyntax(pos, "\'univ\' is a reserved keyword."); + if (name.equals("Int")) throw new ErrorSyntax(pos, "\'Int\' is a reserved keyword."); + if (name.equals("none")) throw new ErrorSyntax(pos, "\'none\' is a reserved keyword."); + if (checkSig && (params.containsKey(name) || sigs.containsKey(name))) + throw new ErrorSyntax(pos, "\""+name+"\" is already the name of a sig/parameter in this module."); + } + + /** Throw an exception if there are more than 1 match; return nonnull if only one match; return null if no match. */ + private Object unique (Pos pos, String name, List objs) throws Err { + if (objs.size()==0) return null; + if (objs.size()==1) return objs.get(0); + StringBuilder msg = new StringBuilder("The name \"").append(name); + msg.append("\" is ambiguous.\n" + "There are ").append(objs.size()).append(" choices:"); + for(int i=0; i ans, Object key) { + if (visitedBy==key) return; + visitedBy=key; + ans.add(this); + for(Open i: opens.values()) if (!(level>0 && i.isPrivate)) { + CompModule m = i.realModule; + if (m!=null) m.getHelper(level<0 ? (-1) : (level+1), ans, key); + } + } + + /** Return the list containing THIS MODULE and all modules reachable from this module. */ + public SafeList getAllReachableModules() { + SafeList ans=new SafeList(); + getHelper(-1, ans, new Object()); // The object must be new, since we need it to be a unique key + return ans.dup(); + } + + /** Return the list of all relative filenames included from this MODULE. */ + public List getAllReachableModulesFilenames() { + Set set = new LinkedHashSet(); + for(Open o: opens.values()) set.add(o.filename); + return new ArrayList(set); + } + + /** Return the list containing THIS MODULE and all modules nameable from this module. */ + private SafeList getAllNameableModules() { + SafeList ans=new SafeList(); + getHelper(0, ans, new Object()); // The object must be new, since we need it to be a unique key + return ans.dup(); + } + + /** Return the list containing UNIV, SIGINT, SEQIDX, STRING, NONE, and all sigs defined in this module or a reachable submodule. */ + public ConstList getAllReachableSigs() { + TempList x = new TempList(); + x.add(UNIV); + x.add(SIGINT); + x.add(SEQIDX); + x.add(STRING); + x.add(NONE); + for(CompModule m:getAllReachableModules()) x.addAll(m.sigs.values()); + return x.makeConst(); + } + + /** Lookup non-fully-qualified Sig/Func/Assertion from the current module; it skips PARAMs. */ + private List getRawNQS (CompModule start, final int r, String name) { + // (r&1)!=0 => Sig, (r&2) != 0 => assertion, (r&4)!=0 => Func + List ans=new ArrayList(); + for(CompModule m:getAllNameableModules()) { + if ((r&1)!=0) { Sig x=m.sigs.get(name); if (x!=null) if (m==start || x.isPrivate==null) ans.add(x); } + if ((r&2)!=0) { Expr x=m.asserts.get(name); if (x!=null) ans.add(x); } + if ((r&4)!=0) { ArrayList x=m.funcs.get(name); if (x!=null) for(Func y:x) if (m==start || y.isPrivate==null) ans.add(y); } + } + return ans; + } + + /** Lookup a fully-qualified Sig/Func/Assertion from the current module; it skips PARAMs. */ + private List getRawQS (final int r, String name) { + // (r&1)!=0 => Sig, (r&2) != 0 => assertion, (r&4)!=0 => Func + List ans=new ArrayList(); + CompModule u=this; + if (name.startsWith("this/")) name=name.substring(5); + for(int level=0; ;level++) { + int i=name.indexOf('/'); + if (i<0) { + if ((r&1)!=0) { Sig x=u.sigs.get(name); if (x!=null) if (level==0 || x.isPrivate==null) ans.add(x); } + if ((r&2)!=0) { Expr x=u.asserts.get(name); if (x!=null) ans.add(x); } + if ((r&4)!=0) { ArrayList x=u.funcs.get(name); if (x!=null) for(Func y:x) if (level==0 || y.isPrivate==null) ans.add(y); } + if (ans.size()==0) return u.getRawNQS(this,r,name); // If nothing at this module, then do a non-qualified search from this module + return ans; + } + String alias=name.substring(0,i); + Open uu=u.opens.get(alias); + if (uu==null || uu.realModule==null) return ans; // may happen during the initial "module" + if (level>0 && uu.isPrivate) return ans; // that means the module is imported privately + u=uu.realModule; + name=name.substring(i+1); + } + } + + /** Lookup a Sig from the current module (and it will also search this.params) */ + private Sig getRawSIG (Pos pos, String name) throws Err { + List s; + Sig s2=null; + if (name.equals("sig$") || name.equals("field$")) if (world!=null) { + s2 = world.sigs.get(name); + if (s2!=null) return s2; + } + if (name.equals("univ")) return UNIV; + if (name.equals("Int")) return SIGINT; + if (name.equals("seq/Int")) return SEQIDX; + if (name.equals("String")) return STRING; + if (name.equals("none")) return NONE; + if (name.indexOf('/')<0) { + s=getRawNQS(this, 1, name); + s2=params.get(name); + } else { + if (name.startsWith("this/")) { name=name.substring(5); s2=params.get(name); } + s=getRawQS(1, name); + } + if (s2!=null && !s.contains(s2)) s.add(s2); + return (Sig) (unique(pos, name, s)); + } + + /** Returns a short description for this module. */ + @Override public String toString() { return "module{"+path+"}"; } + + //============================================================================================================================// + + /** Returns a pointer to the root module in this world. */ + public CompModule getRootModule() { return world; } + + /** Returns the text of the "MODULE" line at the top of the file; "unknown" if the line has not be parsed from the file yet. */ + public String getModelName() { return moduleName; } + + /** Returns an unmodifiable copy of the current list of OPEN statements. */ + public ConstList getOpens() { return ConstList.make(opens.values()); } + + /** Add the "MODULE" declaration. */ + void addModelName(Pos pos, String moduleName, List list) throws Err { + if (status>0) throw new ErrorSyntax(pos, + "The \"module\" declaration must occur at the top,\n" + "and can occur at most once."); + this.moduleName = moduleName; + this.modulePos = pos; + boolean nextIsExact = false; + if (list!=null) for(ExprVar expr: list) { + if (expr==null) { nextIsExact=true; continue; } + String name = expr.label; + dup(expr.span(), name, true); + if (path.length()==0) { + Sig newSig = addSig(name, null, null, null, null, WHERE.make(expr.span())); + if (nextIsExact) exactSigs.add(newSig); + } else { + params.put(name, null); + if (nextIsExact) exactParams.add(name); + } + nextIsExact=false; + } + this.status=1; // This line must be at the end, since "addSig" will otherwise bump the status value to 3 + } + + /** Add util/sequniv to the list of declarations. */ + void addSeq(Pos pos) throws Err { + int oldStatus = status; + status = 0; + try { + addOpen(pos, null, ExprVar.make(pos, "util/sequniv"), null, ExprVar.make(pos, "seq")); + } finally { + status = oldStatus; + } + } + + /** Add an OPEN declaration. */ + void addOpen(Pos pos, Pos isPrivate, ExprVar name, List args, ExprVar alias) throws Err { + if (status>2) throw new ErrorSyntax(pos, "The \"open\" declaration must occur before any\n" + "sig/pred/fun/fact/assert/check/run command."); + String as = (alias==null ? "" : alias.label); + if (name.label.length()==0) throw new ErrorSyntax(name.span(), "The filename cannot be empty."); + if (as.indexOf('$')>=0) throw new ErrorSyntax(alias==null ? null : alias.span(), "Alias must not contain the \'$\' character"); + if (as.indexOf('@')>=0) throw new ErrorSyntax(alias==null ? null : alias.span(), "Alias must not contain the \'@\' character"); + if (as.indexOf('/')>=0) throw new ErrorSyntax(alias==null ? null : alias.span(), "Alias must not contain the \'/\' character"); + if (as.length()==0) { + as="open$"+(1+opens.size()); + if (args==null || args.size()==0) { + for(int i=0; ; i++) { + if (i>=name.label.length()) { as=name.label; break; } + char c=name.label.charAt(i); + if ((c>='a' && c<='z') || (c>='A' && c<='Z')) continue; + if (i==0) break; + if (!(c>='0' && c<='9') && c!='_' && c!='\'' && c!='\"') break; + } + } + } + final TempList newlist = new TempList(args==null ? 0 : args.size()); + if (args!=null) for(int i=0; i=0) throw new ErrorSyntax(arg.span(), "Argument cannot contain the \'@\' chracter."); + newlist.add(arg.label); + } + Open x = opens.get(as); + if (x!=null) { + // we allow this, especially because of util/sequniv + if (x.args.equals(newlist.makeConst()) && x.filename.equals(name.label)) return; + throw new ErrorSyntax(pos, "You cannot import two different modules\n" + "using the same alias."); + } + x = new Open(pos, isPrivate!=null, as, newlist.makeConst(), name.label); + opens.put(as, x); + } + + /** Do any post-parsing processig. */ + void doneParsing() { + status = 3; + LinkedHashMap copy = new LinkedHashMap(opens); + opens.clear(); + for(Map.Entry e: copy.entrySet()) { + String a = e.getKey(); + Open m = e.getValue(); + if (a.indexOf('$')>=0) { + String base = m.filename; + int i = base.lastIndexOf('/'); + if (i>=0) base = base.substring(i+1); + if (!copy.containsKey(base) && !opens.containsKey(base)) { + for(i=0; i='a' && c<='z') || (c>='A' && c<='Z')) continue; + if (i!=0 && ((c>='0' && c<='9') || c=='_' || c=='\'' || c=='\"')) continue; + break; + } + if (i>=base.length()) a = base; + } + } + opens.put(a, new Open(m.pos, m.isPrivate, a, m.args, m.filename)); + } + } + + /** Every param in every module will now point to a nonnull Sig. */ + private static void resolveParams(A4Reporter rep, List modules) throws Err { + while(true) { + boolean chg=false; + Open missing=null; + String missingName=""; + for(CompModule mod: modules) for(Open open: mod.opens.values()) { + CompModule sub = open.realModule; + if (open.args.size()!=sub.params.size()) + throw new ErrorSyntax(open.pos, + "You supplied "+open.args.size()+" arguments to the open statement, but the imported module requires " + +sub.params.size()+" arguments."); + int i=0; + for(Map.Entry p: sub.params.entrySet()) { + Sig old = p.getValue(); + String kn = p.getKey(), vn = open.args.get(i); + i++; + Sig vv=mod.getRawSIG(open.pos, vn); + if (vv==null) {if (old==null) {missing=open; missingName=vn;} continue;} + if (old==vv) continue; + if (old!=null) throw new ErrorFatal(open.pos, "Internal error (module re-instantiated with different arguments)"); + if (vv==NONE) throw new ErrorSyntax(open.pos, "You cannot use \"none\" as an instantiating argument."); + chg=true; + p.setValue(vv); + rep.parse("RESOLVE: "+(sub.path.length()==0?"this/":sub.path)+"/"+kn+" := "+vv+"\n"); + } + } + if (!chg && missing==null) return; + if (!chg) throw new ErrorSyntax(missing.pos, "The signature name \""+missingName+"\" cannot be found."); + } + } + + /** Modules with same filename and instantiating arguments will be merged. */ + private static void resolveModules(A4Reporter rep, List modules) { + // Before merging, the only pointers that go between Module objects are + // (1) a module's "params" may point to a sig in another module + // (2) a module's "imports" may point to another module + // So when we find that two modules A and B should be merged, + // we go through every module and replace "pointers into B" with equivalent "pointers into A". + while(true) { + boolean chg=false; + for(int i=0; i=0)) if ((aa>=0 && bb<0) || Util.slashComparator.compare(a.path, b.path)>0) { + a=b; b=modules.get(i); modules.set(i,a); + } + modules.remove(j); + j--; + for(CompModule c:modules) { + for(Map.Entry p:c.params.entrySet()) + if (isin(p.getValue(), b.sigs)) p.setValue(a.sigs.get(base(p.getValue()))); + for(Open p: c.opens.values()) + if (p.realModule==b) p.realModule=a; + } + } + } + if (!chg) break; + } + } + + //============================================================================================================================// + + /** Add a sig declaration. */ + void addGhostSig() throws Err { + sigs.put(Sig.GHOST.label, Sig.GHOST); + } + + Sig addSig(String name, ExprVar par, List parents, List fields, Expr fact, Attr... attributes) throws Err { + Sig obj; + Pos pos = Pos.UNKNOWN.merge(WHERE.find(attributes)); + status = 3; + dup(pos, name, true); + String full = (path.length()==0) ? "this/"+name : path+"/"+name; + Pos subset=null, subsig=null; + boolean exact = false; + if (par!=null) { + if (par.label.equals("extends")) { subsig=par.span().merge(parents.get(0).span()); } + else { exact=!par.label.equals("in"); subset=par.span(); for(ExprVar p:parents) subset=p.span().merge(subset); } + } + attributes = Util.append(attributes, exact ? Attr.EXACT : null); + if (subset!=null) { + attributes = Util.append(attributes, SUBSET.makenull(subset)); + List newParents = new ArrayList(parents==null ? 0 : parents.size()); + if (parents!=null) for(ExprVar p: parents) newParents.add(new PrimSig(p.label, WHERE.make(p.pos))); + obj = new SubsetSig(full, newParents, attributes); + } else { + attributes = Util.append(attributes, SUBSIG.makenull(subsig)); + PrimSig newParent = (parents!=null && parents.size()>0) ? (new PrimSig(parents.get(0).label, WHERE.make(parents.get(0).pos))) : UNIV; + obj = new PrimSig(full, newParent, attributes); + } + sigs.put(name, obj); + old2fields.put(obj, fields); + old2appendedfacts.put(obj, fact); + return obj; + } + + /** Add an enumeration. */ + void addEnum(Pos pos, Pos priv, ExprVar name, List atoms, Pos closingBracket) throws Err { + ExprVar EXTENDS = ExprVar.make(null, "extends"); + ExprVar THIS = ExprVar.make(null, "this/"+name); + List THESE = Arrays.asList(THIS); + if (atoms==null || atoms.size()==0) throw new ErrorSyntax(pos, "Enumeration must contain at least one name."); + addSig(name.label, null, null, null, null, WHERE.make(name.pos), ABSTRACT.make(name.pos), PRIVATE.makenull(priv), Attr.ENUM); + for(ExprVar a:atoms) addSig(a.label, EXTENDS, THESE, null, null, WHERE.make(a.pos), ONE.make(a.pos), PRIVATE.makenull(priv)); + int oldStatus = status; + status = 0; + try { + addOpen(null, null, ExprVar.make(pos, "util/ordering"), Arrays.asList(THIS), null); + } finally { + status = oldStatus; + } + } + + /** The given Sig will now point to a nonnull Sig. */ + private static Sig resolveSig(CompModule res, Set topo, Sig oldS) throws Err { + if (res.new2old.containsKey(oldS)) return oldS; + Sig realSig; + final Pos pos = oldS.pos; + final CompModule u = res.sig2module.get(oldS); + final String name = base(oldS); + final String fullname = (u.path.length()==0) ? ("this/"+name) : (u.path+"/"+name); + if (!topo.add(oldS)) throw new ErrorType(pos, "Sig "+oldS+" is involved in a cyclic inheritance."); + if (oldS instanceof SubsetSig) { + List parents = new ArrayList(); + for(Sig n: ((SubsetSig)oldS).parents) { + Sig parentAST = u.getRawSIG(n.pos, n.label); + if (parentAST==null) throw new ErrorSyntax(n.pos, "The sig \""+n.label+"\" cannot be found."); + parents.add(resolveSig(res, topo, parentAST)); + } + realSig = new SubsetSig(fullname, parents, oldS.attributes.toArray(new Attr[0])); + } else { + Sig sup = ((PrimSig)oldS).parent; + Sig parentAST = u.getRawSIG(sup.pos, sup.label); + if (parentAST==null) throw new ErrorSyntax(sup.pos, "The sig \""+sup.label+"\" cannot be found."); + Sig parent = resolveSig(res, topo, parentAST); + if (!(parent instanceof PrimSig)) throw new ErrorSyntax(sup.pos, "Cannot extend the subset signature \"" + parent + + "\".\n" + "A signature can only extend a toplevel signature or a subsignature."); + PrimSig p = (PrimSig)parent; + realSig = new PrimSig(fullname, p, oldS.attributes.toArray(new Attr[0])); + } + res.new2old.put(realSig, oldS); + res.sig2module.put(realSig, u); + for(CompModule m: res.allModules) { + for(Map.Entry e: m.sigs.entrySet()) if (e.getValue()==oldS) e.setValue(realSig); + for(Map.Entry e: m.params.entrySet()) if (e.getValue()==oldS) e.setValue(realSig); + } + if (res.exactSigs.remove(oldS)) res.exactSigs.add(realSig); + return realSig; + } + + /** Returns an unmodifiable list of all signatures defined inside this module. */ + public SafeList getAllSigs() { + SafeList x = new SafeList(sigs.values()); + return x.dup(); + } + + //============================================================================================================================// + + /** Add a MACRO declaration. */ + void addMacro(Pos p, Pos isPrivate, String n, List decls, Expr v) throws Err { + if (!Version.experimental) throw new ErrorSyntax(p, "LET declaration is allowed only inside a toplevel paragraph."); + ConstList ds = ConstList.make(decls); + status=3; + dup(p, n, false); + for(int i=0; i decls, Expr t, Expr v) throws Err { + if (decls==null) decls=new ArrayList(); else decls=new ArrayList(decls); + if (f!=null) decls.add(0, new Decl(null, null, null, Util.asList(ExprVar.make(f.span(), "this")), f)); + for(Decl d:decls) { + if (d.isPrivate!=null) { + ExprHasName name = d.names.get(0); + throw new ErrorSyntax(d.isPrivate.merge(name.pos), "Function parameter \""+name.label+"\" is always private already."); + } + if (d.disjoint2!=null) { + ExprHasName name = d.names.get(d.names.size()-1); + throw new ErrorSyntax(d.disjoint2.merge(name.pos), "Function parameter \""+name.label+"\" cannot be bound to a 'disjoint' expression."); + } + } + status=3; + dup(p, n, false); + ExprHasName dup = Decl.findDuplicateName(decls); + if (dup!=null) throw new ErrorSyntax(dup.span(), "The parameter name \""+dup.label+"\" cannot appear more than once."); + Func ans = new Func(p, isPrivate, n, decls, t, v); + ArrayList list = funcs.get(n); + if (list==null) { list = new ArrayList(); funcs.put(n, list); } + list.add(ans); + } + + /** Each FunAST will now point to a bodyless Func object. */ + private JoinableList resolveFuncDecls(A4Reporter rep, JoinableList errors, List warns) throws Err { + for(ArrayList list: funcs.values()) { + for(int listi=0; listi tmpdecls = new TempList(); + boolean err = false; + for(Decl d: f.decls) { + TempList tmpvars = new TempList(); + Expr val = cx.check(d.expr).resolve_as_set(warns); + if (!val.errors.isEmpty()) { err = true; errors = errors.make(val.errors); } + for(ExprHasName n: d.names) { + ExprVar v = ExprVar.make(n.span(), n.label, val.type()); + cx.put(n.label, v); + tmpvars.add(v); + rep.typecheck((f.isPred?"pred ":"fun ")+fullname+", Param "+n.label+": "+v.type()+"\n"); + } + tmpdecls.add(new Decl(d.isPrivate, d.disjoint, d.disjoint2, tmpvars.makeConst(), val)); + } + Expr ret = null; + if (!f.isPred) { + ret = cx.check(f.returnDecl).resolve_as_set(warns); + if (!ret.errors.isEmpty()) { err = true; errors = errors.make(ret.errors); } + } + if (err) continue; + try { + f = new Func(f.pos, f.isPrivate, fullname, tmpdecls.makeConst(), ret, f.getBody()); + list.set(listi, f); + rep.typecheck("" + f + ", RETURN: " + f.returnDecl.type() + "\n"); + } catch(Err ex) { errors = errors.make(ex); } + } + } + return errors; + } + + /** Each Func's body will now be typechecked Expr object. */ + private JoinableList resolveFuncBody(A4Reporter rep, JoinableList errors, List warns) throws Err { + for(ArrayList entry: funcs.values()) for(Func ff: entry) { + Context cx = new Context(this, warns); + cx.rootfunbody = ff; + for(Decl d: ff.decls) for(ExprHasName n: d.names) cx.put(n.label, n); + Expr newBody = cx.check(ff.getBody()); + if (ff.isPred) + newBody = newBody.resolve_as_formula(warns); + else + newBody = newBody.resolve_as_set(warns); + errors = errors.make(newBody.errors); + if (!newBody.errors.isEmpty()) continue; + try { ff.setBody(newBody); } catch(Err er) {errors = errors.make(er); continue;} + if (warns!=null && ff.returnDecl.type().hasTuple() && newBody.type().hasTuple() && !newBody.type().intersects(ff.returnDecl.type())) + warns.add(new ErrorWarning(newBody.span(), + "Function return value is disjoint from its return type.\n" + +"Function body has type "+newBody.type() + "\n" + "but the return type is "+ff.returnDecl.type())); + //else if (warns!=null && Version.experimental && !newBody.type.isSubtypeOf(ff.returnDecl.type)) + // warns.add(new ErrorWarning(newBody.span(), + // "Function may return a tuple not in its declared return type.\n" + // +"The Alloy Analyzer's analysis may be unsound\n" + // +"if it returns a tuple outside its declared return type.\n" + // +"Function body has type "+newBody.type+"\nbut the return type is "+ff.returnDecl.type)); + rep.typecheck(ff.toString()+", BODY:"+newBody.type()+"\n"); + } + return errors; + } + + /** Return an unmodifiable list of all functions in this module. */ + public SafeList getAllFunc() { + SafeList ans = new SafeList(); + for(ArrayList e: funcs.values()) ans.addAll(e); + return ans.dup(); + } + + //============================================================================================================================// + + /** Add an ASSERT declaration. */ + String addAssertion(Pos pos, String name, Expr value) throws Err { + status=3; + if (name==null || name.length()==0) name="assert$"+(1+asserts.size()); + dup(pos, name, false); + Expr old = asserts.put(name, ExprUnary.Op.NOOP.make(value.span().merge(pos), value)); + if (old!=null) { + asserts.put(name, old); + throw new ErrorSyntax(pos, "\""+name+"\" is already the name of an assertion in this module."); + } + return name; + } + + /** Each assertion name now points to a typechecked Expr rather than an untypechecked Exp. */ + private JoinableList resolveAssertions(A4Reporter rep, JoinableList errors, List warns) throws Err { + Context cx = new Context(this, warns); + for(Map.Entry e: asserts.entrySet()) { + Expr expr = e.getValue(); + expr = cx.check(expr).resolve_as_formula(warns); + if (expr.errors.isEmpty()) { + e.setValue(expr); + rep.typecheck("Assertion " + e.getKey() + ": " + expr.type()+"\n"); + } else errors = errors.make(expr.errors); + } + return errors; + } + + /** Return an unmodifiable list of all assertions in this module. */ + public ConstList> getAllAssertions() { + TempList> ans = new TempList>(asserts.size()); + for(Map.Entry e: asserts.entrySet()) { + ans.add(new Pair(e.getKey(), e.getValue())); + } + return ans.makeConst(); + } + + //============================================================================================================================// + + /** Add a FACT declaration. */ + void addFact(Pos pos, String name, Expr value) throws Err { + status=3; + if (name==null || name.length()==0) name="fact$"+(1+facts.size()); + facts.add(new Pair(name, ExprUnary.Op.NOOP.make(value.span().merge(pos), value))); + } + + /** Each fact name now points to a typechecked Expr rather than an untypechecked Exp; we'll also add the sig appended facts. */ + private JoinableList resolveFacts(CompModule res, A4Reporter rep, JoinableList errors, List warns) throws Err { + Context cx = new Context(this, warns); + for(int i=0; i(name, expr)); + rep.typecheck("Fact " + name + ": " + expr.type()+"\n"); + } else errors = errors.make(expr.errors); + } + for(Sig s: sigs.values()) { + Expr f = res.old2appendedfacts.get(res.new2old.get(s)); + if (f == null) continue; + if (f instanceof ExprConstant && ((ExprConstant)f).op==ExprConstant.Op.TRUE) continue; + Expr formula; + cx.rootsig = s; + if (s.isOne==null) { + cx.put("this", s.decl.get()); + formula = cx.check(f).resolve_as_formula(warns); + } else { + cx.put("this", s); + formula = cx.check(f).resolve_as_formula(warns); + } + cx.remove("this"); + if (formula.errors.size()>0) errors = errors.make(formula.errors); else { s.addFact(formula); rep.typecheck("Fact "+s+"$fact: " + formula.type()+"\n"); } + } + return errors; + } + + /** Return an unmodifiable list of all facts in this module. */ + public SafeList> getAllFacts() { + return (new SafeList>(facts)).dup(); + } + + /** Return the conjunction of all facts in this module and all reachable submodules (not including field constraints, nor including sig appended constraints) */ + public Expr getAllReachableFacts() { + ArrayList facts = new ArrayList(); + for(CompModule m: world.getAllReachableModules()) for(Pair f: m.facts) facts.add(f.b); + if (facts.size()==0) return ExprConstant.TRUE; else return ExprList.make(null, null, ExprList.Op.AND, facts); + } + + //============================================================================================================================// + + /** Add a COMMAND declaration. */ + void addCommand(boolean followUp, Pos p, String n, boolean c, int o, int b, int seq, int exp, List s, ExprVar label) throws Err { + if (followUp && !Version.experimental) throw new ErrorSyntax(p, "Syntax error encountering => symbol."); + if (label!=null) p=Pos.UNKNOWN.merge(p).merge(label.pos); + status=3; + if (n.length()==0) throw new ErrorSyntax(p, "Predicate/assertion name cannot be empty."); + if (n.indexOf('@')>=0) throw new ErrorSyntax(p, "Predicate/assertion name cannot contain \'@\'"); + String labelName = (label==null || label.label.length()==0) ? n : label.label; + Command parent = followUp ? commands.get(commands.size()-1) : null; + Command newcommand = new Command(p, labelName, c, o, b, seq, exp, s, null, ExprVar.make(null, n), parent); + if (parent!=null) commands.set(commands.size()-1, newcommand); else commands.add(newcommand); + } + + /** Add a COMMAND declaration. */ + void addCommand(boolean followUp, Pos p, Expr e, boolean c, int o, int b, int seq, int exp, List s, ExprVar label) throws Err { + if (followUp && !Version.experimental) throw new ErrorSyntax(p, "Syntax error encountering => symbol."); + if (label!=null) p=Pos.UNKNOWN.merge(p).merge(label.pos); + status=3; + String n; + if (c) n=addAssertion(p,"check$"+(1+commands.size()),e); + else addFunc(e.span().merge(p), Pos.UNKNOWN, n="run$"+(1+commands.size()), null, new ArrayList(), null, e); + String labelName = (label==null || label.label.length()==0) ? n : label.label; + Command parent = followUp ? commands.get(commands.size()-1) : null; + Command newcommand = new Command(e.span().merge(p), labelName, c, o, b, seq, exp, s, null, ExprVar.make(null, n), parent); + if (parent!=null) commands.set(commands.size()-1, newcommand); else commands.add(newcommand); + } + + /** Resolve a particular command. */ + private Command resolveCommand(Command cmd, ConstList exactSigs, Expr globalFacts) throws Err { + Command parent = cmd.parent==null ? null : resolveCommand(cmd.parent, exactSigs, globalFacts); + String cname = ((ExprVar)(cmd.formula)).label; + Expr e; + if (cmd.check) { + List m = getRawQS(2, cname); // We prefer assertion in the topmost module + if (m.size()==0 && cname.indexOf('/')<0) m=getRawNQS(this, 2, cname); + if (m.size()>1) unique(cmd.pos, cname, m); + if (m.size()<1) throw new ErrorSyntax(cmd.pos, "The assertion \""+cname+"\" cannot be found."); + e = ((Expr)(m.get(0))).not(); + } else { + List m = getRawQS(4, cname); // We prefer fun/pred in the topmost module + if (m.size()==0 && cname.indexOf('/')<0) m=getRawNQS(this, 4, cname); + if (m.size()>1) unique(cmd.pos, cname, m); + if (m.size()<1) throw new ErrorSyntax(cmd.pos, "The predicate/function \""+cname+"\" cannot be found."); + Func f = (Func) (m.get(0)); + e = f.getBody(); + if (!f.isPred) e = e.in(f.returnDecl); + if (f.decls.size()>0) e = ExprQt.Op.SOME.make(null, null, f.decls, e); + } + if (e==null) e = ExprConstant.TRUE; + TempList sc=new TempList(cmd.scope.size()); + for(CommandScope et: cmd.scope) { + Sig s = getRawSIG(et.sig.pos, et.sig.label); + if (s==null) throw new ErrorSyntax(et.sig.pos, "The sig \""+et.sig.label+"\" cannot be found."); + sc.add(new CommandScope(null, s, et.isExact, et.startingScope, et.endingScope, et.increment)); + } + return new Command(cmd.pos, cmd.label, cmd.check, cmd.overall, cmd.bitwidth, cmd.maxseq, cmd.expects, sc.makeConst(), exactSigs, globalFacts.and(e), parent); + } + + /** Each command now points to a typechecked Expr. */ + private void resolveCommands(Expr globalFacts) throws Err { + ConstList exactSigs = ConstList.make(world.exactSigs); + for(int i=0; i getAllCommands() { return ConstList.make(commands); } + + //============================================================================================================================// + + /** Returns true if exists some entry (a,b) in the map, such that b==value (using object identity as the comparison) */ + private static boolean isin(V value, Map map) { + for(V v: map.values()) if (v==value) return true; + return false; + } + + //============================================================================================================================// + + private static void resolveFieldDecl(CompModule res, final A4Reporter rep, final Sig s, final List warns, boolean defined) throws Err { + // When typechecking each field: + // * it is allowed to refer to earlier fields in the same SIG or in any visible ancestor sig + // * it is allowed to refer to visible sigs + // * it is NOT allowed to refer to any predicate or function + // For example, if A.als opens B.als, and B/SIGX extends A/SIGY, + // then B/SIGX's fields cannot refer to A/SIGY, nor any fields in A/SIGY) + final List oldDecls = res.old2fields.get(res.new2old.get(s)); + if (oldDecls==null) return; + final CompModule m = res.sig2module.get(s); + final Context cx = new Context(m, warns); + final ExprHasName dup = Decl.findDuplicateName(oldDecls); + if (dup!=null) throw new ErrorSyntax(dup.span(), "sig \""+s+"\" cannot have 2 fields named \""+dup.label+"\""); + for(final Decl d: oldDecls) { + if (d.expr.mult()!=ExprUnary.Op.EXACTLYOF) {if (defined) continue;} else {if (!defined) continue;} + // The name "this" does matter, since the parser and the typechecker both refer to it as "this" + cx.rootfield = d; + cx.rootsig = s; + cx.put("this", s.decl.get()); + Expr bound = cx.check(d.expr).resolve_as_set(warns); + cx.remove("this"); + String[] names = new String[d.names.size()]; for(int i=0; i modules) throws Err { + // The Alloy language forbids two overlapping sigs from having fields with the same name. + // In other words: if 2 fields have the same name, then their type's first column must not intersect. + final Map> fieldname2fields=new LinkedHashMap>(); + for(CompModule m: modules) { + for(Sig sig: m.sigs.values()) { + for(Field field: sig.getFields()) { + List peers=fieldname2fields.get(field.label); + if (peers==null) { peers=new ArrayList(); fieldname2fields.put(field.label, peers); } + for(Field field2: peers) + if (field.type().firstColumnOverlaps(field2.type())) + throw new ErrorType(field.pos, + "Two overlapping signatures cannot have\n" + "two fields with the same name \""+field.label + +"\":\n\n1) one is in sig \""+field.sig+"\"\n"+field.pos + +"\n\n2) the other is in sig \""+field2.sig+"\"\n"+field2.pos); + peers.add(field); + } + } + } + } + + //============================================================================================================================// + + private static void resolveMeta(final CompModule root) throws Err { + // Now, add the meta sigs and fields if needed + Map sig2meta = new LinkedHashMap(); + Map field2meta = new LinkedHashMap(); + boolean hasMetaSig = false, hasMetaField = false; + root.new2old.put(root.metaSig, root.metaSig); root.sigs.put(base(root.metaSig), root.metaSig); + root.new2old.put(root.metaField, root.metaField); root.sigs.put(base(root.metaField), root.metaField); + for(CompModule m: root.allModules) for(Sig s: new ArrayList(m.sigs.values())) if (m!=root || (s!=root.metaSig && s!=root.metaField)) { + PrimSig ka = new PrimSig(s.label+"$", root.metaSig, Attr.ONE, PRIVATE.makenull(s.isPrivate), Attr.META); + sig2meta.put(s, ka); + ka.addDefinedField(Pos.UNKNOWN, null, Pos.UNKNOWN, "value", s); + m.new2old.put(ka, ka); + m.sigs.put(base(ka), ka); + hasMetaSig = true; + Expr allfields = ExprConstant.EMPTYNESS; + for(Field field: s.getFields()) { + Pos priv = field.isPrivate; if (priv==null) priv = s.isPrivate; + PrimSig kb = new PrimSig(s.label+"$"+field.label, root.metaField, Attr.ONE, PRIVATE.makenull(priv), Attr.META); + field2meta.put(field, kb); + m.new2old.put(kb, kb); + m.sigs.put(base(kb), kb); + hasMetaField = true; + kb.addDefinedField(Pos.UNKNOWN, null, Pos.UNKNOWN, "value", field); + if (allfields==ExprConstant.EMPTYNESS) allfields = kb; else allfields = allfields.plus(kb); + } + ka.addDefinedField(Pos.UNKNOWN, null, Pos.UNKNOWN, "fields", allfields); + } + for(Map.Entry e: sig2meta.entrySet()) { + Expr expr = null; + if ((e.getKey()) instanceof PrimSig) { + PrimSig a = (PrimSig)(e.getKey()); + if (a.parent!=null && a.parent!=UNIV) expr = sig2meta.get(a.parent); + } + e.getValue().addDefinedField(Pos.UNKNOWN, null, Pos.UNKNOWN, "parent", (expr==null ? ExprConstant.EMPTYNESS : expr)); + } + for(Map.Entry e: sig2meta.entrySet()) { + Sig s = e.getKey(); + PrimSig s2 = e.getValue(); + Expr allfields = ExprConstant.EMPTYNESS; + for(Field f: s.getFields()) { + PrimSig metaF = field2meta.get(f); + if (allfields==ExprConstant.EMPTYNESS) allfields = metaF; else allfields = allfields.plus(metaF); + } + if (s instanceof PrimSig) for(Sig c: (((PrimSig)s).descendents())) for(Field f: c.getFields()) { + PrimSig metaF = field2meta.get(f); + if (allfields==ExprConstant.EMPTYNESS) allfields = metaF; else allfields = allfields.plus(metaF); + } + s2.addDefinedField(Pos.UNKNOWN, null, Pos.UNKNOWN, "subfields", allfields); + } + if (hasMetaSig==false) root.facts.add(new Pair("sig$fact", root.metaSig.no().and(root.metaField.no()))); + else if (hasMetaField==false) root.facts.add(new Pair("sig$fact", root.metaField.no())); + } + + //============================================================================================================================// + + /** This method resolves the entire world; NOTE: if it throws an exception, it may leave the world in an inconsistent state! */ + static CompModule resolveAll(final A4Reporter rep, final CompModule root) throws Err { + final List warns = new ArrayList(); + for(CompModule m: root.getAllReachableModules()) root.allModules.add(m); + resolveParams(rep, root.allModules); + resolveModules(rep, root.allModules); + for(CompModule m: root.allModules) for(Sig s: m.sigs.values()) root.sig2module.put(s, m); + // Resolves SigAST -> Sig, and topologically sort the sigs into the "sorted" array + root.new2old.put(UNIV,UNIV); + root.new2old.put(SIGINT,SIGINT); + root.new2old.put(SEQIDX,SEQIDX); + root.new2old.put(STRING,STRING); + root.new2old.put(NONE,NONE); + HashSet topo = new HashSet(); + for(CompModule m: root.allModules) for(Sig s: m.sigs.values()) resolveSig(root, topo, s); + // Add the non-defined fields to the sigs in topologically sorted order (since fields in subsigs are allowed to refer to parent's fields) + for(Sig oldS: root.new2old.keySet()) resolveFieldDecl(root, rep, oldS, warns, false); + // Typecheck the function declarations + JoinableList errors = new JoinableList(); + for(CompModule x: root.allModules) errors = x.resolveFuncDecls(rep, errors, warns); + if (!errors.isEmpty()) throw errors.pick(); + // Typecheck the defined fields + for(Sig oldS: root.new2old.keySet()) resolveFieldDecl(root, rep, oldS, warns, true); + if (Version.experimental && root.seenDollar) resolveMeta(root); + // Reject name clash + rejectNameClash(root.allModules); + // Typecheck the function bodies, assertions, and facts (which can refer to function declarations) + for(CompModule x: root.allModules) { + errors = x.resolveFuncBody(rep, errors, warns); + errors = x.resolveAssertions(rep, errors, warns); + errors = x.resolveFacts(root, rep, errors, warns); + // also, we can collect up all the exact sigs and add them to the root module's list of exact sigs + for(String n: x.exactParams) { Sig sig = x.params.get(n); if (sig!=null) root.exactSigs.add(sig); } + } + if (!errors.isEmpty()) throw errors.pick(); + // Typecheck the run/check commands (which can refer to function bodies and assertions) + root.resolveCommands(root.getAllReachableFacts()); + if (!errors.isEmpty()) throw errors.pick(); + for(ErrorWarning w:warns) rep.warning(w); + for(Sig s: root.exactSigs) rep.debug("Forced to be exact: "+s+"\n"); + return root; + } + + //============================================================================================================================// + + /** Add a global expression; if the name already exists, it is removed first. */ + public void addGlobal(String name, Expr value) { + globals.put(name, value); + } + + /** Resolve the name based on the current context and this module. */ + private Expr populate(TempList ch, TempList re, Decl rootfield, Sig rootsig, boolean rootfunparam, Func rootfunbody, Pos pos, String fullname, Expr THIS) { + // Return object can be Func(with > 0 arguments) or Expr + final String name = (fullname.charAt(0)=='@') ? fullname.substring(1) : fullname; + boolean fun = (rootsig!=null && (rootfield==null || rootfield.expr.mult()==ExprUnary.Op.EXACTLYOF)) + || (rootsig==null && !rootfunparam); + if (name.equals("univ")) return ExprUnary.Op.NOOP.make(pos, UNIV); + if (name.equals("Int")) return ExprUnary.Op.NOOP.make(pos, SIGINT); + if (name.equals("seq/Int")) return ExprUnary.Op.NOOP.make(pos, SEQIDX); + if (name.equals("String")) return ExprUnary.Op.NOOP.make(pos, STRING); + if (name.equals("none")) return ExprUnary.Op.NOOP.make(pos, NONE); + if (name.equals("iden")) return ExprConstant.Op.IDEN.make(pos, 0); + if (name.equals("sig$") || name.equals("field$")) if (world!=null) { + Sig s = world.sigs.get(name); + if (s!=null) return ExprUnary.Op.NOOP.make(pos, s); + } + final List ans = name.indexOf('/')>=0 ? getRawQS(fun?5:1, name) : getRawNQS(this, fun?5:1, name); + final Sig param = params.get(name); if (param!=null && !ans.contains(param)) ans.add(param); + for(Object x: ans) { + if (x instanceof Sig) { + Sig y = (Sig)x; + ch.add(ExprUnary.Op.NOOP.make(pos, y, null, 0)); + re.add("sig "+y.label); + } + else if (x instanceof Func) { + Func f = (Func)x; + int fn = f.count(); + int penalty = 0; + if (resolution==1 && fn>0 && rootsig!=null && THIS!=null && THIS.type().hasArity(1) && f.get(0).type().intersects(THIS.type())) { + // If we're inside a sig, and there is a unary variable bound to "this", + // we should consider it as a possible FIRST ARGUMENT of a fun/pred call + ConstList t = Util.asList(THIS); + ch.add(fn==1 ? ExprCall.make(pos, null, f, t, 1+penalty) : ExprBadCall.make(pos, null, f, t, 1+penalty)); // penalty of 1 + re.add((f.isPred?"pred this.":"fun this.")+f.label); + } + if (resolution==1) { + ch.add(fn==0 ? ExprCall.make(pos, null, f, null, penalty) : ExprBadCall.make(pos, null, f, null, penalty)); + re.add((f.isPred?"pred ":"fun ")+f.label); + } + if (resolution==2 && f!=rootfunbody && THIS!=null && fullname.charAt(0)!='@' && fn>0 && f.get(0).type().intersects(THIS.type())) { + // If there is some value bound to "this", we should consider it as a possible FIRST ARGUMENT of a fun/pred call + ConstList t = Util.asList(THIS); + ch.add(fn==1 ? ExprCall.make(pos, null, f, t, 0) : ExprBadCall.make(pos, null, f, t, 0)); + re.add((f.isPred?"pred this.":"fun this.")+f.label); + } + if (resolution!=1) { + ch.add(fn==0 ? ExprCall.make(pos, null, f, null, 0) : ExprBadCall.make(pos, null, f, null, 0)); + re.add((f.isPred?"pred ":"fun ")+f.label); + } + } + } + // Within a field decl + // (1) Can refer to any visible sig/param (but you cannot call any function or predicates) + // (2) Can refer to field in this sig (defined earlier than you), and fields in any visible ancestor sig + // Within an appended facts + // (1) Can refer to any visible sig/param/func/predicate + // (2) Can refer to any visible field + // Within a function paramDecl/returnDecl + // (1) Cannot call + // (2) But can refer to anything else visible. + // All else: we can call, and can refer to anything visible. + for(CompModule m: getAllNameableModules()) + for(Sig s: m.sigs.values()) if (m==this || s.isPrivate==null) + for(Field f: s.getFields()) if (f.isMeta==null && (m==this || f.isPrivate==null) && f.label.equals(name)) + if (resolution==1) { + Expr x=null; + if (rootsig==null) + { x=ExprUnary.Op.NOOP.make(pos, f, null, 0); } + else if (rootsig.isSameOrDescendentOf(f.sig)) + { x=ExprUnary.Op.NOOP.make(pos, f, null, 0); if (fullname.charAt(0)!='@') x=THIS.join(x); } + else if (rootfield==null || rootfield.expr.mult()==ExprUnary.Op.EXACTLYOF) + { x=ExprUnary.Op.NOOP.make(pos, f, null, 1); } // penalty of 1 + if (x!=null) { ch.add(x); re.add("field "+f.sig.label+" <: "+f.label); } + } else if (rootfield==null || rootsig.isSameOrDescendentOf(f.sig)) { + Expr x0 = ExprUnary.Op.NOOP.make(pos, f, null, 0); + if (resolution==2 && THIS!=null && fullname.charAt(0)!='@' && f.type().firstColumnOverlaps(THIS.type())) { + ch.add(THIS.join(x0)); + re.add("field "+f.sig.label+" <: this."+f.label); + if (rootsig!=null) continue; + } + ch.add(x0); + re.add("field "+f.sig.label+" <: "+f.label); + } + if (metaSig()!=null && (rootsig==null || rootfield==null)) { + SafeList children = null; + try { children=metaSig().children(); } catch(Err err) { return null; } // exception NOT possible + for(PrimSig s:children) for(Field f:s.getFields()) if (f.label.equals(name)) { + Expr x=ExprUnary.Op.NOOP.make(pos, f, null, 0); ch.add(x); re.add("field "+f.sig.label+" <: "+f.label); + } + } + if (metaField()!=null && (rootsig==null || rootfield==null)) { + SafeList children = null; + try { children=metaField().children(); } catch(Err err) { return null; } // exception NOT possible + for(PrimSig s:children) for(Field f:s.getFields()) if (f.label.equals(name)) { + Expr x=ExprUnary.Op.NOOP.make(pos, f, null, 0); ch.add(x); re.add("field "+f.sig.label+" <: "+f.label); + } + } + return null; + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/parser/CompParser.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/parser/CompParser.java new file mode 100644 index 00000000..0ecc13ce --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/parser/CompParser.java @@ -0,0 +1,7836 @@ + +//---------------------------------------------------- +// The following code was generated by CUP v0.11a beta 20060608 +// Tue Nov 08 16:09:13 EST 2011 +//---------------------------------------------------- + +package edu.mit.csail.sdg.alloy4compiler.parser; + +import java.util.Stack; +import java.util.List; +import java.util.ArrayList; +import java.util.TreeSet; +import java.util.Map; +import java.util.LinkedHashMap; +import java.io.BufferedReader; +import java.io.FileNotFoundException; +import java.io.Reader; +import java.io.IOException; +import java.io.StringReader; +import java_cup.runtime.*; +import edu.mit.csail.sdg.alloy4.Err; +import edu.mit.csail.sdg.alloy4.ErrorFatal; +import edu.mit.csail.sdg.alloy4.ErrorSyntax; +import edu.mit.csail.sdg.alloy4.Pos; +import edu.mit.csail.sdg.alloy4.Pair; +import edu.mit.csail.sdg.alloy4.Util; +import edu.mit.csail.sdg.alloy4.Version; +import edu.mit.csail.sdg.alloy4compiler.ast.Attr.AttrType; +import edu.mit.csail.sdg.alloy4compiler.ast.CommandScope; +import edu.mit.csail.sdg.alloy4compiler.ast.Decl; +import edu.mit.csail.sdg.alloy4compiler.ast.Expr; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprBadJoin; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprITE; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprLet; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprBinary; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprList; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprConstant; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprQt; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprUnary; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprVar; +import edu.mit.csail.sdg.alloy4compiler.ast.Sig; +import edu.mit.csail.sdg.alloy4compiler.ast.Sig.PrimSig; + +/** CUP v0.11a beta 20060608 generated parser. + * @version Tue Nov 08 16:09:13 EST 2011 + */ +public class CompParser extends java_cup.runtime.lr_parser { + + /** Default constructor. */ + public CompParser() {super();} + + /** Constructor which sets the default scanner. */ + public CompParser(java_cup.runtime.Scanner s) {super(s);} + + /** Constructor which sets the default scanner. */ + public CompParser(java_cup.runtime.Scanner s, java_cup.runtime.SymbolFactory sf) {super(s,sf);} + + /** Production table. */ + protected static final short _production_table[][] = + unpackFromStrings(new String[] { + "\000\u015e\000\002\106\003\000\002\002\004\000\002\107" + + "\005\000\002\107\010\000\002\107\006\000\002\107\010" + + "\000\002\107\011\000\002\107\013\000\002\107\011\000" + + "\002\107\010\000\002\107\005\000\002\107\006\000\002" + + "\107\006\000\002\107\005\000\002\107\006\000\002\107" + + "\006\000\002\107\004\000\002\107\004\000\002\107\004" + + "\000\002\107\004\000\002\107\004\000\002\107\002\000" + + "\002\013\003\000\002\013\003\000\002\012\007\000\002" + + "\012\006\000\002\012\011\000\002\012\010\000\002\012" + + "\007\000\002\012\006\000\002\012\011\000\002\012\010" + + "\000\002\027\002\000\002\027\004\000\002\075\004\000" + + "\002\075\006\000\002\075\004\000\002\075\002\000\002" + + "\112\003\000\002\112\005\000\002\111\004\000\002\111" + + "\004\000\002\111\004\000\002\111\004\000\002\111\004" + + "\000\002\111\004\000\002\111\004\000\002\110\004\000" + + "\002\110\007\000\002\110\011\000\002\110\006\000\002" + + "\110\003\000\002\110\006\000\002\110\010\000\002\110" + + "\005\000\002\051\011\000\002\051\010\000\002\051\011" + + "\000\002\051\010\000\002\051\006\000\002\052\003\000" + + "\002\052\004\000\002\037\013\000\002\037\013\000\002" + + "\037\010\000\002\037\015\000\002\037\015\000\002\037" + + "\012\000\002\067\011\000\002\067\011\000\002\067\006" + + "\000\002\067\013\000\002\067\013\000\002\067\010\000" + + "\002\123\002\000\002\123\003\000\002\076\011\000\002" + + "\100\003\000\002\100\003\000\002\100\003\000\002\100" + + "\003\000\002\100\003\000\002\101\003\000\002\101\004" + + "\000\002\077\004\000\002\077\004\000\002\077\004\000" + + "\002\077\002\000\002\102\003\000\002\102\003\000\002" + + "\102\003\000\002\102\003\000\002\102\005\000\002\102" + + "\003\000\002\104\002\000\002\104\003\000\002\103\003" + + "\000\002\103\005\000\002\105\003\000\002\105\005\000" + + "\002\053\003\000\002\053\005\000\002\053\005\000\002" + + "\054\003\000\002\054\005\000\002\055\003\000\002\055" + + "\005\000\002\056\003\000\002\056\004\000\002\056\005" + + "\000\002\056\006\000\002\014\006\000\002\014\006\000" + + "\002\014\006\000\002\014\007\000\002\014\006\000\002" + + "\014\005\000\002\014\007\000\002\014\007\000\002\014" + + "\007\000\002\014\010\000\002\014\007\000\002\014\006" + + "\000\002\015\003\000\002\015\006\000\002\015\006\000" + + "\002\015\006\000\002\015\007\000\002\015\006\000\002" + + "\015\005\000\002\015\007\000\002\015\007\000\002\015" + + "\007\000\002\015\010\000\002\015\007\000\002\015\006" + + "\000\002\020\005\000\002\020\003\000\002\016\005\000" + + "\002\016\003\000\002\017\002\000\002\017\003\000\002" + + "\017\005\000\002\017\004\000\002\050\006\000\002\050" + + "\007\000\002\032\002\000\002\032\003\000\002\031\005" + + "\000\002\031\004\000\002\033\003\000\002\033\004\000" + + "\002\034\004\000\002\034\003\000\002\035\002\000\002" + + "\035\003\000\002\036\003\000\002\036\005\000\002\030" + + "\003\000\002\030\003\000\002\030\003\000\002\005\004" + + "\000\002\005\005\000\002\005\005\000\002\005\005\000" + + "\002\005\005\000\002\005\005\000\002\005\005\000\002" + + "\063\003\000\002\063\005\000\002\064\003\000\002\064" + + "\005\000\002\025\003\000\002\025\005\000\002\026\003" + + "\000\002\026\005\000\002\040\003\000\002\040\003\000" + + "\002\042\003\000\002\042\007\000\002\044\007\000\002" + + "\044\005\000\002\042\007\000\002\044\005\000\002\041" + + "\003\000\002\041\003\000\002\043\003\000\002\043\007" + + "\000\002\045\007\000\002\045\005\000\002\002\003\000" + + "\002\002\005\000\002\003\003\000\002\003\005\000\002" + + "\057\003\000\002\057\004\000\002\057\004\000\002\060" + + "\003\000\002\060\004\000\002\010\005\000\002\010\005" + + "\000\002\010\005\000\002\010\005\000\002\010\005\000" + + "\002\010\005\000\002\010\005\000\002\010\005\000\002" + + "\010\005\000\002\010\005\000\002\010\005\000\002\010" + + "\005\000\002\010\004\000\002\010\004\000\002\010\004" + + "\000\002\010\004\000\002\010\004\000\002\010\004\000" + + "\002\010\004\000\002\010\003\000\002\011\005\000\002" + + "\011\005\000\002\011\005\000\002\011\005\000\002\011" + + "\005\000\002\011\005\000\002\011\005\000\002\011\005" + + "\000\002\011\005\000\002\011\005\000\002\011\005\000" + + "\002\011\005\000\002\011\004\000\002\011\004\000\002" + + "\011\004\000\002\011\004\000\002\011\004\000\002\011" + + "\004\000\002\011\004\000\002\011\003\000\002\113\003" + + "\000\002\113\005\000\002\113\005\000\002\113\005\000" + + "\002\114\003\000\002\114\005\000\002\114\005\000\002" + + "\114\005\000\002\117\003\000\002\117\005\000\002\117" + + "\005\000\002\117\005\000\002\117\005\000\002\120\003" + + "\000\002\120\005\000\002\120\005\000\002\120\005\000" + + "\002\120\005\000\002\115\003\000\002\115\005\000\002" + + "\115\005\000\002\115\005\000\002\116\003\000\002\116" + + "\005\000\002\116\005\000\002\116\005\000\002\061\003" + + "\000\002\061\004\000\002\061\004\000\002\061\004\000" + + "\002\061\004\000\002\061\004\000\002\061\004\000\002" + + "\062\003\000\002\062\004\000\002\062\004\000\002\062" + + "\004\000\002\065\003\000\002\065\005\000\002\066\003" + + "\000\002\066\005\000\002\046\003\000\002\046\005\000" + + "\002\047\003\000\002\047\005\000\002\072\003\000\002" + + "\072\003\000\002\072\003\000\002\072\003\000\002\072" + + "\003\000\002\072\003\000\002\072\003\000\002\072\003" + + "\000\002\072\003\000\002\072\003\000\002\072\003\000" + + "\002\072\003\000\002\072\003\000\002\072\003\000\002" + + "\072\003\000\002\072\003\000\002\073\003\000\002\073" + + "\005\000\002\074\003\000\002\074\005\000\002\021\003" + + "\000\002\021\005\000\002\022\003\000\002\022\005\000" + + "\002\070\003\000\002\070\005\000\002\071\003\000\002" + + "\071\005\000\002\006\003\000\002\007\003\000\002\007" + + "\006\000\002\007\006\000\002\007\006\000\002\007\006" + + "\000\002\007\006\000\002\023\003\000\002\023\005\000" + + "\002\024\003\000\002\024\005\000\002\024\005\000\002" + + "\024\005\000\002\024\005\000\002\024\005\000\002\121" + + "\004\000\002\121\004\000\002\121\004\000\002\121\004" + + "\000\002\121\004\000\002\121\004\000\002\122\003\000" + + "\002\122\004\000\002\122\004\000\002\122\004\000\002" + + "\004\003\000\002\004\003\000\002\004\003\000\002\004" + + "\003\000\002\004\003\000\002\004\003\000\002\004\003" + + "\000\002\004\005\000\002\004\003\000\002\004\004\000" + + "\002\004\003\000\002\004\006\000\002\004\005" }); + + /** Access to production table. */ + public short[][] production_table() {return _production_table;} + + /** Parse-action table. */ + protected static final short[][] _action_table = + unpackFromStrings(new String[] { + "\000\u02b4\000\002\001\uffec\000\004\002\u02b6\001\002\000" + + "\032\002\001\035\026\043\010\050\017\065\016\104\006" + + "\111\021\125\027\134\012\141\024\147\013\153\030\001" + + "\uffb7\000\002\001\uffb3\000\002\001\uffef\000\014\100\242" + + "\142\053\160\046\163\044\165\u02b2\001\002\000\002\001" + + "\uffee\000\014\057\uffb6\067\uffb6\102\uffb6\126\uffb6\133\uffb6" + + "\001\uffb0\000\002\001\uffaf\000\012\100\242\142\053\160" + + "\046\163\044\001\002\000\002\001\ufff0\000\014\100\242" + + "\142\053\160\046\163\044\165\u02a1\001\002\000\002\001" + + "\uffeb\000\002\001\ufff1\000\010\142\053\160\046\163\044" + + "\001\002\000\016\035\026\104\006\125\027\134\u0294\147" + + "\013\153\030\001\002\000\010\142\053\160\046\163\044" + + "\001\002\000\002\001\uffea\000\004\075\u0252\001\uffed\000" + + "\002\001\uffb4\000\002\001\uffb2\000\002\001\uffb1\000\014" + + "\057\032\067\036\102\033\126\035\133\034\001\002\000" + + "\010\142\053\160\046\163\044\001\002\000\010\142\053" + + "\160\046\163\044\001\002\000\020\114\045\142\041\150" + + "\047\155\040\160\046\162\043\163\044\001\002\000\010" + + "\142\053\160\046\163\044\001\002\000\020\114\045\142" + + "\041\150\047\155\040\160\046\162\043\163\044\001\002" + + "\000\010\051\u020b\101\u020d\105\u020c\001\uffa9\000\002\001" + + "\uffa7\000\004\151\u0112\001\002\000\004\151\056\001\uff9d" + + "\000\002\001\uffa8\000\002\001\uff9a\000\002\001\uffa4\000" + + "\004\151\u011e\001\002\000\002\001\uffa6\000\004\055\051" + + "\001\002\000\010\142\053\160\046\163\044\001\002\000" + + "\010\051\060\101\062\105\061\001\002\000\004\151\054" + + "\001\002\000\004\163\044\001\002\000\004\151\056\001" + + "\uff9b\000\004\163\057\001\002\000\002\001\uff99\000\116" + + "\031\120\032\144\033\150\034\151\036\124\037\174\044" + + "\102\047\210\053\223\072\220\073\116\077\165\100\125" + + "\102\215\103\204\104\153\105\127\112\152\113\212\114" + + "\045\115\134\124\113\125\154\142\200\143\177\150\047" + + "\152\161\153\205\154\162\155\040\156\176\157\121\160" + + "\171\161\135\162\043\163\044\164\147\165\137\001\002" + + "\000\022\052\072\053\063\062\065\130\064\134\074\142" + + "\053\160\046\163\044\001\uff75\000\022\052\072\053\063" + + "\062\065\130\064\134\074\142\053\160\046\163\044\001" + + "\uff75\000\010\142\053\160\046\163\044\001\002\000\010" + + "\142\053\160\046\163\044\001\002\000\010\142\053\160" + + "\046\163\044\001\002\000\004\052\u01f3\001\uff74\000\002" + + "\001\uff98\000\010\051\u01bc\052\077\060\u01ef\001\002\000" + + "\002\001\uff86\000\022\052\072\053\063\062\065\130\064" + + "\134\074\142\053\160\046\163\044\001\uff75\000\004\137" + + "\u01ea\001\002\000\012\053\075\142\053\160\046\163\044" + + "\001\002\000\010\142\053\160\046\163\044\001\002\000" + + "\010\051\101\052\077\060\100\001\002\000\010\142\053" + + "\160\046\163\044\001\002\000\116\031\120\032\144\033" + + "\150\034\151\036\124\037\174\044\102\047\210\053\u01e2" + + "\072\220\073\116\077\165\100\125\102\215\103\204\104" + + "\153\105\127\112\152\113\212\114\045\115\134\124\113" + + "\125\154\142\200\143\177\150\047\152\161\153\205\154" + + "\162\155\040\156\176\157\121\160\171\161\135\162\043" + + "\163\044\164\147\165\137\001\002\000\116\031\120\032" + + "\144\033\150\034\151\036\124\037\174\044\102\047\210" + + "\053\167\072\220\073\116\077\165\100\125\102\215\103" + + "\204\104\153\105\127\112\152\113\212\114\045\115\134" + + "\124\113\125\154\142\200\143\177\150\047\152\161\153" + + "\205\154\162\155\040\156\176\157\121\160\171\161\135" + + "\162\043\163\044\164\147\165\137\001\002\000\010\142" + + "\053\160\046\163\044\001\002\000\002\001\uff53\000\002" + + "\001\uff55\000\002\001\uff61\000\006\041\u01d7\075\u01d6\001" + + "\uff47\000\002\001\uff4f\000\044\004\350\005\335\006\351" + + "\007\343\010\344\011\353\012\346\013\340\014\354\015" + + "\342\016\337\017\345\020\336\021\347\022\341\023\355" + + "\054\u01d2\001\ufed3\000\002\001\ufed5\000\002\001\ufefc\000" + + "\020\053\063\062\065\130\064\134\074\142\053\160\046" + + "\163\044\001\002\000\002\001\uff00\000\004\132\u01ce\001" + + "\ufef1\000\002\001\ufeae\000\002\001\ufef8\000\002\001\ufeac" + + "\000\102\031\120\032\144\033\150\034\151\037\174\044" + + "\102\047\210\053\223\072\220\073\116\077\165\100\125" + + "\101\312\102\215\103\204\105\127\112\152\114\045\124" + + "\113\142\041\150\047\152\161\154\162\155\040\156\176" + + "\157\121\160\171\161\135\162\043\163\044\164\147\165" + + "\137\001\002\000\002\001\uff41\000\002\001\uff43\000\062" + + "\031\120\032\144\033\150\034\151\044\102\047\210\053" + + "\223\072\220\073\116\077\165\100\125\105\127\114\045" + + "\142\041\150\047\154\162\155\040\157\121\160\171\161" + + "\135\162\043\163\044\164\147\165\137\001\002\000\126" + + "\031\120\032\144\033\150\034\151\036\124\037\174\044" + + "\102\047\210\053\u01a6\062\u01a2\072\220\073\116\077\165" + + "\100\125\102\215\103\204\104\153\105\127\112\152\113" + + "\212\114\045\115\134\124\113\125\154\130\u01a3\134\u01a9" + + "\136\246\142\200\143\177\150\047\152\161\153\205\154" + + "\162\155\040\156\176\157\121\160\171\161\135\162\043" + + "\163\044\164\147\165\137\001\002\000\002\001\uff8e\000" + + "\116\031\120\032\144\033\150\034\151\036\124\037\174" + + "\044\102\047\210\053\223\072\220\073\116\077\165\100" + + "\125\102\215\103\204\104\153\105\127\112\152\113\212" + + "\114\045\115\134\124\113\125\154\142\200\143\177\150" + + "\047\152\161\153\205\154\162\155\040\156\176\157\121" + + "\160\171\161\135\162\043\163\044\164\147\165\137\001" + + "\002\000\004\135\u019e\001\ufecf\000\002\001\ufed1\000\006" + + "\055\u019c\101\301\001\ufecb\000\002\001\ufecd\000\116\031" + + "\120\032\144\033\150\034\151\036\124\037\174\044\102" + + "\047\210\053\223\072\220\073\116\077\165\100\125\102" + + "\215\103\204\104\153\105\127\112\152\113\212\114\045" + + "\115\134\124\113\125\154\142\200\143\177\150\047\152" + + "\161\153\205\154\162\155\040\156\176\157\121\160\171" + + "\161\135\162\043\163\044\164\147\165\137\001\002\000" + + "\066\031\120\032\144\033\150\037\174\044\102\047\210" + + "\073\116\100\125\102\215\103\204\105\127\112\152\114" + + "\045\124\113\142\041\150\047\152\161\154\162\155\040" + + "\156\176\160\171\161\135\162\043\163\044\164\147\165" + + "\137\001\002\000\002\001\ufea8\000\002\001\ufeaf\000\010" + + "\144\375\145\373\146\374\001\uff13\000\002\001\uff27\000" + + "\032\060\u017e\070\u017c\071\u0177\076\u0176\106\u017f\107\u0178" + + "\116\u0175\117\u017a\120\u0179\121\u0174\122\u017d\123\u017b\001" + + "\uff3c\000\002\001\uff3f\000\002\001\ufeab\000\004\127\u0170" + + "\001\uff62\000\002\001\uff63\000\002\001\ufeb0\000\002\001" + + "\ufeaa\000\004\101\u016d\001\002\000\020\053\063\062\065" + + "\130\064\134\074\142\053\160\046\163\044\001\002\000" + + "\062\031\120\032\144\033\150\034\151\044\102\047\210" + + "\053\223\072\220\073\116\077\165\100\125\105\127\114" + + "\045\142\041\150\047\154\162\155\040\157\121\160\171" + + "\161\135\162\043\163\044\164\147\165\137\001\002\000" + + "\062\031\120\032\144\033\150\034\151\044\102\047\210" + + "\053\223\072\220\073\116\077\165\100\125\105\127\114" + + "\045\142\041\150\047\154\162\155\040\157\121\160\171" + + "\161\135\162\043\163\044\164\147\165\137\001\002\000" + + "\004\074\u012d\001\uff57\000\002\001\uff59\000\002\001\ufee7" + + "\000\002\001\ufee9\000\020\053\063\062\065\130\064\134" + + "\074\142\053\160\046\163\044\001\002\000\066\031\120" + + "\032\144\033\150\037\174\044\102\047\210\073\116\100" + + "\125\102\215\103\204\105\127\112\152\114\045\124\113" + + "\142\041\150\047\152\161\154\162\155\040\156\176\160" + + "\171\161\135\162\043\163\044\164\147\165\137\001\002" + + "\000\002\001\ufec0\000\002\001\ufec2\000\102\031\120\032" + + "\144\033\150\034\151\037\174\044\102\047\210\053\223" + + "\072\220\073\116\077\165\100\125\101\315\102\215\103" + + "\204\105\127\112\152\114\045\124\113\142\041\150\047" + + "\152\161\154\162\155\040\156\176\157\121\160\171\161" + + "\135\162\043\163\044\164\147\165\137\001\002\000\002" + + "\001\ufeb4\000\120\031\120\032\144\033\150\034\151\036" + + "\124\037\174\044\102\047\210\053\223\072\220\073\116" + + "\077\165\100\125\101\225\102\215\103\204\104\153\105" + + "\127\112\152\113\212\114\045\115\134\124\113\125\154" + + "\142\200\143\177\150\047\152\161\153\205\154\162\155" + + "\040\156\176\157\121\160\171\161\135\162\043\163\044" + + "\164\147\165\137\001\002\000\010\026\u0120\027\u0122\030" + + "\u0121\001\uff05\000\004\151\u011e\001\ufead\000\002\001\uff0a" + + "\000\004\040\u011c\001\ufeeb\000\020\053\063\062\065\130" + + "\064\134\074\142\053\160\046\163\044\001\002\000\002" + + "\001\ufeed\000\020\053\063\062\065\130\064\134\074\142" + + "\053\160\046\163\044\001\002\000\062\031\120\032\144" + + "\033\150\034\151\044\102\047\210\053\223\072\220\073" + + "\116\077\165\100\125\105\127\114\045\142\041\150\047" + + "\154\162\155\040\157\121\160\171\161\135\162\043\163" + + "\044\164\147\165\137\001\002\000\064\031\120\032\144" + + "\033\150\034\151\044\102\047\210\053\223\072\220\073" + + "\116\077\165\100\125\105\127\114\045\142\041\150\047" + + "\151\u0112\154\162\155\040\157\121\160\171\161\135\162" + + "\043\163\044\164\147\165\137\001\002\000\002\001\uffa9" + + "\000\002\001\ufec8\000\002\001\ufec9\000\020\053\063\062" + + "\065\130\064\134\074\142\053\160\046\163\044\001\002" + + "\000\062\031\120\032\144\033\150\034\151\044\102\047" + + "\210\053\223\072\220\073\116\077\165\100\125\105\127" + + "\114\045\142\041\150\047\154\162\155\040\157\121\160" + + "\171\161\135\162\043\163\044\164\147\165\137\001\002" + + "\000\002\001\uff49\000\002\001\uff51\000\066\031\120\032" + + "\144\033\150\037\174\044\102\047\210\073\116\100\125" + + "\102\215\103\204\105\127\112\152\114\045\124\113\142" + + "\041\150\047\152\161\154\162\155\040\156\176\160\171" + + "\161\135\162\043\163\044\164\147\165\137\001\002\000" + + "\002\001\ufea6\000\062\031\120\032\144\033\150\034\151" + + "\044\102\047\210\053\223\072\220\073\116\077\165\100" + + "\125\105\127\114\045\142\041\150\047\154\162\155\040" + + "\157\121\160\171\161\135\162\043\163\044\164\147\165" + + "\137\001\002\000\012\024\257\025\255\110\256\131\254" + + "\001\uff0e\000\002\001\uff12\000\010\142\053\160\046\163" + + "\044\001\002\000\002\001\uff48\000\002\001\uff50\000\100" + + "\031\120\032\144\033\150\034\151\037\174\044\102\047" + + "\210\053\223\072\220\073\116\077\165\100\125\102\215" + + "\103\204\105\127\112\152\114\045\124\113\142\041\150" + + "\047\152\161\154\162\155\040\156\176\157\121\160\171" + + "\161\135\162\043\163\044\164\147\165\137\001\002\000" + + "\002\001\ufef0\000\002\001\ufef4\000\004\101\225\001\002" + + "\000\002\001\ufef7\000\116\031\120\032\144\033\150\034" + + "\151\036\124\037\174\044\102\047\210\053\223\072\220" + + "\073\116\077\165\100\125\102\215\103\204\104\153\105" + + "\127\112\152\113\212\114\045\115\134\124\113\125\154" + + "\142\200\143\177\150\047\152\161\153\205\154\162\155" + + "\040\156\176\157\121\160\171\161\135\162\043\163\044" + + "\164\147\165\137\001\uff67\000\002\001\uff65\000\004\137" + + "\233\001\002\000\004\052\231\001\uff66\000\116\031\120" + + "\032\144\033\150\034\151\036\124\037\174\044\102\047" + + "\210\053\223\072\220\073\116\077\165\100\125\102\215" + + "\103\204\104\153\105\127\112\152\113\212\114\045\115" + + "\134\124\113\125\154\142\200\143\177\150\047\152\161" + + "\153\205\154\162\155\040\156\176\157\121\160\171\161" + + "\135\162\043\163\044\164\147\165\137\001\002\000\002" + + "\001\uff64\000\002\001\ufec6\000\004\060\236\001\002\000" + + "\002\001\uff60\000\116\031\120\032\144\033\150\034\151" + + "\036\124\037\174\044\102\047\210\053\223\072\220\073" + + "\116\077\165\100\125\102\215\103\204\104\153\105\127" + + "\112\152\113\212\114\045\115\134\124\113\125\154\142" + + "\200\143\177\150\047\152\161\153\205\154\162\155\040" + + "\156\176\157\121\160\171\161\135\162\043\163\044\164" + + "\147\165\137\001\002\000\010\045\243\052\240\100\242" + + "\001\002\000\010\142\053\160\046\163\044\001\002\000" + + "\002\001\uff71\000\120\031\120\032\144\033\150\034\151" + + "\036\124\037\174\044\102\047\210\053\223\072\220\073" + + "\116\077\165\100\125\102\215\103\204\104\153\105\127" + + "\112\152\113\212\114\045\115\134\124\113\125\154\136" + + "\246\142\200\143\177\150\047\152\161\153\205\154\162" + + "\155\040\156\176\157\121\160\171\161\135\162\043\163" + + "\044\164\147\165\137\001\002\000\116\031\120\032\144" + + "\033\150\034\151\036\124\037\174\044\102\047\210\053" + + "\223\072\220\073\116\077\165\100\125\102\215\103\204" + + "\104\153\105\127\112\152\113\212\114\045\115\134\124" + + "\113\125\154\142\200\143\177\150\047\152\161\153\205" + + "\154\162\155\040\156\176\157\121\160\171\161\135\162" + + "\043\163\044\164\147\165\137\001\002\000\002\001\uff68" + + "\000\002\001\uff69\000\002\001\uff6c\000\002\001\uff6b\000" + + "\120\031\120\032\144\033\150\034\151\036\124\037\174" + + "\044\102\047\210\053\223\072\220\073\116\077\165\100" + + "\125\102\215\103\204\104\153\105\127\112\152\113\212" + + "\114\045\115\134\124\113\125\154\136\251\142\200\143" + + "\177\150\047\152\161\153\205\154\162\155\040\156\176" + + "\157\121\160\171\161\135\162\043\163\044\164\147\165" + + "\137\001\002\000\002\001\uff6d\000\002\001\uff6a\000\002" + + "\001\uff70\000\100\031\120\032\144\033\150\034\151\037" + + "\174\044\102\047\267\053\223\072\261\073\116\077\262" + + "\100\125\102\215\103\204\105\127\112\152\114\045\124" + + "\113\142\041\150\047\152\161\154\264\155\040\156\176" + + "\157\272\160\171\161\263\162\043\163\044\164\147\165" + + "\137\001\002\000\100\031\120\032\144\033\150\034\151" + + "\037\174\044\102\047\267\053\223\072\261\073\116\077" + + "\262\100\125\102\215\103\204\105\127\112\152\114\045" + + "\124\113\142\041\150\047\152\161\154\264\155\040\156" + + "\176\157\272\160\171\161\263\162\043\163\044\164\147" + + "\165\137\001\002\000\100\031\120\032\144\033\150\034" + + "\151\037\174\044\102\047\267\053\223\072\261\073\116" + + "\077\262\100\125\102\215\103\204\105\127\112\152\114" + + "\045\124\113\142\041\150\047\152\161\154\264\155\040" + + "\156\176\157\272\160\171\161\263\162\043\163\044\164" + + "\147\165\137\001\002\000\100\031\120\032\144\033\150" + + "\034\151\037\174\044\102\047\267\053\223\072\261\073" + + "\116\077\262\100\125\102\215\103\204\105\127\112\152" + + "\114\045\124\113\142\041\150\047\152\161\154\264\155" + + "\040\156\176\157\272\160\171\161\263\162\043\163\044" + + "\164\147\165\137\001\002\000\004\040\332\001\ufeeb\000" + + "\062\031\120\032\144\033\150\034\151\044\102\047\267" + + "\053\223\072\261\073\116\077\262\100\125\105\127\114" + + "\045\142\041\150\047\154\264\155\040\157\272\160\171" + + "\161\263\162\043\163\044\164\147\165\137\001\002\000" + + "\064\031\120\032\144\033\150\034\151\044\102\047\267" + + "\053\223\072\261\073\116\077\262\100\125\101\315\105" + + "\127\114\045\142\041\150\047\154\264\155\040\157\272" + + "\160\171\161\263\162\043\163\044\164\147\165\137\001" + + "\002\000\050\031\120\032\144\033\150\044\102\047\267" + + "\073\116\100\125\105\127\114\045\142\041\150\047\154" + + "\264\155\040\160\171\161\263\162\043\163\044\164\147" + + "\165\137\001\002\000\050\031\120\032\144\033\150\044" + + "\102\047\267\073\116\100\125\105\127\114\045\142\041" + + "\150\047\154\264\155\040\160\171\161\263\162\043\163" + + "\044\164\147\165\137\001\002\000\044\004\350\005\335" + + "\006\351\007\343\010\344\011\353\012\346\013\340\014" + + "\354\015\342\016\337\017\345\020\336\021\347\022\341" + + "\023\355\054\334\001\ufed3\000\004\132\330\001\ufef1\000" + + "\050\031\120\032\144\033\150\044\102\047\267\073\116" + + "\100\125\105\127\114\045\142\041\150\047\154\264\155" + + "\040\160\171\161\263\162\043\163\044\164\147\165\137" + + "\001\002\000\010\026\321\027\323\030\322\001\uff02\000" + + "\006\055\302\101\301\001\ufecb\000\064\031\120\032\144" + + "\033\150\034\151\044\102\047\267\053\223\072\261\073" + + "\116\077\262\100\125\101\312\105\127\114\045\142\041" + + "\150\047\154\264\155\040\157\272\160\171\161\263\162" + + "\043\163\044\164\147\165\137\001\002\000\004\135\275" + + "\001\ufecf\000\002\001\uff07\000\060\031\120\032\144\033" + + "\150\034\151\044\102\047\267\053\223\073\116\077\276" + + "\100\125\105\127\114\045\142\041\150\047\154\264\155" + + "\040\157\277\160\171\161\263\162\043\163\044\164\147" + + "\165\137\001\002\000\004\101\315\001\002\000\004\101" + + "\312\001\002\000\006\055\302\101\301\001\ufeca\000\116" + + "\031\120\032\144\033\150\034\151\036\124\037\174\044" + + "\102\047\210\053\223\072\220\073\116\077\165\100\125" + + "\102\215\103\204\104\153\105\127\112\152\113\212\114" + + "\045\115\134\124\113\125\154\142\200\143\177\150\047" + + "\152\161\153\205\154\162\155\040\156\176\157\121\160" + + "\171\161\135\162\043\163\044\164\147\165\137\001\uff67" + + "\000\060\031\120\032\144\033\150\034\303\044\102\047" + + "\267\053\305\073\116\077\304\100\125\105\127\114\045" + + "\142\041\150\047\154\264\155\040\157\306\160\171\161" + + "\263\162\043\163\044\164\147\165\137\001\002\000\002" + + "\001\ufebd\000\002\001\ufebc\000\002\001\ufebe\000\002\001" + + "\ufebb\000\002\001\ufebf\000\004\137\311\001\002\000\002" + + "\001\ufec7\000\116\031\120\032\144\033\150\034\151\036" + + "\124\037\174\044\102\047\210\053\223\072\220\073\116" + + "\077\165\100\125\102\215\103\204\104\153\105\127\112" + + "\152\113\212\114\045\115\134\124\113\125\154\142\200" + + "\143\177\150\047\152\161\153\205\154\162\155\040\156" + + "\176\157\121\160\171\161\135\162\043\163\044\164\147" + + "\165\137\001\uff67\000\004\137\314\001\002\000\002\001" + + "\ufec3\000\116\031\120\032\144\033\150\034\151\036\124" + + "\037\174\044\102\047\210\053\223\072\220\073\116\077" + + "\165\100\125\102\215\103\204\104\153\105\127\112\152" + + "\113\212\114\045\115\134\124\113\125\154\142\200\143" + + "\177\150\047\152\161\153\205\154\162\155\040\156\176" + + "\157\121\160\171\161\135\162\043\163\044\164\147\165" + + "\137\001\uff67\000\004\137\317\001\002\000\002\001\ufec4" + + "\000\002\001\ufeef\000\062\031\120\032\144\033\150\034" + + "\151\044\102\047\267\053\223\072\261\073\116\077\262" + + "\100\125\105\127\114\045\142\041\150\047\154\264\155" + + "\040\157\272\160\171\161\263\162\043\163\044\164\147" + + "\165\137\001\002\000\062\031\120\032\144\033\150\034" + + "\151\044\102\047\267\053\223\072\261\073\116\077\262" + + "\100\125\105\127\114\045\142\041\150\047\154\264\155" + + "\040\157\272\160\171\161\263\162\043\163\044\164\147" + + "\165\137\001\002\000\062\031\120\032\144\033\150\034" + + "\151\044\102\047\267\053\223\072\261\073\116\077\262" + + "\100\125\105\127\114\045\142\041\150\047\154\264\155" + + "\040\157\272\160\171\161\263\162\043\163\044\164\147" + + "\165\137\001\002\000\002\001\ufefa\000\002\001\ufef9\000" + + "\002\001\ufefb\000\002\001\ufeb1\000\060\031\120\032\144" + + "\033\150\034\151\044\102\047\267\053\223\073\116\077" + + "\276\100\125\105\127\114\045\142\041\150\047\154\264" + + "\155\040\157\277\160\171\161\263\162\043\163\044\164" + + "\147\165\137\001\002\000\004\040\332\001\ufeea\000\060" + + "\031\120\032\144\033\150\034\151\044\102\047\267\053" + + "\223\073\116\077\276\100\125\105\127\114\045\142\041" + + "\150\047\154\264\155\040\157\277\160\171\161\263\162" + + "\043\163\044\164\147\165\137\001\002\000\002\001\ufee6" + + "\000\060\031\120\032\144\033\150\034\151\044\102\047" + + "\267\053\223\073\116\077\276\100\125\105\127\114\045" + + "\142\041\150\047\154\264\155\040\157\277\160\171\161" + + "\263\162\043\163\044\164\147\165\137\001\002\000\002" + + "\001\ufee4\000\002\001\ufed9\000\002\001\ufedb\000\002\001" + + "\ufede\000\002\001\ufed7\000\002\001\ufedc\000\002\001\ufee2" + + "\000\002\001\ufee1\000\002\001\ufeda\000\002\001\ufedf\000" + + "\002\001\ufed8\000\002\001\ufee5\000\002\001\ufee3\000\060" + + "\031\120\032\144\033\150\034\151\044\102\047\267\053" + + "\223\073\116\077\276\100\125\105\127\114\045\142\041" + + "\150\047\154\264\155\040\157\277\160\171\161\263\162" + + "\043\163\044\164\147\165\137\001\002\000\002\001\ufee0" + + "\000\002\001\ufedd\000\002\001\ufed6\000\002\001\ufed2\000" + + "\004\135\275\001\ufece\000\002\001\ufeb2\000\002\001\ufeb3" + + "\000\002\001\ufeee\000\010\026\321\027\323\030\322\001" + + "\uff03\000\002\001\uff08\000\010\026\321\027\323\030\322" + + "\001\uff01\000\002\001\uff06\000\010\026\321\027\323\030" + + "\322\001\uff04\000\002\001\uff09\000\010\144\375\145\373" + + "\146\374\001\uff19\000\002\001\uff2d\000\100\031\120\032" + + "\144\033\150\034\151\037\174\044\102\047\267\053\223" + + "\072\261\073\116\077\262\100\125\102\215\103\204\105" + + "\127\112\152\114\045\124\113\142\041\150\047\152\161" + + "\154\264\155\040\156\176\157\272\160\171\161\263\162" + + "\043\163\044\164\147\165\137\001\002\000\100\031\120" + + "\032\144\033\150\034\151\037\174\044\102\047\267\053" + + "\223\072\261\073\116\077\262\100\125\102\215\103\204" + + "\105\127\112\152\114\045\124\113\142\041\150\047\152" + + "\161\154\264\155\040\156\176\157\272\160\171\161\263" + + "\162\043\163\044\164\147\165\137\001\002\000\100\031" + + "\120\032\144\033\150\034\151\037\174\044\102\047\267" + + "\053\223\072\261\073\116\077\262\100\125\102\215\103" + + "\204\105\127\112\152\114\045\124\113\142\041\150\047" + + "\152\161\154\264\155\040\156\176\157\272\160\171\161" + + "\263\162\043\163\044\164\147\165\137\001\002\000\010" + + "\026\321\027\323\030\322\001\uff05\000\012\024\u0104\025" + + "\u0102\110\u0103\131\u0101\001\uff0d\000\002\001\uff11\000\062" + + "\031\120\032\144\033\150\034\151\044\102\047\267\053" + + "\223\072\261\073\116\077\262\100\125\105\127\114\045" + + "\142\041\150\047\154\264\155\040\157\272\160\171\161" + + "\263\162\043\163\044\164\147\165\137\001\002\000\062" + + "\031\120\032\144\033\150\034\151\044\102\047\267\053" + + "\223\072\261\073\116\077\262\100\125\105\127\114\045" + + "\142\041\150\047\154\264\155\040\157\272\160\171\161" + + "\263\162\043\163\044\164\147\165\137\001\002\000\062" + + "\031\120\032\144\033\150\034\151\044\102\047\267\053" + + "\223\072\261\073\116\077\262\100\125\105\127\114\045" + + "\142\041\150\047\154\264\155\040\157\272\160\171\161" + + "\263\162\043\163\044\164\147\165\137\001\002\000\062" + + "\031\120\032\144\033\150\034\151\044\102\047\267\053" + + "\223\072\261\073\116\077\262\100\125\105\127\114\045" + + "\142\041\150\047\154\264\155\040\157\272\160\171\161" + + "\263\162\043\163\044\164\147\165\137\001\002\000\012" + + "\024\u0104\025\u0102\110\u0103\131\u0101\001\uff0b\000\002\001" + + "\uff0f\000\012\024\u0104\025\u0102\110\u0103\131\u0101\001\uff0c" + + "\000\002\001\uff10\000\002\001\ufeb8\000\002\001\ufeb5\000" + + "\010\144\375\145\373\146\374\001\uff18\000\002\001\uff2c" + + "\000\002\001\uff76\000\010\045\243\052\u010f\100\242\001" + + "\002\000\020\053\063\062\065\130\064\134\074\142\053" + + "\160\046\163\044\001\002\000\002\001\uff5c\000\002\001" + + "\uff77\000\006\150\u0115\163\044\001\002\000\010\144\375" + + "\145\373\146\374\001\uff14\000\002\001\uff28\000\002\001" + + "\uffa5\000\010\144\375\145\373\146\374\001\uff15\000\002" + + "\001\uff29\000\010\045\243\052\u010f\100\242\001\002\000" + + "\002\001\uff5a\000\010\045\243\052\u010f\100\242\001\002" + + "\000\002\001\uff5f\000\076\031\120\032\144\033\150\034" + + "\151\037\174\044\102\047\267\053\223\073\116\077\276" + + "\100\125\102\215\103\204\105\127\112\152\114\045\124" + + "\113\142\041\150\047\152\161\154\264\155\040\156\176" + + "\157\277\160\171\161\263\162\043\163\044\164\147\165" + + "\137\001\002\000\002\001\ufee8\000\004\163\044\001\002" + + "\000\004\151\056\001\uff9c\000\100\031\120\032\144\033" + + "\150\034\151\037\174\044\102\047\267\053\223\072\261" + + "\073\116\077\262\100\125\102\215\103\204\105\127\112" + + "\152\114\045\124\113\142\041\150\047\152\161\154\264" + + "\155\040\156\176\157\272\160\171\161\263\162\043\163" + + "\044\164\147\165\137\001\002\000\100\031\120\032\144" + + "\033\150\034\151\037\174\044\102\047\267\053\223\072" + + "\261\073\116\077\262\100\125\102\215\103\204\105\127" + + "\112\152\114\045\124\113\142\041\150\047\152\161\154" + + "\264\155\040\156\176\157\272\160\171\161\263\162\043" + + "\163\044\164\147\165\137\001\002\000\100\031\120\032" + + "\144\033\150\034\151\037\174\044\102\047\267\053\223" + + "\072\261\073\116\077\262\100\125\102\215\103\204\105" + + "\127\112\152\114\045\124\113\142\041\150\047\152\161" + + "\154\264\155\040\156\176\157\272\160\171\161\263\162" + + "\043\163\044\164\147\165\137\001\002\000\002\001\ufefe" + + "\000\002\001\ufefd\000\002\001\ufeff\000\002\001\uff88\000" + + "\002\001\ufef2\000\002\001\ufef5\000\002\001\ufeb9\000\002" + + "\001\ufeb6\000\010\045\243\052\u010f\100\242\001\002\000" + + "\002\001\uff5d\000\116\031\120\032\144\033\150\034\151" + + "\036\u0139\037\174\044\102\047\267\053\223\072\261\073" + + "\116\077\262\100\125\102\215\103\204\104\u0138\105\127" + + "\112\152\113\u0137\114\045\115\u0130\124\113\125\u0136\142" + + "\u012e\143\u013b\150\047\152\161\153\u012f\154\264\155\040" + + "\156\176\157\272\160\171\161\263\162\043\163\044\164" + + "\147\165\137\001\002\000\064\031\120\032\144\033\150" + + "\034\151\044\102\047\267\053\223\072\261\073\116\077" + + "\262\100\125\105\127\114\045\142\041\150\047\151\u0112" + + "\154\264\155\040\157\272\160\171\161\263\162\043\163" + + "\044\164\147\165\137\001\002\000\062\031\120\032\144" + + "\033\150\034\151\044\102\047\267\053\223\072\261\073" + + "\116\077\262\100\125\105\127\114\045\142\041\150\047" + + "\154\264\155\040\157\272\160\171\161\263\162\043\163" + + "\044\164\147\165\137\001\002\000\100\031\120\032\144" + + "\033\150\034\151\036\u0139\044\102\047\267\053\223\072" + + "\261\073\116\077\262\100\125\104\u0138\105\127\113\u0137" + + "\114\045\115\u0130\125\u0136\142\u012e\143\u013b\150\047\153" + + "\u012f\154\264\155\040\157\272\160\171\161\263\162\043" + + "\163\044\164\147\165\137\001\002\000\010\144\u013f\145" + + "\u013d\146\u013e\001\uff13\000\032\060\u0156\070\u0154\071\u014f" + + "\076\u014d\106\u0157\107\u0150\116\u014e\117\u0152\120\u0151\121" + + "\u014c\122\u0155\123\u0153\001\uff3c\000\006\041\u0145\075\u0144" + + "\001\uff47\000\002\001\uff52\000\012\024\u0104\025\u0102\110" + + "\u0103\131\u0101\001\uff0e\000\062\031\120\032\144\033\150" + + "\034\151\044\102\047\267\053\223\072\261\073\116\077" + + "\262\100\125\105\127\114\045\142\041\150\047\154\264" + + "\155\040\157\272\160\171\161\263\162\043\163\044\164" + + "\147\165\137\001\002\000\062\031\120\032\144\033\150" + + "\034\151\044\102\047\267\053\223\072\261\073\116\077" + + "\262\100\125\105\127\114\045\142\041\150\047\154\264" + + "\155\040\157\272\160\171\161\263\162\043\163\044\164" + + "\147\165\137\001\002\000\062\031\120\032\144\033\150" + + "\034\151\044\102\047\267\053\223\072\261\073\116\077" + + "\262\100\125\105\127\114\045\142\041\150\047\154\264" + + "\155\040\157\272\160\171\161\263\162\043\163\044\164" + + "\147\165\137\001\002\000\062\031\120\032\144\033\150" + + "\034\151\044\102\047\267\053\223\072\261\073\116\077" + + "\262\100\125\105\127\114\045\142\041\150\047\154\264" + + "\155\040\157\272\160\171\161\263\162\043\163\044\164" + + "\147\165\137\001\002\000\002\001\uff54\000\062\031\120" + + "\032\144\033\150\034\151\044\102\047\267\053\223\072" + + "\261\073\116\077\262\100\125\105\127\114\045\142\041" + + "\150\047\154\264\155\040\157\272\160\171\161\263\162" + + "\043\163\044\164\147\165\137\001\002\000\010\144\u013f" + + "\145\u013d\146\u013e\001\uff15\000\062\031\120\032\144\033" + + "\150\034\151\044\102\047\267\053\223\072\261\073\116" + + "\077\262\100\125\105\127\114\045\142\041\150\047\154" + + "\264\155\040\157\272\160\171\161\263\162\043\163\044" + + "\164\147\165\137\001\002\000\062\031\120\032\144\033" + + "\150\034\151\044\102\047\267\053\223\072\261\073\116" + + "\077\262\100\125\105\127\114\045\142\041\150\047\154" + + "\264\155\040\157\272\160\171\161\263\162\043\163\044" + + "\164\147\165\137\001\002\000\062\031\120\032\144\033" + + "\150\034\151\044\102\047\267\053\223\072\261\073\116" + + "\077\262\100\125\105\127\114\045\142\041\150\047\154" + + "\264\155\040\157\272\160\171\161\263\162\043\163\044" + + "\164\147\165\137\001\002\000\010\144\u013f\145\u013d\146" + + "\u013e\001\uff1a\000\010\144\u013f\145\u013d\146\u013e\001\uff17" + + "\000\010\144\u013f\145\u013d\146\u013e\001\uff19\000\010\144" + + "\u013f\145\u013d\146\u013e\001\uff16\000\100\031\120\032\144" + + "\033\150\034\151\036\u0139\044\102\047\267\053\223\072" + + "\261\073\116\077\262\100\125\104\u0138\105\127\113\u0137" + + "\114\045\115\u0130\125\u0136\142\u012e\143\u013b\150\047\153" + + "\u012f\154\264\155\040\157\272\160\171\161\263\162\043" + + "\163\044\164\147\165\137\001\002\000\100\031\120\032" + + "\144\033\150\034\151\036\u0139\044\102\047\267\053\223" + + "\072\261\073\116\077\262\100\125\104\u0138\105\127\113" + + "\u0137\114\045\115\u0130\125\u0136\142\u012e\143\u013b\150\047" + + "\153\u012f\154\264\155\040\157\272\160\171\161\263\162" + + "\043\163\044\164\147\165\137\001\002\000\002\001\uff40" + + "\000\002\001\uff44\000\004\056\u0149\001\uff49\000\100\031" + + "\120\032\144\033\150\034\151\036\u0139\044\102\047\267" + + "\053\223\072\261\073\116\077\262\100\125\104\u0138\105" + + "\127\113\u0137\114\045\115\u0130\125\u0136\142\u012e\143\u013b" + + "\150\047\153\u012f\154\264\155\040\157\272\160\171\161" + + "\263\162\043\163\044\164\147\165\137\001\002\000\002" + + "\001\uff45\000\002\001\uff46\000\062\031\120\032\144\033" + + "\150\034\151\044\102\047\267\053\223\072\261\073\116" + + "\077\262\100\125\105\127\114\045\142\041\150\047\154" + + "\264\155\040\157\272\160\171\161\263\162\043\163\044" + + "\164\147\165\137\001\002\000\062\031\120\032\144\033" + + "\150\034\151\044\102\047\267\053\223\072\261\073\116" + + "\077\262\100\125\105\127\114\045\142\041\150\047\154" + + "\264\155\040\157\272\160\171\161\263\162\043\163\044" + + "\164\147\165\137\001\002\000\062\031\120\032\144\033" + + "\150\034\151\044\102\047\267\053\223\072\261\073\116" + + "\077\262\100\125\105\127\114\045\142\041\150\047\154" + + "\264\155\040\157\272\160\171\161\263\162\043\163\044" + + "\164\147\165\137\001\002\000\062\031\120\032\144\033" + + "\150\034\151\044\102\047\267\053\223\072\261\073\116" + + "\077\262\100\125\105\127\114\045\142\041\150\047\154" + + "\264\155\040\157\272\160\171\161\263\162\043\163\044" + + "\164\147\165\137\001\002\000\062\031\120\032\144\033" + + "\150\034\151\044\102\047\267\053\223\072\261\073\116" + + "\077\262\100\125\105\127\114\045\142\041\150\047\154" + + "\264\155\040\157\272\160\171\161\263\162\043\163\044" + + "\164\147\165\137\001\002\000\062\031\120\032\144\033" + + "\150\034\151\044\102\047\267\053\223\072\261\073\116" + + "\077\262\100\125\105\127\114\045\142\041\150\047\154" + + "\264\155\040\157\272\160\171\161\263\162\043\163\044" + + "\164\147\165\137\001\002\000\062\031\120\032\144\033" + + "\150\034\151\044\102\047\267\053\223\072\261\073\116" + + "\077\262\100\125\105\127\114\045\142\041\150\047\154" + + "\264\155\040\157\272\160\171\161\263\162\043\163\044" + + "\164\147\165\137\001\002\000\062\031\120\032\144\033" + + "\150\034\151\044\102\047\267\053\223\072\261\073\116" + + "\077\262\100\125\105\127\114\045\142\041\150\047\154" + + "\264\155\040\157\272\160\171\161\263\162\043\163\044" + + "\164\147\165\137\001\002\000\062\031\120\032\144\033" + + "\150\034\151\044\102\047\267\053\223\072\261\073\116" + + "\077\262\100\125\105\127\114\045\142\041\150\047\154" + + "\264\155\040\157\272\160\171\161\263\162\043\163\044" + + "\164\147\165\137\001\002\000\062\031\120\032\144\033" + + "\150\034\151\044\102\047\267\053\223\072\261\073\116" + + "\077\262\100\125\105\127\114\045\142\041\150\047\154" + + "\264\155\040\157\272\160\171\161\263\162\043\163\044" + + "\164\147\165\137\001\002\000\062\031\120\032\144\033" + + "\150\034\151\044\102\047\267\053\223\072\261\073\116" + + "\077\262\100\125\105\127\114\045\142\041\150\047\154" + + "\264\155\040\157\272\160\171\161\263\162\043\163\044" + + "\164\147\165\137\001\002\000\062\031\120\032\144\033" + + "\150\034\151\044\102\047\267\053\223\072\261\073\116" + + "\077\262\100\125\105\127\114\045\142\041\150\047\154" + + "\264\155\040\157\272\160\171\161\263\162\043\163\044" + + "\164\147\165\137\001\002\000\010\144\u013f\145\u013d\146" + + "\u013e\001\uff24\000\010\144\u013f\145\u013d\146\u013e\001\uff25" + + "\000\010\144\u013f\145\u013d\146\u013e\001\uff1e\000\010\144" + + "\u013f\145\u013d\146\u013e\001\uff23\000\010\144\u013f\145\u013d" + + "\146\u013e\001\uff1c\000\010\144\u013f\145\u013d\146\u013e\001" + + "\uff1d\000\010\144\u013f\145\u013d\146\u013e\001\uff1b\000\010" + + "\144\u013f\145\u013d\146\u013e\001\uff22\000\010\144\u013f\145" + + "\u013d\146\u013e\001\uff21\000\010\144\u013f\145\u013d\146\u013e" + + "\001\uff1f\000\010\144\u013f\145\u013d\146\u013e\001\uff26\000" + + "\010\144\u013f\145\u013d\146\u013e\001\uff20\000\002\001\uff3b" + + "\000\010\144\u013f\145\u013d\146\u013e\001\uff18\000\010\144" + + "\u013f\145\u013d\146\u013e\001\uff14\000\010\144\375\145\373" + + "\146\374\001\uff16\000\002\001\uff2a\000\010\144\375\145" + + "\373\146\374\001\uff17\000\002\001\uff2b\000\010\045\243" + + "\052\u010f\100\242\001\002\000\002\001\uff5e\000\116\031" + + "\120\032\144\033\150\034\151\036\124\037\174\044\102" + + "\047\210\053\223\072\220\073\116\077\165\100\125\102" + + "\215\103\204\104\153\105\127\112\152\113\212\114\045" + + "\115\134\124\113\125\154\142\200\143\177\150\047\152" + + "\161\153\205\154\162\155\040\156\176\157\121\160\171" + + "\161\135\162\043\163\044\164\147\165\137\001\uff67\000" + + "\004\137\u016f\001\002\000\002\001\ufec5\000\116\031\120" + + "\032\144\033\150\034\151\036\u0139\037\174\044\102\047" + + "\267\053\223\072\261\073\116\077\262\100\125\102\215" + + "\103\204\104\u0138\105\127\112\152\113\u0137\114\045\115" + + "\u0130\124\113\125\u0136\142\u012e\143\u013b\150\047\152\161" + + "\153\u012f\154\264\155\040\156\176\157\272\160\171\161" + + "\263\162\043\163\044\164\147\165\137\001\002\000\004" + + "\074\u0173\001\uff56\000\002\001\uff58\000\100\031\120\032" + + "\144\033\150\034\151\036\u0139\044\102\047\267\053\223" + + "\072\261\073\116\077\262\100\125\104\u0138\105\127\113" + + "\u0137\114\045\115\u0130\125\u0136\142\u012e\143\u013b\150\047" + + "\153\u012f\154\264\155\040\157\272\160\171\161\263\162" + + "\043\163\044\164\147\165\137\001\002\000\062\031\120" + + "\032\144\033\150\034\151\044\102\047\210\053\223\072" + + "\220\073\116\077\165\100\125\105\127\114\045\142\041" + + "\150\047\154\162\155\040\157\121\160\171\161\135\162" + + "\043\163\044\164\147\165\137\001\002\000\062\031\120" + + "\032\144\033\150\034\151\044\102\047\210\053\223\072" + + "\220\073\116\077\165\100\125\105\127\114\045\142\041" + + "\150\047\154\162\155\040\157\121\160\171\161\135\162" + + "\043\163\044\164\147\165\137\001\002\000\062\031\120" + + "\032\144\033\150\034\151\044\102\047\210\053\223\072" + + "\220\073\116\077\165\100\125\105\127\114\045\142\041" + + "\150\047\154\162\155\040\157\121\160\171\161\135\162" + + "\043\163\044\164\147\165\137\001\002\000\062\031\120" + + "\032\144\033\150\034\151\044\102\047\210\053\223\072" + + "\220\073\116\077\165\100\125\105\127\114\045\142\041" + + "\150\047\154\162\155\040\157\121\160\171\161\135\162" + + "\043\163\044\164\147\165\137\001\002\000\062\031\120" + + "\032\144\033\150\034\151\044\102\047\210\053\223\072" + + "\220\073\116\077\165\100\125\105\127\114\045\142\041" + + "\150\047\154\162\155\040\157\121\160\171\161\135\162" + + "\043\163\044\164\147\165\137\001\002\000\062\031\120" + + "\032\144\033\150\034\151\044\102\047\210\053\223\072" + + "\220\073\116\077\165\100\125\105\127\114\045\142\041" + + "\150\047\154\162\155\040\157\121\160\171\161\135\162" + + "\043\163\044\164\147\165\137\001\002\000\062\031\120" + + "\032\144\033\150\034\151\044\102\047\210\053\223\072" + + "\220\073\116\077\165\100\125\105\127\114\045\142\041" + + "\150\047\154\162\155\040\157\121\160\171\161\135\162" + + "\043\163\044\164\147\165\137\001\002\000\062\031\120" + + "\032\144\033\150\034\151\044\102\047\210\053\223\072" + + "\220\073\116\077\165\100\125\105\127\114\045\142\041" + + "\150\047\154\162\155\040\157\121\160\171\161\135\162" + + "\043\163\044\164\147\165\137\001\002\000\062\031\120" + + "\032\144\033\150\034\151\044\102\047\210\053\223\072" + + "\220\073\116\077\165\100\125\105\127\114\045\142\041" + + "\150\047\154\162\155\040\157\121\160\171\161\135\162" + + "\043\163\044\164\147\165\137\001\002\000\062\031\120" + + "\032\144\033\150\034\151\044\102\047\210\053\223\072" + + "\220\073\116\077\165\100\125\105\127\114\045\142\041" + + "\150\047\154\162\155\040\157\121\160\171\161\135\162" + + "\043\163\044\164\147\165\137\001\002\000\062\031\120" + + "\032\144\033\150\034\151\044\102\047\210\053\223\072" + + "\220\073\116\077\165\100\125\105\127\114\045\142\041" + + "\150\047\154\162\155\040\157\121\160\171\161\135\162" + + "\043\163\044\164\147\165\137\001\002\000\062\031\120" + + "\032\144\033\150\034\151\044\102\047\210\053\223\072" + + "\220\073\116\077\165\100\125\105\127\114\045\142\041" + + "\150\047\154\162\155\040\157\121\160\171\161\135\162" + + "\043\163\044\164\147\165\137\001\002\000\010\144\375" + + "\145\373\146\374\001\uff24\000\002\001\uff38\000\010\144" + + "\375\145\373\146\374\001\uff25\000\002\001\uff39\000\010" + + "\144\375\145\373\146\374\001\uff1e\000\002\001\uff32\000" + + "\010\144\375\145\373\146\374\001\uff23\000\002\001\uff37" + + "\000\010\144\375\145\373\146\374\001\uff1c\000\002\001" + + "\uff30\000\010\144\375\145\373\146\374\001\uff1d\000\002" + + "\001\uff31\000\010\144\375\145\373\146\374\001\uff1b\000" + + "\002\001\uff2f\000\010\144\375\145\373\146\374\001\uff22" + + "\000\002\001\uff36\000\010\144\375\145\373\146\374\001" + + "\uff21\000\002\001\uff35\000\010\144\375\145\373\146\374" + + "\001\uff26\000\002\001\uff3a\000\010\144\375\145\373\146" + + "\374\001\uff1f\000\002\001\uff33\000\010\144\375\145\373" + + "\146\374\001\uff20\000\002\001\uff34\000\002\001\ufeba\000" + + "\002\001\ufeb7\000\002\001\uff3e\000\002\001\uff3d\000\076" + + "\031\120\032\144\033\150\034\303\037\174\044\102\047" + + "\267\053\305\073\116\077\304\100\125\102\215\103\204" + + "\105\127\112\152\114\045\124\113\142\041\150\047\152" + + "\161\154\264\155\040\156\176\157\306\160\171\161\263" + + "\162\043\163\044\164\147\165\137\001\002\000\002\001" + + "\ufec1\000\076\031\120\032\144\033\150\034\151\037\174" + + "\044\102\047\267\053\223\073\116\077\276\100\125\102" + + "\215\103\204\105\127\112\152\114\045\124\113\142\041" + + "\150\047\152\161\154\264\155\040\156\176\157\277\160" + + "\171\161\263\162\043\163\044\164\147\165\137\001\002" + + "\000\002\001\ufecc\000\004\140\u01a1\001\002\000\002\001" + + "\ufea9\000\010\142\053\160\046\163\044\001\002\000\010" + + "\142\053\160\046\163\044\001\002\000\006\051\u01bc\052" + + "\077\001\002\000\012\045\243\052\u01b6\100\242\136\u01b8" + + "\001\002\000\012\101\225\142\053\160\046\163\044\001" + + "\002\000\002\001\uff78\000\006\051\uff98\052\uff98\001\uffa9" + + "\000\012\053\u01aa\142\053\160\046\163\044\001\002\000" + + "\010\142\053\160\046\163\044\001\002\000\006\051\101" + + "\052\077\001\002\000\006\051\u01ad\052\077\001\002\000" + + "\116\031\120\032\144\033\150\034\151\036\124\037\174" + + "\044\102\047\210\053\u01af\072\220\073\116\077\165\100" + + "\125\102\215\103\204\104\153\105\127\112\152\113\212" + + "\114\045\115\134\124\113\125\154\142\200\143\177\150" + + "\047\152\161\153\205\154\162\155\040\156\176\157\121" + + "\160\171\161\135\162\043\163\044\164\147\165\137\001" + + "\002\000\002\001\uff8f\000\120\031\120\032\144\033\150" + + "\034\151\036\124\037\174\044\102\047\210\053\223\072" + + "\220\073\116\077\165\100\125\101\225\102\215\103\204" + + "\104\153\105\127\112\152\113\212\114\045\115\134\124" + + "\113\125\154\142\200\143\177\150\047\152\161\153\205" + + "\154\162\155\040\156\176\157\121\160\171\161\135\162" + + "\043\163\044\164\147\165\137\001\002\000\002\001\uff89" + + "\000\006\051\u01b2\052\077\001\002\000\116\031\120\032" + + "\144\033\150\034\151\036\124\037\174\044\102\047\210" + + "\053\u01b4\072\220\073\116\077\165\100\125\102\215\103" + + "\204\104\153\105\127\112\152\113\212\114\045\115\134" + + "\124\113\125\154\142\200\143\177\150\047\152\161\153" + + "\205\154\162\155\040\156\176\157\121\160\171\161\135" + + "\162\043\163\044\164\147\165\137\001\002\000\002\001" + + "\uff90\000\120\031\120\032\144\033\150\034\151\036\124" + + "\037\174\044\102\047\210\053\223\072\220\073\116\077" + + "\165\100\125\101\225\102\215\103\204\104\153\105\127" + + "\112\152\113\212\114\045\115\134\124\113\125\154\142" + + "\200\143\177\150\047\152\161\153\205\154\162\155\040" + + "\156\176\157\121\160\171\161\135\162\043\163\044\164" + + "\147\165\137\001\002\000\002\001\uff8a\000\020\053\u01ba" + + "\062\u01a2\130\u01a3\134\u01a9\142\053\160\046\163\044\001" + + "\002\000\004\136\u01b9\001\002\000\002\001\ufea4\000\002" + + "\001\ufea5\000\010\142\053\160\046\163\044\001\002\000" + + "\002\001\uff79\000\116\031\120\032\144\033\150\034\151" + + "\036\124\037\174\044\102\047\210\053\u01be\072\220\073" + + "\116\077\165\100\125\102\215\103\204\104\153\105\127" + + "\112\152\113\212\114\045\115\134\124\113\125\154\142" + + "\200\143\177\150\047\152\161\153\205\154\162\155\040" + + "\156\176\157\121\160\171\161\135\162\043\163\044\164" + + "\147\165\137\001\002\000\002\001\uff8d\000\120\031\120" + + "\032\144\033\150\034\151\036\124\037\174\044\102\047" + + "\210\053\223\072\220\073\116\077\165\100\125\101\225" + + "\102\215\103\204\104\153\105\127\112\152\113\212\114" + + "\045\115\134\124\113\125\154\142\200\143\177\150\047" + + "\152\161\153\205\154\162\155\040\156\176\157\121\160" + + "\171\161\135\162\043\163\044\164\147\165\137\001\002" + + "\000\002\001\uff87\000\006\051\u01c1\052\077\001\002\000" + + "\116\031\120\032\144\033\150\034\151\036\124\037\174" + + "\044\102\047\210\053\u01c3\072\220\073\116\077\165\100" + + "\125\102\215\103\204\104\153\105\127\112\152\113\212" + + "\114\045\115\134\124\113\125\154\142\200\143\177\150" + + "\047\152\161\153\205\154\162\155\040\156\176\157\121" + + "\160\171\161\135\162\043\163\044\164\147\165\137\001" + + "\002\000\002\001\uff92\000\120\031\120\032\144\033\150" + + "\034\151\036\124\037\174\044\102\047\210\053\223\072" + + "\220\073\116\077\165\100\125\101\225\102\215\103\204" + + "\104\153\105\127\112\152\113\212\114\045\115\134\124" + + "\113\125\154\142\200\143\177\150\047\152\161\153\205" + + "\154\162\155\040\156\176\157\121\160\171\161\135\162" + + "\043\163\044\164\147\165\137\001\002\000\002\001\uff8c" + + "\000\006\051\u01c6\052\077\001\002\000\116\031\120\032" + + "\144\033\150\034\151\036\124\037\174\044\102\047\210" + + "\053\u01c8\072\220\073\116\077\165\100\125\102\215\103" + + "\204\104\153\105\127\112\152\113\212\114\045\115\134" + + "\124\113\125\154\142\200\143\177\150\047\152\161\153" + + "\205\154\162\155\040\156\176\157\121\160\171\161\135" + + "\162\043\163\044\164\147\165\137\001\002\000\002\001" + + "\uff91\000\120\031\120\032\144\033\150\034\151\036\124" + + "\037\174\044\102\047\210\053\223\072\220\073\116\077" + + "\165\100\125\101\225\102\215\103\204\104\153\105\127" + + "\112\152\113\212\114\045\115\134\124\113\125\154\142" + + "\200\143\177\150\047\152\161\153\205\154\162\155\040" + + "\156\176\157\121\160\171\161\135\162\043\163\044\164" + + "\147\165\137\001\002\000\002\001\uff8b\000\010\144\375" + + "\145\373\146\374\001\uff1a\000\002\001\uff2e\000\002\001" + + "\ufef3\000\002\001\ufef6\000\076\031\120\032\144\033\150" + + "\034\151\037\174\044\102\047\267\053\223\073\116\077" + + "\276\100\125\102\215\103\204\105\127\112\152\114\045" + + "\124\113\142\041\150\047\152\161\154\264\155\040\156" + + "\176\157\277\160\171\161\263\162\043\163\044\164\147" + + "\165\137\001\002\000\002\001\ufeec\000\010\045\243\052" + + "\u010f\100\242\001\002\000\002\001\uff5b\000\076\031\120" + + "\032\144\033\150\034\151\037\174\044\102\047\267\053" + + "\223\073\116\077\276\100\125\102\215\103\204\105\127" + + "\112\152\114\045\124\113\142\041\150\047\152\161\154" + + "\264\155\040\156\176\157\277\160\171\161\263\162\043" + + "\163\044\164\147\165\137\001\002\000\076\031\120\032" + + "\144\033\150\034\151\037\174\044\102\047\267\053\223" + + "\073\116\077\276\100\125\102\215\103\204\105\127\112" + + "\152\114\045\124\113\142\041\150\047\152\161\154\264" + + "\155\040\156\176\157\277\160\171\161\263\162\043\163" + + "\044\164\147\165\137\001\002\000\002\001\ufed4\000\002" + + "\001\ufed0\000\116\031\120\032\144\033\150\034\151\036" + + "\124\037\174\044\102\047\210\053\223\072\220\073\116" + + "\077\165\100\125\102\215\103\204\104\153\105\127\112" + + "\152\113\212\114\045\115\134\124\113\125\154\142\200" + + "\143\177\150\047\152\161\153\205\154\162\155\040\156" + + "\176\157\121\160\171\161\135\162\043\163\044\164\147" + + "\165\137\001\002\000\116\031\120\032\144\033\150\034" + + "\151\036\u0139\037\174\044\102\047\267\053\223\072\261" + + "\073\116\077\262\100\125\102\215\103\204\104\u0138\105" + + "\127\112\152\113\u0137\114\045\115\u0130\124\113\125\u0136" + + "\142\u012e\143\u013b\150\047\152\161\153\u012f\154\264\155" + + "\040\156\176\157\272\160\171\161\263\162\043\163\044" + + "\164\147\165\137\001\002\000\002\001\uff42\000\002\001" + + "\uff4c\000\002\001\uff4a\000\004\056\u01dc\001\uff49\000\116" + + "\031\120\032\144\033\150\034\151\036\124\037\174\044" + + "\102\047\210\053\223\072\220\073\116\077\165\100\125" + + "\102\215\103\204\104\153\105\127\112\152\113\212\114" + + "\045\115\134\124\113\125\154\142\200\143\177\150\047" + + "\152\161\153\205\154\162\155\040\156\176\157\121\160" + + "\171\161\135\162\043\163\044\164\147\165\137\001\002" + + "\000\002\001\uff4b\000\002\001\uff4e\000\002\001\uff4d\000" + + "\002\001\ufea7\000\002\001\uff81\000\120\031\120\032\144" + + "\033\150\034\151\036\124\037\174\044\102\047\210\053" + + "\223\072\220\073\116\077\165\100\125\101\225\102\215" + + "\103\204\104\153\105\127\112\152\113\212\114\045\115" + + "\134\124\113\125\154\142\200\143\177\150\047\152\161" + + "\153\205\154\162\155\040\156\176\157\121\160\171\161" + + "\135\162\043\163\044\164\147\165\137\001\002\000\002" + + "\001\uff7b\000\002\001\uff97\000\010\051\u01ad\052\077\060" + + "\u01e6\001\002\000\116\031\120\032\144\033\150\034\151" + + "\036\124\037\174\044\102\047\210\053\u01e8\072\220\073" + + "\116\077\165\100\125\102\215\103\204\104\153\105\127" + + "\112\152\113\212\114\045\115\134\124\113\125\154\142" + + "\200\143\177\150\047\152\161\153\205\154\162\155\040" + + "\156\176\157\121\160\171\161\135\162\043\163\044\164" + + "\147\165\137\001\002\000\002\001\uff82\000\120\031\120" + + "\032\144\033\150\034\151\036\124\037\174\044\102\047" + + "\210\053\223\072\220\073\116\077\165\100\125\101\225" + + "\102\215\103\204\104\153\105\127\112\152\113\212\114" + + "\045\115\134\124\113\125\154\142\200\143\177\150\047" + + "\152\161\153\205\154\162\155\040\156\176\157\121\160" + + "\171\161\135\162\043\163\044\164\147\165\137\001\002" + + "\000\002\001\uff7c\000\004\051\u01eb\001\002\000\116\031" + + "\120\032\144\033\150\034\151\036\124\037\174\044\102" + + "\047\210\053\223\072\220\073\116\077\165\100\125\102" + + "\215\103\204\104\153\105\127\112\152\113\212\114\045" + + "\115\134\124\113\125\154\142\200\143\177\150\047\152" + + "\161\153\205\154\162\155\040\156\176\157\121\160\171" + + "\161\135\162\043\163\044\164\147\165\137\001\002\000" + + "\004\100\242\001\002\000\002\001\uffbf\000\002\001\uff72" + + "\000\116\031\120\032\144\033\150\034\151\036\124\037" + + "\174\044\102\047\210\053\u01f1\072\220\073\116\077\165" + + "\100\125\102\215\103\204\104\153\105\127\112\152\113" + + "\212\114\045\115\134\124\113\125\154\142\200\143\177" + + "\150\047\152\161\153\205\154\162\155\040\156\176\157" + + "\121\160\171\161\135\162\043\163\044\164\147\165\137" + + "\001\002\000\002\001\uff80\000\120\031\120\032\144\033" + + "\150\034\151\036\124\037\174\044\102\047\210\053\223" + + "\072\220\073\116\077\165\100\125\101\225\102\215\103" + + "\204\104\153\105\127\112\152\113\212\114\045\115\134" + + "\124\113\125\154\142\200\143\177\150\047\152\161\153" + + "\205\154\162\155\040\156\176\157\121\160\171\161\135" + + "\162\043\163\044\164\147\165\137\001\002\000\002\001" + + "\uff7a\000\022\052\072\053\063\062\065\130\064\134\074" + + "\142\053\160\046\163\044\001\uff75\000\002\001\uff73\000" + + "\010\051\u01c6\052\077\060\u01f6\001\002\000\116\031\120" + + "\032\144\033\150\034\151\036\124\037\174\044\102\047" + + "\210\053\u01f8\072\220\073\116\077\165\100\125\102\215" + + "\103\204\104\153\105\127\112\152\113\212\114\045\115" + + "\134\124\113\125\154\142\200\143\177\150\047\152\161" + + "\153\205\154\162\155\040\156\176\157\121\160\171\161" + + "\135\162\043\163\044\164\147\165\137\001\002\000\002" + + "\001\uff84\000\120\031\120\032\144\033\150\034\151\036" + + "\124\037\174\044\102\047\210\053\223\072\220\073\116" + + "\077\165\100\125\101\225\102\215\103\204\104\153\105" + + "\127\112\152\113\212\114\045\115\134\124\113\125\154" + + "\142\200\143\177\150\047\152\161\153\205\154\162\155" + + "\040\156\176\157\121\160\171\161\135\162\043\163\044" + + "\164\147\165\137\001\002\000\002\001\uff7e\000\010\051" + + "\u01c1\052\077\060\u01fb\001\002\000\116\031\120\032\144" + + "\033\150\034\151\036\124\037\174\044\102\047\210\053" + + "\u01fd\072\220\073\116\077\165\100\125\102\215\103\204" + + "\104\153\105\127\112\152\113\212\114\045\115\134\124" + + "\113\125\154\142\200\143\177\150\047\152\161\153\205" + + "\154\162\155\040\156\176\157\121\160\171\161\135\162" + + "\043\163\044\164\147\165\137\001\002\000\002\001\uff85" + + "\000\120\031\120\032\144\033\150\034\151\036\124\037" + + "\174\044\102\047\210\053\223\072\220\073\116\077\165" + + "\100\125\101\225\102\215\103\204\104\153\105\127\112" + + "\152\113\212\114\045\115\134\124\113\125\154\142\200" + + "\143\177\150\047\152\161\153\205\154\162\155\040\156" + + "\176\157\121\160\171\161\135\162\043\163\044\164\147" + + "\165\137\001\002\000\002\001\uff7f\000\010\051\u01b2\052" + + "\077\060\u0200\001\002\000\116\031\120\032\144\033\150" + + "\034\151\036\124\037\174\044\102\047\210\053\u0202\072" + + "\220\073\116\077\165\100\125\102\215\103\204\104\153" + + "\105\127\112\152\113\212\114\045\115\134\124\113\125" + + "\154\142\200\143\177\150\047\152\161\153\205\154\162" + + "\155\040\156\176\157\121\160\171\161\135\162\043\163" + + "\044\164\147\165\137\001\002\000\002\001\uff83\000\120" + + "\031\120\032\144\033\150\034\151\036\124\037\174\044" + + "\102\047\210\053\223\072\220\073\116\077\165\100\125" + + "\101\225\102\215\103\204\104\153\105\127\112\152\113" + + "\212\114\045\115\134\124\113\125\154\142\200\143\177" + + "\150\047\152\161\153\205\154\162\155\040\156\176\157" + + "\121\160\171\161\135\162\043\163\044\164\147\165\137" + + "\001\002\000\002\001\uff7d\000\004\140\u0205\001\002\000" + + "\004\051\u0206\001\002\000\116\031\120\032\144\033\150" + + "\034\151\036\124\037\174\044\102\047\210\053\223\072" + + "\220\073\116\077\165\100\125\102\215\103\204\104\153" + + "\105\127\112\152\113\212\114\045\115\134\124\113\125" + + "\154\142\200\143\177\150\047\152\161\153\205\154\162" + + "\155\040\156\176\157\121\160\171\161\135\162\043\163" + + "\044\164\147\165\137\001\002\000\004\100\242\001\002" + + "\000\002\001\uffc0\000\004\100\242\001\002\000\002\001" + + "\uffbe\000\116\031\120\032\144\033\150\034\151\036\124" + + "\037\174\044\102\047\210\053\223\072\220\073\116\077" + + "\165\100\125\102\215\103\204\104\153\105\127\112\152" + + "\113\212\114\045\115\134\124\113\125\154\142\200\143" + + "\177\150\047\152\161\153\205\154\162\155\040\156\176" + + "\157\121\160\171\161\135\162\043\163\044\164\147\165" + + "\137\001\002\000\022\052\072\053\063\062\065\130\064" + + "\134\074\142\053\160\046\163\044\001\uff75\000\022\052" + + "\072\053\063\062\065\130\064\134\074\142\053\160\046" + + "\163\044\001\uff75\000\004\137\u020f\001\002\000\004\051" + + "\u0210\001\002\000\116\031\120\032\144\033\150\034\151" + + "\036\124\037\174\044\102\047\210\053\223\072\220\073" + + "\116\077\165\100\125\102\215\103\204\104\153\105\127" + + "\112\152\113\212\114\045\115\134\124\113\125\154\142" + + "\200\143\177\150\047\152\161\153\205\154\162\155\040" + + "\156\176\157\121\160\171\161\135\162\043\163\044\164" + + "\147\165\137\001\002\000\004\100\242\001\002\000\002" + + "\001\uffc2\000\004\140\u0214\001\002\000\004\051\u0215\001" + + "\002\000\116\031\120\032\144\033\150\034\151\036\124" + + "\037\174\044\102\047\210\053\223\072\220\073\116\077" + + "\165\100\125\102\215\103\204\104\153\105\127\112\152" + + "\113\212\114\045\115\134\124\113\125\154\142\200\143" + + "\177\150\047\152\161\153\205\154\162\155\040\156\176" + + "\157\121\160\171\161\135\162\043\163\044\164\147\165" + + "\137\001\002\000\004\100\242\001\002\000\002\001\uffc3" + + "\000\004\100\242\001\002\000\002\001\uffc1\000\006\042" + + "\u021b\101\u021c\001\ufffd\000\010\142\053\160\046\163\044" + + "\001\002\000\020\114\045\142\041\150\047\155\040\160" + + "\046\162\043\163\044\001\uffa3\000\004\137\u0222\001\002" + + "\000\004\052\u0220\001\uffa2\000\002\001\uffa1\000\020\114" + + "\045\142\041\150\047\155\040\160\046\162\043\163\044" + + "\001\002\000\002\001\uffa0\000\004\042\u0223\001\ufffb\000" + + "\010\142\053\160\046\163\044\001\002\000\002\001\ufffa" + + "\000\002\001\ufffc\000\010\100\242\101\u0235\105\u0234\001" + + "\uffa9\000\004\055\u0228\001\002\000\010\142\053\160\046" + + "\163\044\001\002\000\010\100\242\101\u022c\105\u022b\001" + + "\002\000\002\001\uffb8\000\022\052\072\053\063\062\065" + + "\130\064\134\074\142\053\160\046\163\044\001\uff75\000" + + "\022\052\072\053\063\062\065\130\064\134\074\142\053" + + "\160\046\163\044\001\uff75\000\004\137\u022e\001\002\000" + + "\004\100\242\001\002\000\002\001\uffb9\000\004\140\u0231" + + "\001\002\000\004\100\242\001\002\000\002\001\uffba\000" + + "\002\001\uffbb\000\022\052\072\053\063\062\065\130\064" + + "\134\074\142\053\160\046\163\044\001\uff75\000\022\052" + + "\072\053\063\062\065\130\064\134\074\142\053\160\046" + + "\163\044\001\uff75\000\004\137\u0237\001\002\000\004\100" + + "\242\001\002\000\002\001\uffbc\000\004\140\u023a\001\002" + + "\000\004\100\242\001\002\000\002\001\uffbd\000\012\060" + + "\u023d\100\242\101\u0241\105\u0240\001\002\000\116\031\120" + + "\032\144\033\150\034\151\036\124\037\174\044\102\047" + + "\210\053\223\072\220\073\116\077\165\100\125\102\215" + + "\103\204\104\153\105\127\112\152\113\212\114\045\115" + + "\134\124\113\125\154\142\200\143\177\150\047\152\161" + + "\153\205\154\162\155\040\156\176\157\121\160\171\161" + + "\135\162\043\163\044\164\147\165\137\001\002\000\002" + + "\001\uffc6\000\002\001\uffc5\000\012\140\u0247\142\053\160" + + "\046\163\044\001\002\000\012\137\u0243\142\053\160\046" + + "\163\044\001\002\000\006\052\077\137\u0245\001\002\000" + + "\006\060\u023d\100\242\001\002\000\002\001\uffc7\000\006" + + "\060\u023d\100\242\001\002\000\002\001\uffc8\000\006\060" + + "\u023d\100\242\001\002\000\006\052\077\140\u0249\001\002" + + "\000\006\060\u023d\100\242\001\002\000\002\001\uffca\000" + + "\002\001\uffc9\000\002\001\uffc4\000\004\100\u024e\001\002" + + "\000\012\136\u0250\142\053\160\046\163\044\001\002\000" + + "\006\052\077\136\u0251\001\002\000\002\001\ufff8\000\002" + + "\001\ufff9\000\006\050\017\141\024\001\002\000\012\100" + + "\242\142\053\160\046\163\044\001\002\000\014\066\u0257" + + "\100\242\142\053\160\046\163\044\001\uffdc\000\004\066" + + "\u0257\001\uffdc\000\004\063\u0279\001\uffe1\000\006\061\u0258" + + "\164\u025b\001\002\000\004\164\u0270\001\002\000\022\077" + + "\u0269\114\u026e\142\u026c\150\u026f\155\u026b\160\046\162\u026d" + + "\163\044\001\002\000\004\052\u0266\001\uffdd\000\030\046" + + "\u025e\051\u025d\055\u025f\077\uffce\114\uffce\142\uffce\150\uffce" + + "\155\uffce\160\uffce\162\uffce\163\uffce\001\uffdf\000\002\001" + + "\uffdb\000\004\164\u0268\001\002\000\006\061\u0258\164\u0265" + + "\001\002\000\004\055\u0260\001\002\000\004\164\u0261\001" + + "\002\000\004\051\u0262\001\uffcd\000\004\164\u0263\001\002" + + "\000\002\001\uffcc\000\004\052\u0266\001\uffde\000\006\051" + + "\u025d\055\u025f\001\uffce\000\006\061\u0258\164\u0265\001\002" + + "\000\002\001\uffda\000\002\001\uffcb\000\002\001\uffd7\000" + + "\002\001\uffd9\000\002\001\uffd4\000\004\151\054\001\uffd6" + + "\000\002\001\uffd5\000\002\001\uffd3\000\002\001\uffd8\000" + + "\006\051\u0271\055\u0272\001\uffd2\000\004\164\u0277\001\002" + + "\000\004\055\u0273\001\002\000\004\164\u0274\001\002\000" + + "\004\051\u0275\001\uffd1\000\004\164\u0276\001\002\000\002" + + "\001\uffd0\000\002\001\uffcf\000\002\001\uffe6\000\004\164" + + "\u027a\001\002\000\002\001\uffe0\000\004\066\u0257\001\uffdc" + + "\000\004\066\u0257\001\uffdc\000\004\063\u0279\001\uffe1\000" + + "\002\001\uffe2\000\004\063\u0279\001\uffe1\000\002\001\uffe7" + + "\000\004\063\u0279\001\uffe1\000\002\001\uffe3\000\012\052" + + "\077\060\u0286\064\u0285\076\u0284\001\uffaa\000\020\114\045" + + "\142\041\150\047\155\040\160\046\162\043\163\044\001" + + "\002\000\020\114\045\142\041\150\047\155\040\160\046" + + "\162\043\163\044\001\002\000\020\114\045\142\041\150" + + "\047\155\040\160\046\162\043\163\044\001\002\000\004" + + "\100\u0288\001\002\000\022\052\072\053\063\062\065\130" + + "\064\134\074\142\053\160\046\163\044\001\uff75\000\004" + + "\136\u028a\001\002\000\004\100\242\001\uff6f\000\002\001" + + "\uffb5\000\002\001\uff6e\000\004\131\u028f\001\uffab\000\002" + + "\001\uff9f\000\020\114\045\142\041\150\047\155\040\160" + + "\046\162\043\163\044\001\002\000\002\001\uff9e\000\002" + + "\001\uffad\000\004\131\u028f\001\uffac\000\002\001\uffae\000" + + "\002\001\uffb0\000\004\101\u0296\001\uffff\000\012\061\u0298" + + "\142\053\160\046\163\044\001\002\000\002\001\uff96\000" + + "\010\142\053\160\046\163\044\001\002\000\006\052\u029a" + + "\137\u029b\001\002\000\012\061\u029d\142\053\160\046\163" + + "\044\001\002\000\002\001\ufffe\000\002\001\uff94\000\010" + + "\142\053\160\046\163\044\001\002\000\002\001\uff93\000" + + "\002\001\uff95\000\004\100\242\001\002\000\004\100\242" + + "\001\002\000\002\001\ufff7\000\002\001\ufff5\000\002\001" + + "\ufff6\000\014\066\u0257\100\242\142\053\160\046\163\044" + + "\001\uffdc\000\004\066\u0257\001\uffdc\000\004\063\u0279\001" + + "\uffe1\000\002\001\uffe8\000\004\066\u0257\001\uffdc\000\004" + + "\066\u0257\001\uffdc\000\004\063\u0279\001\uffe1\000\002\001" + + "\uffe4\000\004\063\u0279\001\uffe1\000\002\001\uffe9\000\004" + + "\063\u0279\001\uffe1\000\002\001\uffe5\000\004\100\242\001" + + "\002\000\004\100\242\001\002\000\002\001\ufff4\000\002" + + "\001\ufff2\000\002\001\ufff3\000\002\001\000" }); + + /** Access to parse-action table. */ + public short[][] action_table() {return _action_table;} + + /** reduce_goto table. */ + protected static final short[][] _reduce_table = + unpackFromStrings(new String[] { + "\000\u02b4\000\006\106\003\107\004\001\001\000\002\001" + + "\001\000\024\012\024\013\013\037\014\051\010\067\006" + + "\076\017\100\021\101\022\123\030\001\001\000\002\001" + + "\001\000\002\001\001\000\010\031\u02b2\053\u02b0\054\041" + + "\001\001\000\002\001\001\000\002\001\001\000\002\001" + + "\001\000\010\031\u02a5\053\u02a4\054\041\001\001\000\002" + + "\001\001\000\010\031\u02a1\053\u029f\054\041\001\001\000" + + "\002\001\001\000\002\001\001\000\006\053\u0294\054\041" + + "\001\001\000\006\100\021\101\u0292\001\001\000\010\053" + + "\066\054\041\055\u0282\001\001\000\002\001\001\000\002" + + "\001\001\000\002\001\001\000\002\001\001\000\002\001" + + "\001\000\002\001\001\000\006\053\u024c\054\041\001\001" + + "\000\006\053\u023b\054\041\001\001\000\010\053\u0225\054" + + "\041\102\u0226\001\001\000\006\053\u0219\054\041\001\001" + + "\000\010\053\036\054\041\102\047\001\001\000\002\001" + + "\001\000\002\001\001\000\002\001\001\000\002\001\001" + + "\000\002\001\001\000\002\001\001\000\002\001\001\000" + + "\002\001\001\000\002\001\001\000\002\001\001\000\006" + + "\053\051\054\041\001\001\000\002\001\001\000\002\001" + + "\001\000\004\054\054\001\001\000\002\001\001\000\002" + + "\001\001\000\002\001\001\000\140\002\106\003\105\004" + + "\165\005\104\006\132\007\131\010\142\011\141\021\110" + + "\022\107\023\202\024\201\025\155\026\154\030\u0208\031" + + "\210\040\103\041\102\042\206\043\205\044\216\045\215" + + "\046\174\047\172\053\200\054\041\057\122\060\121\061" + + "\113\062\111\063\145\064\144\065\116\066\114\070\130" + + "\071\127\073\157\074\156\102\135\113\140\114\137\115" + + "\171\116\167\117\213\120\212\121\163\122\162\001\001" + + "\000\016\014\070\015\065\017\u0203\053\066\054\041\055" + + "\067\001\001\000\016\014\070\015\065\017\072\053\066" + + "\054\041\055\067\001\001\000\010\053\066\054\041\055" + + "\u01fe\001\001\000\010\053\066\054\041\055\u01f9\001\001" + + "\000\010\053\066\054\041\055\u01f4\001\001\000\002\001" + + "\001\000\002\001\001\000\002\001\001\000\002\001\001" + + "\000\016\014\070\015\065\017\u01ed\053\066\054\041\055" + + "\067\001\001\000\002\001\001\000\010\053\066\054\041" + + "\055\075\001\001\000\010\053\066\054\041\055\u01e4\001" + + "\001\000\002\001\001\000\006\053\u01e3\054\041\001\001" + + "\000\140\002\106\003\105\004\165\005\104\006\132\007" + + "\131\010\142\011\141\021\110\022\107\023\202\024\201" + + "\025\155\026\154\030\u01e0\031\210\040\103\041\102\042" + + "\206\043\205\044\216\045\215\046\174\047\172\053\200" + + "\054\041\057\122\060\121\061\113\062\111\063\145\064" + + "\144\065\116\066\114\070\130\071\127\073\157\074\156" + + "\102\135\113\140\114\137\115\171\116\167\117\213\120" + + "\212\121\163\122\162\001\001\000\140\002\106\003\105" + + "\004\165\005\104\006\132\007\131\010\142\011\141\021" + + "\110\022\107\023\202\024\201\025\155\026\154\030\125" + + "\031\210\040\103\041\102\042\206\043\205\044\216\045" + + "\215\046\174\047\172\053\200\054\041\057\122\060\121" + + "\061\113\062\111\063\145\064\144\065\116\066\114\070" + + "\130\071\127\073\157\074\156\102\135\113\140\114\137" + + "\115\171\116\167\117\213\120\212\121\163\122\162\001" + + "\001\000\006\053\u01df\054\041\001\001\000\002\001\001" + + "\000\002\001\001\000\002\001\001\000\002\001\001\000" + + "\002\001\001\000\004\072\u01d2\001\001\000\002\001\001" + + "\000\002\001\001\000\016\014\070\015\u010c\016\u01cf\053" + + "\066\054\041\055\067\001\001\000\002\001\001\000\002" + + "\001\001\000\002\001\001\000\002\001\001\000\002\001" + + "\001\000\062\004\165\005\u01cc\006\132\007\131\021\110" + + "\022\107\023\202\024\201\031\210\046\174\047\172\053" + + "\200\054\041\061\u01cb\062\317\065\116\066\114\070\130" + + "\071\127\073\157\074\156\102\135\121\163\122\162\001" + + "\001\000\002\001\001\000\002\001\001\000\074\004\165" + + "\006\132\007\131\021\110\022\107\023\202\024\201\031" + + "\210\046\174\047\172\053\200\054\041\061\113\062\111" + + "\065\116\066\114\070\130\071\127\073\157\074\156\102" + + "\135\113\u01ca\114\u01c9\115\171\116\167\117\213\120\212" + + "\121\163\122\162\001\001\000\150\002\106\003\105\004" + + "\165\005\104\006\132\007\131\010\142\011\141\014\u01a6" + + "\020\u01a4\021\110\022\107\023\202\024\201\025\155\026" + + "\154\030\246\031\210\033\247\040\103\041\102\042\206" + + "\043\205\044\216\045\215\046\174\047\172\053\u01a7\054" + + "\041\055\u01a3\057\122\060\121\061\113\062\111\063\145" + + "\064\144\065\116\066\114\070\130\071\127\073\157\074" + + "\156\102\135\113\140\114\137\115\171\116\167\117\213" + + "\120\212\121\163\122\162\001\001\000\002\001\001\000" + + "\140\002\106\003\105\004\165\005\104\006\132\007\131" + + "\010\142\011\141\021\110\022\107\023\202\024\201\025" + + "\155\026\154\030\u019f\031\210\040\103\041\102\042\206" + + "\043\205\044\216\045\215\046\174\047\172\053\200\054" + + "\041\057\122\060\121\061\113\062\111\063\145\064\144" + + "\065\116\066\114\070\130\071\127\073\157\074\156\102" + + "\135\113\140\114\137\115\171\116\167\117\213\120\212" + + "\121\163\122\162\001\001\000\002\001\001\000\002\001" + + "\001\000\002\001\001\000\002\001\001\000\106\004\165" + + "\005\u0199\006\132\007\131\010\142\011\141\021\110\022" + + "\107\023\202\024\201\031\210\046\174\047\172\053\200" + + "\054\041\057\u019a\060\u0163\061\113\062\111\065\116\066" + + "\114\070\130\071\127\073\157\074\156\102\135\113\140" + + "\114\137\115\171\116\167\117\213\120\212\121\163\122" + + "\162\001\001\000\022\004\165\005\u0197\031\210\053\200" + + "\054\041\102\135\121\u0198\122\360\001\001\000\002\001" + + "\001\000\002\001\001\000\002\001\001\000\002\001\001" + + "\000\002\001\001\000\002\001\001\000\002\001\001\000" + + "\002\001\001\000\002\001\001\000\002\001\001\000\002" + + "\001\001\000\002\001\001\000\016\014\070\015\u010c\016" + + "\u016a\053\066\054\041\055\067\001\001\000\074\004\165" + + "\006\132\007\131\021\110\022\107\023\202\024\201\031" + + "\210\046\174\047\172\053\200\054\041\061\113\062\111" + + "\065\116\066\114\070\130\071\127\073\157\074\156\102" + + "\135\113\u0169\114\u0168\115\171\116\167\117\213\120\212" + + "\121\163\122\162\001\001\000\074\004\165\006\132\007" + + "\131\021\110\022\107\023\202\024\201\031\210\046\174" + + "\047\172\053\200\054\041\061\113\062\111\065\116\066" + + "\114\070\130\071\127\073\157\074\156\102\135\113\u0167" + + "\114\u0166\115\171\116\167\117\213\120\212\121\163\122" + + "\162\001\001\000\002\001\001\000\002\001\001\000\002" + + "\001\001\000\002\001\001\000\016\014\070\015\u010c\016" + + "\u012a\053\066\054\041\055\067\001\001\000\022\004\165" + + "\005\u0128\031\210\053\200\054\041\102\135\121\u0129\122" + + "\357\001\001\000\002\001\001\000\002\001\001\000\062" + + "\004\165\005\u0127\006\132\007\131\021\110\022\107\023" + + "\202\024\201\031\210\046\174\047\172\053\200\054\041" + + "\061\u0126\062\361\065\116\066\114\070\130\071\127\073" + + "\157\074\156\102\135\121\163\122\162\001\001\000\002" + + "\001\001\000\140\002\106\003\105\004\165\005\104\006" + + "\132\007\131\010\142\011\141\021\110\022\107\023\202" + + "\024\201\025\155\026\154\030\u0125\031\210\040\103\041" + + "\102\042\206\043\205\044\216\045\215\046\174\047\172" + + "\053\200\054\041\057\122\060\121\061\113\062\111\063" + + "\145\064\144\065\116\066\114\070\130\071\127\073\157" + + "\074\156\102\135\113\140\114\137\115\171\116\167\117" + + "\213\120\212\121\163\122\162\001\001\000\002\001\001" + + "\000\002\001\001\000\002\001\001\000\002\001\001\000" + + "\016\014\070\015\u010c\016\u0119\053\066\054\041\055\067" + + "\001\001\000\002\001\001\000\016\014\070\015\u010c\016" + + "\u0117\053\066\054\041\055\067\001\001\000\074\004\165" + + "\006\132\007\131\021\110\022\107\023\202\024\201\031" + + "\210\046\174\047\172\053\200\054\041\061\113\062\111" + + "\065\116\066\114\070\130\071\127\073\157\074\156\102" + + "\135\113\u0116\114\u0115\115\171\116\167\117\213\120\212" + + "\121\163\122\162\001\001\000\074\004\165\006\132\007" + + "\131\021\110\022\107\023\202\024\201\031\210\046\174" + + "\047\172\053\200\054\041\061\113\062\111\065\116\066" + + "\114\070\130\071\127\073\157\074\156\102\135\113\u0113" + + "\114\u0112\115\171\116\167\117\213\120\212\121\163\122" + + "\162\001\001\000\002\001\001\000\002\001\001\000\002" + + "\001\001\000\016\014\070\015\u010c\016\u010d\053\066\054" + + "\041\055\067\001\001\000\074\004\165\006\132\007\131" + + "\021\110\022\107\023\202\024\201\031\210\046\174\047" + + "\172\053\200\054\041\061\113\062\111\065\116\066\114" + + "\070\130\071\127\073\157\074\156\102\135\113\u010b\114" + + "\u010a\115\171\116\167\117\213\120\212\121\163\122\162" + + "\001\001\000\002\001\001\000\002\001\001\000\022\004" + + "\165\005\u0108\031\210\053\200\054\041\102\135\121\u0109" + + "\122\326\001\001\000\002\001\001\000\074\004\165\006" + + "\132\007\131\021\110\022\107\023\202\024\201\031\210" + + "\046\174\047\172\053\200\054\041\061\113\062\111\065" + + "\116\066\114\070\130\071\127\073\157\074\156\102\135" + + "\113\371\114\370\115\171\116\167\117\213\120\212\121" + + "\163\122\162\001\001\000\002\001\001\000\002\001\001" + + "\000\010\050\234\053\233\054\041\001\001\000\002\001" + + "\001\000\002\001\001\000\062\004\165\005\223\006\132" + + "\007\131\021\110\022\107\023\202\024\201\031\210\046" + + "\174\047\172\053\200\054\041\061\221\062\220\065\116" + + "\066\114\070\130\071\127\073\157\074\156\102\135\121" + + "\163\122\162\001\001\000\002\001\001\000\002\001\001" + + "\000\002\001\001\000\002\001\001\000\144\002\106\003" + + "\105\004\165\005\104\006\132\007\131\010\142\011\141" + + "\021\110\022\107\023\202\024\201\025\155\026\154\030" + + "\225\031\210\035\226\036\227\040\103\041\102\042\206" + + "\043\205\044\216\045\215\046\174\047\172\053\200\054" + + "\041\057\122\060\121\061\113\062\111\063\145\064\144" + + "\065\116\066\114\070\130\071\127\073\157\074\156\102" + + "\135\113\140\114\137\115\171\116\167\117\213\120\212" + + "\121\163\122\162\001\001\000\002\001\001\000\002\001" + + "\001\000\002\001\001\000\140\002\106\003\105\004\165" + + "\005\104\006\132\007\131\010\142\011\141\021\110\022" + + "\107\023\202\024\201\025\155\026\154\030\231\031\210" + + "\040\103\041\102\042\206\043\205\044\216\045\215\046" + + "\174\047\172\053\200\054\041\057\122\060\121\061\113" + + "\062\111\063\145\064\144\065\116\066\114\070\130\071" + + "\127\073\157\074\156\102\135\113\140\114\137\115\171" + + "\116\167\117\213\120\212\121\163\122\162\001\001\000" + + "\002\001\001\000\002\001\001\000\002\001\001\000\002" + + "\001\001\000\140\002\106\003\105\004\165\005\104\006" + + "\132\007\131\010\142\011\141\021\110\022\107\023\202" + + "\024\201\025\155\026\154\030\236\031\210\040\103\041" + + "\102\042\206\043\205\044\216\045\215\046\174\047\172" + + "\053\200\054\041\057\122\060\121\061\113\062\111\063" + + "\145\064\144\065\116\066\114\070\130\071\127\073\157" + + "\074\156\102\135\113\140\114\137\115\171\116\167\117" + + "\213\120\212\121\163\122\162\001\001\000\006\031\243" + + "\034\240\001\001\000\010\050\252\053\233\054\041\001" + + "\001\000\002\001\001\000\142\002\106\003\105\004\165" + + "\005\104\006\132\007\131\010\142\011\141\021\110\022" + + "\107\023\202\024\201\025\155\026\154\030\246\031\210" + + "\033\247\040\103\041\102\042\206\043\205\044\216\045" + + "\215\046\174\047\172\053\200\054\041\057\122\060\121" + + "\061\113\062\111\063\145\064\144\065\116\066\114\070" + + "\130\071\127\073\157\074\156\102\135\113\140\114\137" + + "\115\171\116\167\117\213\120\212\121\163\122\162\001" + + "\001\000\140\002\106\003\105\004\165\005\104\006\132" + + "\007\131\010\142\011\141\021\110\022\107\023\202\024" + + "\201\025\155\026\154\030\244\031\210\040\103\041\102" + + "\042\206\043\205\044\216\045\215\046\174\047\172\053" + + "\200\054\041\057\122\060\121\061\113\062\111\063\145" + + "\064\144\065\116\066\114\070\130\071\127\073\157\074" + + "\156\102\135\113\140\114\137\115\171\116\167\117\213" + + "\120\212\121\163\122\162\001\001\000\002\001\001\000" + + "\002\001\001\000\002\001\001\000\002\001\001\000\140" + + "\002\106\003\105\004\165\005\104\006\132\007\131\010" + + "\142\011\141\021\110\022\107\023\202\024\201\025\155" + + "\026\154\030\251\031\210\040\103\041\102\042\206\043" + + "\205\044\216\045\215\046\174\047\172\053\200\054\041" + + "\057\122\060\121\061\113\062\111\063\145\064\144\065" + + "\116\066\114\070\130\071\127\073\157\074\156\102\135" + + "\113\140\114\137\115\171\116\167\117\213\120\212\121" + + "\163\122\162\001\001\000\002\001\001\000\002\001\001" + + "\000\002\001\001\000\042\004\165\005\367\007\270\022" + + "\264\024\201\031\210\047\257\053\200\054\041\062\111" + + "\066\265\071\272\074\156\102\135\116\366\122\162\001" + + "\001\000\042\004\165\005\365\007\270\022\264\024\201" + + "\031\210\047\257\053\200\054\041\062\111\066\265\071" + + "\272\074\156\102\135\116\364\122\162\001\001\000\042" + + "\004\165\005\363\007\270\022\264\024\201\031\210\047" + + "\257\053\200\054\041\062\111\066\265\071\272\074\156" + + "\102\135\116\362\122\162\001\001\000\042\004\165\005" + + "\273\007\270\022\264\024\201\031\210\047\257\053\200" + + "\054\041\062\111\066\265\071\272\074\156\102\135\116" + + "\267\122\162\001\001\000\002\001\001\000\036\004\165" + + "\007\270\022\264\024\201\031\210\047\257\053\200\054" + + "\041\062\220\066\265\071\272\074\156\102\135\122\162" + + "\001\001\000\036\004\165\007\270\022\264\024\201\031" + + "\210\047\257\053\200\054\041\062\361\066\265\071\272" + + "\074\156\102\135\122\162\001\001\000\016\004\165\031" + + "\210\053\200\054\041\102\135\122\360\001\001\000\016" + + "\004\165\031\210\053\200\054\041\102\135\122\357\001" + + "\001\000\004\072\351\001\001\000\002\001\001\000\016" + + "\004\165\031\210\053\200\054\041\102\135\122\326\001" + + "\001\000\002\001\001\000\002\001\001\000\036\004\165" + + "\007\270\022\264\024\201\031\210\047\257\053\200\054" + + "\041\062\317\066\265\071\272\074\156\102\135\122\162" + + "\001\001\000\002\001\001\000\002\001\001\000\022\004" + + "\165\007\277\024\201\031\210\053\200\054\041\102\135" + + "\122\162\001\001\000\002\001\001\000\002\001\001\000" + + "\002\001\001\000\144\002\106\003\105\004\165\005\104" + + "\006\132\007\131\010\142\011\141\021\110\022\107\023" + + "\202\024\201\025\155\026\154\030\225\031\210\035\307" + + "\036\227\040\103\041\102\042\206\043\205\044\216\045" + + "\215\046\174\047\172\053\200\054\041\057\122\060\121" + + "\061\113\062\111\063\145\064\144\065\116\066\114\070" + + "\130\071\127\073\157\074\156\102\135\113\140\114\137" + + "\115\171\116\167\117\213\120\212\121\163\122\162\001" + + "\001\000\016\004\165\031\210\053\200\054\041\102\135" + + "\122\306\001\001\000\002\001\001\000\002\001\001\000" + + "\002\001\001\000\002\001\001\000\002\001\001\000\002" + + "\001\001\000\002\001\001\000\144\002\106\003\105\004" + + "\165\005\104\006\132\007\131\010\142\011\141\021\110" + + "\022\107\023\202\024\201\025\155\026\154\030\225\031" + + "\210\035\312\036\227\040\103\041\102\042\206\043\205" + + "\044\216\045\215\046\174\047\172\053\200\054\041\057" + + "\122\060\121\061\113\062\111\063\145\064\144\065\116" + + "\066\114\070\130\071\127\073\157\074\156\102\135\113" + + "\140\114\137\115\171\116\167\117\213\120\212\121\163" + + "\122\162\001\001\000\002\001\001\000\002\001\001\000" + + "\144\002\106\003\105\004\165\005\104\006\132\007\131" + + "\010\142\011\141\021\110\022\107\023\202\024\201\025" + + "\155\026\154\030\225\031\210\035\315\036\227\040\103" + + "\041\102\042\206\043\205\044\216\045\215\046\174\047" + + "\172\053\200\054\041\057\122\060\121\061\113\062\111" + + "\063\145\064\144\065\116\066\114\070\130\071\127\073" + + "\157\074\156\102\135\113\140\114\137\115\171\116\167" + + "\117\213\120\212\121\163\122\162\001\001\000\002\001" + + "\001\000\002\001\001\000\002\001\001\000\036\004\165" + + "\007\270\022\264\024\201\031\210\047\257\053\200\054" + + "\041\062\325\066\265\071\272\074\156\102\135\122\162" + + "\001\001\000\036\004\165\007\270\022\264\024\201\031" + + "\210\047\257\053\200\054\041\062\324\066\265\071\272" + + "\074\156\102\135\122\162\001\001\000\036\004\165\007" + + "\270\022\264\024\201\031\210\047\257\053\200\054\041" + + "\062\323\066\265\071\272\074\156\102\135\122\162\001" + + "\001\000\002\001\001\000\002\001\001\000\002\001\001" + + "\000\002\001\001\000\032\004\165\007\270\022\264\024" + + "\201\031\210\047\330\053\200\054\041\071\272\074\156" + + "\102\135\122\162\001\001\000\002\001\001\000\030\004" + + "\165\007\270\022\264\024\201\031\210\053\200\054\041" + + "\071\272\074\332\102\135\122\162\001\001\000\002\001" + + "\001\000\024\004\165\007\270\024\201\031\210\053\200" + + "\054\041\071\356\102\135\122\162\001\001\000\002\001" + + "\001\000\002\001\001\000\002\001\001\000\002\001\001" + + "\000\002\001\001\000\002\001\001\000\002\001\001\000" + + "\002\001\001\000\002\001\001\000\002\001\001\000\002" + + "\001\001\000\002\001\001\000\002\001\001\000\030\004" + + "\165\007\270\022\264\024\201\031\210\053\200\054\041" + + "\071\272\074\355\102\135\122\162\001\001\000\002\001" + + "\001\000\002\001\001\000\002\001\001\000\002\001\001" + + "\000\002\001\001\000\002\001\001\000\002\001\001\000" + + "\002\001\001\000\002\001\001\000\002\001\001\000\002" + + "\001\001\000\002\001\001\000\002\001\001\000\002\001" + + "\001\000\002\001\001\000\002\001\001\000\044\004\165" + + "\005\u0107\007\270\022\264\024\201\031\210\047\257\053" + + "\200\054\041\062\111\066\265\071\272\074\156\102\135" + + "\116\375\120\u0106\122\162\001\001\000\044\004\165\005" + + "\u0105\007\270\022\264\024\201\031\210\047\257\053\200" + + "\054\041\062\111\066\265\071\272\074\156\102\135\116" + + "\375\120\u0104\122\162\001\001\000\044\004\165\005\377" + + "\007\270\022\264\024\201\031\210\047\257\053\200\054" + + "\041\062\111\066\265\071\272\074\156\102\135\116\375" + + "\120\376\122\162\001\001\000\002\001\001\000\002\001" + + "\001\000\002\001\001\000\040\004\165\007\270\022\264" + + "\024\201\031\210\047\257\053\200\054\041\062\111\066" + + "\265\071\272\074\156\102\135\116\366\122\162\001\001" + + "\000\040\004\165\007\270\022\264\024\201\031\210\047" + + "\257\053\200\054\041\062\111\066\265\071\272\074\156" + + "\102\135\116\364\122\162\001\001\000\040\004\165\007" + + "\270\022\264\024\201\031\210\047\257\053\200\054\041" + + "\062\111\066\265\071\272\074\156\102\135\116\362\122" + + "\162\001\001\000\040\004\165\007\270\022\264\024\201" + + "\031\210\047\257\053\200\054\041\062\111\066\265\071" + + "\272\074\156\102\135\116\267\122\162\001\001\000\002" + + "\001\001\000\002\001\001\000\002\001\001\000\002\001" + + "\001\000\002\001\001\000\002\001\001\000\002\001\001" + + "\000\002\001\001\000\002\001\001\000\006\031\243\034" + + "\u010f\001\001\000\014\014\070\015\u0110\053\066\054\041" + + "\055\067\001\001\000\002\001\001\000\002\001\001\000" + + "\004\054\054\001\001\000\002\001\001\000\002\001\001" + + "\000\002\001\001\000\002\001\001\000\002\001\001\000" + + "\006\031\243\034\u0118\001\001\000\002\001\001\000\006" + + "\031\243\034\u011a\001\001\000\002\001\001\000\032\004" + + "\165\005\u011c\007\270\022\264\024\201\031\210\053\200" + + "\054\041\071\272\074\332\102\135\122\162\001\001\000" + + "\002\001\001\000\004\054\u011e\001\001\000\002\001\001" + + "\000\040\004\165\005\u0124\007\270\022\264\024\201\031" + + "\210\047\257\053\200\054\041\062\325\066\265\071\272" + + "\074\156\102\135\122\162\001\001\000\040\004\165\005" + + "\u0123\007\270\022\264\024\201\031\210\047\257\053\200" + + "\054\041\062\324\066\265\071\272\074\156\102\135\122" + + "\162\001\001\000\040\004\165\005\u0122\007\270\022\264" + + "\024\201\031\210\047\257\053\200\054\041\062\323\066" + + "\265\071\272\074\156\102\135\122\162\001\001\000\002" + + "\001\001\000\002\001\001\000\002\001\001\000\002\001" + + "\001\000\002\001\001\000\002\001\001\000\002\001\001" + + "\000\002\001\001\000\006\031\243\034\u012b\001\001\000" + + "\002\001\001\000\062\003\u0132\004\165\005\u0139\007\270" + + "\011\u0131\022\264\024\201\031\210\041\u0133\043\205\045" + + "\215\047\257\053\200\054\041\060\121\062\111\066\265" + + "\071\272\074\156\102\135\114\u0130\116\375\120\u0134\122" + + "\162\001\001\000\044\004\165\007\270\022\264\024\201" + + "\031\210\047\257\053\200\054\041\062\111\066\265\071" + + "\272\074\156\102\135\114\u0165\116\375\120\u0134\122\162" + + "\001\001\000\044\004\165\007\270\022\264\024\201\031" + + "\210\047\257\053\200\054\041\062\111\066\265\071\272" + + "\074\156\102\135\114\u0164\116\375\120\u0134\122\162\001" + + "\001\000\050\004\165\007\270\011\u0131\022\264\024\201" + + "\031\210\047\257\053\200\054\041\060\u0163\062\111\066" + + "\265\071\272\074\156\102\135\114\u0130\116\375\120\u0134" + + "\122\162\001\001\000\002\001\001\000\002\001\001\000" + + "\002\001\001\000\002\001\001\000\002\001\001\000\044" + + "\004\165\007\270\022\264\024\201\031\210\047\257\053" + + "\200\054\041\062\111\066\265\071\272\074\156\102\135" + + "\114\u0142\116\375\120\u0134\122\162\001\001\000\044\004" + + "\165\007\270\022\264\024\201\031\210\047\257\053\200" + + "\054\041\062\111\066\265\071\272\074\156\102\135\114" + + "\u0141\116\375\120\u0134\122\162\001\001\000\044\004\165" + + "\007\270\022\264\024\201\031\210\047\257\053\200\054" + + "\041\062\111\066\265\071\272\074\156\102\135\114\u0140" + + "\116\375\120\u0134\122\162\001\001\000\044\004\165\007" + + "\270\022\264\024\201\031\210\047\257\053\200\054\041" + + "\062\111\066\265\071\272\074\156\102\135\114\u013f\116" + + "\375\120\u0134\122\162\001\001\000\002\001\001\000\044" + + "\004\165\007\270\022\264\024\201\031\210\047\257\053" + + "\200\054\041\062\111\066\265\071\272\074\156\102\135" + + "\114\u013b\116\375\120\u0134\122\162\001\001\000\002\001" + + "\001\000\042\004\165\007\270\022\264\024\201\031\210" + + "\047\257\053\200\054\041\062\111\066\265\071\272\074" + + "\156\102\135\116\375\120\u0106\122\162\001\001\000\042" + + "\004\165\007\270\022\264\024\201\031\210\047\257\053" + + "\200\054\041\062\111\066\265\071\272\074\156\102\135" + + "\116\375\120\u0104\122\162\001\001\000\042\004\165\007" + + "\270\022\264\024\201\031\210\047\257\053\200\054\041" + + "\062\111\066\265\071\272\074\156\102\135\116\375\120" + + "\376\122\162\001\001\000\002\001\001\000\002\001\001" + + "\000\002\001\001\000\002\001\001\000\060\003\u0132\004" + + "\165\007\270\011\u0131\022\264\024\201\031\210\041\u0146" + + "\043\u0147\045\215\047\257\053\200\054\041\060\121\062" + + "\111\066\265\071\272\074\156\102\135\114\u0130\116\375" + + "\120\u0134\122\162\001\001\000\050\004\165\007\270\011" + + "\u0131\022\264\024\201\031\210\047\257\053\200\054\041" + + "\060\u0145\062\111\066\265\071\272\074\156\102\135\114" + + "\u0130\116\375\120\u0134\122\162\001\001\000\002\001\001" + + "\000\002\001\001\000\002\001\001\000\056\003\u0132\004" + + "\165\007\270\011\u0131\022\264\024\201\031\210\043\u014a" + + "\045\u0149\047\257\053\200\054\041\060\121\062\111\066" + + "\265\071\272\074\156\102\135\114\u0130\116\375\120\u0134" + + "\122\162\001\001\000\002\001\001\000\002\001\001\000" + + "\044\004\165\007\270\022\264\024\201\031\210\047\257" + + "\053\200\054\041\062\111\066\265\071\272\074\156\102" + + "\135\114\u0162\116\375\120\u0134\122\162\001\001\000\044" + + "\004\165\007\270\022\264\024\201\031\210\047\257\053" + + "\200\054\041\062\111\066\265\071\272\074\156\102\135" + + "\114\u0161\116\375\120\u0134\122\162\001\001\000\044\004" + + "\165\007\270\022\264\024\201\031\210\047\257\053\200" + + "\054\041\062\111\066\265\071\272\074\156\102\135\114" + + "\u0160\116\375\120\u0134\122\162\001\001\000\044\004\165" + + "\007\270\022\264\024\201\031\210\047\257\053\200\054" + + "\041\062\111\066\265\071\272\074\156\102\135\114\u015f" + + "\116\375\120\u0134\122\162\001\001\000\044\004\165\007" + + "\270\022\264\024\201\031\210\047\257\053\200\054\041" + + "\062\111\066\265\071\272\074\156\102\135\114\u015e\116" + + "\375\120\u0134\122\162\001\001\000\044\004\165\007\270" + + "\022\264\024\201\031\210\047\257\053\200\054\041\062" + + "\111\066\265\071\272\074\156\102\135\114\u015d\116\375" + + "\120\u0134\122\162\001\001\000\044\004\165\007\270\022" + + "\264\024\201\031\210\047\257\053\200\054\041\062\111" + + "\066\265\071\272\074\156\102\135\114\u015c\116\375\120" + + "\u0134\122\162\001\001\000\044\004\165\007\270\022\264" + + "\024\201\031\210\047\257\053\200\054\041\062\111\066" + + "\265\071\272\074\156\102\135\114\u015b\116\375\120\u0134" + + "\122\162\001\001\000\044\004\165\007\270\022\264\024" + + "\201\031\210\047\257\053\200\054\041\062\111\066\265" + + "\071\272\074\156\102\135\114\u015a\116\375\120\u0134\122" + + "\162\001\001\000\044\004\165\007\270\022\264\024\201" + + "\031\210\047\257\053\200\054\041\062\111\066\265\071" + + "\272\074\156\102\135\114\u0159\116\375\120\u0134\122\162" + + "\001\001\000\044\004\165\007\270\022\264\024\201\031" + + "\210\047\257\053\200\054\041\062\111\066\265\071\272" + + "\074\156\102\135\114\u0158\116\375\120\u0134\122\162\001" + + "\001\000\044\004\165\007\270\022\264\024\201\031\210" + + "\047\257\053\200\054\041\062\111\066\265\071\272\074" + + "\156\102\135\114\u0157\116\375\120\u0134\122\162\001\001" + + "\000\002\001\001\000\002\001\001\000\002\001\001\000" + + "\002\001\001\000\002\001\001\000\002\001\001\000\002" + + "\001\001\000\002\001\001\000\002\001\001\000\002\001" + + "\001\000\002\001\001\000\002\001\001\000\002\001\001" + + "\000\002\001\001\000\002\001\001\000\002\001\001\000" + + "\002\001\001\000\002\001\001\000\002\001\001\000\006" + + "\031\243\034\u016b\001\001\000\002\001\001\000\144\002" + + "\106\003\105\004\165\005\104\006\132\007\131\010\142" + + "\011\141\021\110\022\107\023\202\024\201\025\155\026" + + "\154\030\225\031\210\035\u016d\036\227\040\103\041\102" + + "\042\206\043\205\044\216\045\215\046\174\047\172\053" + + "\200\054\041\057\122\060\121\061\113\062\111\063\145" + + "\064\144\065\116\066\114\070\130\071\127\073\157\074" + + "\156\102\135\113\140\114\137\115\171\116\167\117\213" + + "\120\212\121\163\122\162\001\001\000\002\001\001\000" + + "\002\001\001\000\064\003\u0132\004\165\005\u0171\007\270" + + "\011\u0131\022\264\024\201\026\u0170\031\210\041\102\043" + + "\205\045\215\047\257\053\200\054\041\060\121\062\111" + + "\066\265\071\272\074\156\102\135\114\u0130\116\375\120" + + "\u0134\122\162\001\001\000\002\001\001\000\002\001\001" + + "\000\060\003\u0132\004\165\007\270\011\u0131\022\264\024" + + "\201\031\210\041\u0133\043\205\045\215\047\257\053\200" + + "\054\041\060\121\062\111\066\265\071\272\074\156\102" + + "\135\114\u0130\116\375\120\u0134\122\162\001\001\000\074" + + "\004\165\006\132\007\131\021\110\022\107\023\202\024" + + "\201\031\210\046\174\047\172\053\200\054\041\061\113" + + "\062\111\065\116\066\114\070\130\071\127\073\157\074" + + "\156\102\135\113\u0196\114\u0195\115\171\116\167\117\213" + + "\120\212\121\163\122\162\001\001\000\074\004\165\006" + + "\132\007\131\021\110\022\107\023\202\024\201\031\210" + + "\046\174\047\172\053\200\054\041\061\113\062\111\065" + + "\116\066\114\070\130\071\127\073\157\074\156\102\135" + + "\113\u0194\114\u0193\115\171\116\167\117\213\120\212\121" + + "\163\122\162\001\001\000\074\004\165\006\132\007\131" + + "\021\110\022\107\023\202\024\201\031\210\046\174\047" + + "\172\053\200\054\041\061\113\062\111\065\116\066\114" + + "\070\130\071\127\073\157\074\156\102\135\113\u0192\114" + + "\u0191\115\171\116\167\117\213\120\212\121\163\122\162" + + "\001\001\000\074\004\165\006\132\007\131\021\110\022" + + "\107\023\202\024\201\031\210\046\174\047\172\053\200" + + "\054\041\061\113\062\111\065\116\066\114\070\130\071" + + "\127\073\157\074\156\102\135\113\u0190\114\u018f\115\171" + + "\116\167\117\213\120\212\121\163\122\162\001\001\000" + + "\074\004\165\006\132\007\131\021\110\022\107\023\202" + + "\024\201\031\210\046\174\047\172\053\200\054\041\061" + + "\113\062\111\065\116\066\114\070\130\071\127\073\157" + + "\074\156\102\135\113\u018e\114\u018d\115\171\116\167\117" + + "\213\120\212\121\163\122\162\001\001\000\074\004\165" + + "\006\132\007\131\021\110\022\107\023\202\024\201\031" + + "\210\046\174\047\172\053\200\054\041\061\113\062\111" + + "\065\116\066\114\070\130\071\127\073\157\074\156\102" + + "\135\113\u018c\114\u018b\115\171\116\167\117\213\120\212" + + "\121\163\122\162\001\001\000\074\004\165\006\132\007" + + "\131\021\110\022\107\023\202\024\201\031\210\046\174" + + "\047\172\053\200\054\041\061\113\062\111\065\116\066" + + "\114\070\130\071\127\073\157\074\156\102\135\113\u018a" + + "\114\u0189\115\171\116\167\117\213\120\212\121\163\122" + + "\162\001\001\000\074\004\165\006\132\007\131\021\110" + + "\022\107\023\202\024\201\031\210\046\174\047\172\053" + + "\200\054\041\061\113\062\111\065\116\066\114\070\130" + + "\071\127\073\157\074\156\102\135\113\u0188\114\u0187\115" + + "\171\116\167\117\213\120\212\121\163\122\162\001\001" + + "\000\074\004\165\006\132\007\131\021\110\022\107\023" + + "\202\024\201\031\210\046\174\047\172\053\200\054\041" + + "\061\113\062\111\065\116\066\114\070\130\071\127\073" + + "\157\074\156\102\135\113\u0186\114\u0185\115\171\116\167" + + "\117\213\120\212\121\163\122\162\001\001\000\074\004" + + "\165\006\132\007\131\021\110\022\107\023\202\024\201" + + "\031\210\046\174\047\172\053\200\054\041\061\113\062" + + "\111\065\116\066\114\070\130\071\127\073\157\074\156" + + "\102\135\113\u0184\114\u0183\115\171\116\167\117\213\120" + + "\212\121\163\122\162\001\001\000\074\004\165\006\132" + + "\007\131\021\110\022\107\023\202\024\201\031\210\046" + + "\174\047\172\053\200\054\041\061\113\062\111\065\116" + + "\066\114\070\130\071\127\073\157\074\156\102\135\113" + + "\u0182\114\u0181\115\171\116\167\117\213\120\212\121\163" + + "\122\162\001\001\000\074\004\165\006\132\007\131\021" + + "\110\022\107\023\202\024\201\031\210\046\174\047\172" + + "\053\200\054\041\061\113\062\111\065\116\066\114\070" + + "\130\071\127\073\157\074\156\102\135\113\u0180\114\u017f" + + "\115\171\116\167\117\213\120\212\121\163\122\162\001" + + "\001\000\002\001\001\000\002\001\001\000\002\001\001" + + "\000\002\001\001\000\002\001\001\000\002\001\001\000" + + "\002\001\001\000\002\001\001\000\002\001\001\000\002" + + "\001\001\000\002\001\001\000\002\001\001\000\002\001" + + "\001\000\002\001\001\000\002\001\001\000\002\001\001" + + "\000\002\001\001\000\002\001\001\000\002\001\001\000" + + "\002\001\001\000\002\001\001\000\002\001\001\000\002" + + "\001\001\000\002\001\001\000\002\001\001\000\002\001" + + "\001\000\002\001\001\000\002\001\001\000\020\004\165" + + "\005\u019c\031\210\053\200\054\041\102\135\122\306\001" + + "\001\000\002\001\001\000\024\004\165\005\u019e\007\277" + + "\024\201\031\210\053\200\054\041\102\135\122\162\001" + + "\001\000\002\001\001\000\002\001\001\000\002\001\001" + + "\000\010\053\066\054\041\055\u01c4\001\001\000\010\053" + + "\066\054\041\055\u01bf\001\001\000\002\001\001\000\006" + + "\031\243\034\u01b6\001\001\000\010\053\066\054\041\055" + + "\u01b0\001\001\000\002\001\001\000\002\001\001\000\010" + + "\053\066\054\041\055\u01aa\001\001\000\010\053\066\054" + + "\041\055\u01ab\001\001\000\002\001\001\000\002\001\001" + + "\000\140\002\106\003\105\004\165\005\104\006\132\007" + + "\131\010\142\011\141\021\110\022\107\023\202\024\201" + + "\025\155\026\154\030\u01ad\031\210\040\103\041\102\042" + + "\206\043\205\044\216\045\215\046\174\047\172\053\200" + + "\054\041\057\122\060\121\061\113\062\111\063\145\064" + + "\144\065\116\066\114\070\130\071\127\073\157\074\156" + + "\102\135\113\140\114\137\115\171\116\167\117\213\120" + + "\212\121\163\122\162\001\001\000\002\001\001\000\140" + + "\002\106\003\105\004\165\005\104\006\132\007\131\010" + + "\142\011\141\021\110\022\107\023\202\024\201\025\155" + + "\026\154\030\u01af\031\210\040\103\041\102\042\206\043" + + "\205\044\216\045\215\046\174\047\172\053\200\054\041" + + "\057\122\060\121\061\113\062\111\063\145\064\144\065" + + "\116\066\114\070\130\071\127\073\157\074\156\102\135" + + "\113\140\114\137\115\171\116\167\117\213\120\212\121" + + "\163\122\162\001\001\000\002\001\001\000\002\001\001" + + "\000\140\002\106\003\105\004\165\005\104\006\132\007" + + "\131\010\142\011\141\021\110\022\107\023\202\024\201" + + "\025\155\026\154\030\u01b2\031\210\040\103\041\102\042" + + "\206\043\205\044\216\045\215\046\174\047\172\053\200" + + "\054\041\057\122\060\121\061\113\062\111\063\145\064" + + "\144\065\116\066\114\070\130\071\127\073\157\074\156" + + "\102\135\113\140\114\137\115\171\116\167\117\213\120" + + "\212\121\163\122\162\001\001\000\002\001\001\000\140" + + "\002\106\003\105\004\165\005\104\006\132\007\131\010" + + "\142\011\141\021\110\022\107\023\202\024\201\025\155" + + "\026\154\030\u01b4\031\210\040\103\041\102\042\206\043" + + "\205\044\216\045\215\046\174\047\172\053\200\054\041" + + "\057\122\060\121\061\113\062\111\063\145\064\144\065" + + "\116\066\114\070\130\071\127\073\157\074\156\102\135" + + "\113\140\114\137\115\171\116\167\117\213\120\212\121" + + "\163\122\162\001\001\000\002\001\001\000\012\014\u01ba" + + "\053\066\054\041\055\u01a3\001\001\000\002\001\001\000" + + "\002\001\001\000\002\001\001\000\010\053\066\054\041" + + "\055\u01b0\001\001\000\002\001\001\000\140\002\106\003" + + "\105\004\165\005\104\006\132\007\131\010\142\011\141" + + "\021\110\022\107\023\202\024\201\025\155\026\154\030" + + "\u01bc\031\210\040\103\041\102\042\206\043\205\044\216" + + "\045\215\046\174\047\172\053\200\054\041\057\122\060" + + "\121\061\113\062\111\063\145\064\144\065\116\066\114" + + "\070\130\071\127\073\157\074\156\102\135\113\140\114" + + "\137\115\171\116\167\117\213\120\212\121\163\122\162" + + "\001\001\000\002\001\001\000\140\002\106\003\105\004" + + "\165\005\104\006\132\007\131\010\142\011\141\021\110" + + "\022\107\023\202\024\201\025\155\026\154\030\u01be\031" + + "\210\040\103\041\102\042\206\043\205\044\216\045\215" + + "\046\174\047\172\053\200\054\041\057\122\060\121\061" + + "\113\062\111\063\145\064\144\065\116\066\114\070\130" + + "\071\127\073\157\074\156\102\135\113\140\114\137\115" + + "\171\116\167\117\213\120\212\121\163\122\162\001\001" + + "\000\002\001\001\000\002\001\001\000\140\002\106\003" + + "\105\004\165\005\104\006\132\007\131\010\142\011\141" + + "\021\110\022\107\023\202\024\201\025\155\026\154\030" + + "\u01c1\031\210\040\103\041\102\042\206\043\205\044\216" + + "\045\215\046\174\047\172\053\200\054\041\057\122\060" + + "\121\061\113\062\111\063\145\064\144\065\116\066\114" + + "\070\130\071\127\073\157\074\156\102\135\113\140\114" + + "\137\115\171\116\167\117\213\120\212\121\163\122\162" + + "\001\001\000\002\001\001\000\140\002\106\003\105\004" + + "\165\005\104\006\132\007\131\010\142\011\141\021\110" + + "\022\107\023\202\024\201\025\155\026\154\030\u01c3\031" + + "\210\040\103\041\102\042\206\043\205\044\216\045\215" + + "\046\174\047\172\053\200\054\041\057\122\060\121\061" + + "\113\062\111\063\145\064\144\065\116\066\114\070\130" + + "\071\127\073\157\074\156\102\135\113\140\114\137\115" + + "\171\116\167\117\213\120\212\121\163\122\162\001\001" + + "\000\002\001\001\000\002\001\001\000\140\002\106\003" + + "\105\004\165\005\104\006\132\007\131\010\142\011\141" + + "\021\110\022\107\023\202\024\201\025\155\026\154\030" + + "\u01c6\031\210\040\103\041\102\042\206\043\205\044\216" + + "\045\215\046\174\047\172\053\200\054\041\057\122\060" + + "\121\061\113\062\111\063\145\064\144\065\116\066\114" + + "\070\130\071\127\073\157\074\156\102\135\113\140\114" + + "\137\115\171\116\167\117\213\120\212\121\163\122\162" + + "\001\001\000\002\001\001\000\140\002\106\003\105\004" + + "\165\005\104\006\132\007\131\010\142\011\141\021\110" + + "\022\107\023\202\024\201\025\155\026\154\030\u01c8\031" + + "\210\040\103\041\102\042\206\043\205\044\216\045\215" + + "\046\174\047\172\053\200\054\041\057\122\060\121\061" + + "\113\062\111\063\145\064\144\065\116\066\114\070\130" + + "\071\127\073\157\074\156\102\135\113\140\114\137\115" + + "\171\116\167\117\213\120\212\121\163\122\162\001\001" + + "\000\002\001\001\000\002\001\001\000\002\001\001\000" + + "\002\001\001\000\002\001\001\000\034\004\165\005\u01ce" + + "\007\270\022\264\024\201\031\210\047\330\053\200\054" + + "\041\071\272\074\156\102\135\122\162\001\001\000\002" + + "\001\001\000\006\031\243\034\u01d0\001\001\000\002\001" + + "\001\000\026\004\165\005\u01d4\007\270\024\201\031\210" + + "\053\200\054\041\071\356\102\135\122\162\001\001\000" + + "\032\004\165\005\u01d3\007\270\022\264\024\201\031\210" + + "\053\200\054\041\071\272\074\355\102\135\122\162\001" + + "\001\000\002\001\001\000\002\001\001\000\126\002\106" + + "\003\105\004\165\005\u01d9\006\132\007\131\010\142\011" + + "\141\021\110\022\107\023\202\024\201\031\210\040\u01d8" + + "\041\u0146\042\206\043\u01da\044\216\045\215\046\174\047" + + "\172\053\200\054\041\057\122\060\121\061\113\062\111" + + "\065\116\066\114\070\130\071\127\073\157\074\156\102" + + "\135\113\140\114\137\115\171\116\167\117\213\120\212" + + "\121\163\122\162\001\001\000\052\004\165\005\u01d7\007" + + "\270\011\u0131\022\264\024\201\031\210\047\257\053\200" + + "\054\041\060\u0145\062\111\066\265\071\272\074\156\102" + + "\135\114\u0130\116\375\120\u0134\122\162\001\001\000\002" + + "\001\001\000\002\001\001\000\002\001\001\000\002\001" + + "\001\000\122\002\106\003\105\004\165\005\u01dc\006\132" + + "\007\131\010\142\011\141\021\110\022\107\023\202\024" + + "\201\031\210\042\u01dd\043\u014a\044\u01de\045\u0149\046\174" + + "\047\172\053\200\054\041\057\122\060\121\061\113\062" + + "\111\065\116\066\114\070\130\071\127\073\157\074\156" + + "\102\135\113\140\114\137\115\171\116\167\117\213\120" + + "\212\121\163\122\162\001\001\000\002\001\001\000\002" + + "\001\001\000\002\001\001\000\002\001\001\000\002\001" + + "\001\000\140\002\106\003\105\004\165\005\104\006\132" + + "\007\131\010\142\011\141\021\110\022\107\023\202\024" + + "\201\025\155\026\154\030\u01e2\031\210\040\103\041\102" + + "\042\206\043\205\044\216\045\215\046\174\047\172\053" + + "\200\054\041\057\122\060\121\061\113\062\111\063\145" + + "\064\144\065\116\066\114\070\130\071\127\073\157\074" + + "\156\102\135\113\140\114\137\115\171\116\167\117\213" + + "\120\212\121\163\122\162\001\001\000\002\001\001\000" + + "\002\001\001\000\002\001\001\000\140\002\106\003\105" + + "\004\165\005\104\006\132\007\131\010\142\011\141\021" + + "\110\022\107\023\202\024\201\025\155\026\154\030\u01e6" + + "\031\210\040\103\041\102\042\206\043\205\044\216\045" + + "\215\046\174\047\172\053\200\054\041\057\122\060\121" + + "\061\113\062\111\063\145\064\144\065\116\066\114\070" + + "\130\071\127\073\157\074\156\102\135\113\140\114\137" + + "\115\171\116\167\117\213\120\212\121\163\122\162\001" + + "\001\000\002\001\001\000\140\002\106\003\105\004\165" + + "\005\104\006\132\007\131\010\142\011\141\021\110\022" + + "\107\023\202\024\201\025\155\026\154\030\u01e8\031\210" + + "\040\103\041\102\042\206\043\205\044\216\045\215\046" + + "\174\047\172\053\200\054\041\057\122\060\121\061\113" + + "\062\111\063\145\064\144\065\116\066\114\070\130\071" + + "\127\073\157\074\156\102\135\113\140\114\137\115\171" + + "\116\167\117\213\120\212\121\163\122\162\001\001\000" + + "\002\001\001\000\002\001\001\000\140\002\106\003\105" + + "\004\165\005\104\006\132\007\131\010\142\011\141\021" + + "\110\022\107\023\202\024\201\025\155\026\154\030\u01eb" + + "\031\210\040\103\041\102\042\206\043\205\044\216\045" + + "\215\046\174\047\172\053\200\054\041\057\122\060\121" + + "\061\113\062\111\063\145\064\144\065\116\066\114\070" + + "\130\071\127\073\157\074\156\102\135\113\140\114\137" + + "\115\171\116\167\117\213\120\212\121\163\122\162\001" + + "\001\000\004\031\u01ec\001\001\000\002\001\001\000\002" + + "\001\001\000\140\002\106\003\105\004\165\005\104\006" + + "\132\007\131\010\142\011\141\021\110\022\107\023\202" + + "\024\201\025\155\026\154\030\u01ef\031\210\040\103\041" + + "\102\042\206\043\205\044\216\045\215\046\174\047\172" + + "\053\200\054\041\057\122\060\121\061\113\062\111\063" + + "\145\064\144\065\116\066\114\070\130\071\127\073\157" + + "\074\156\102\135\113\140\114\137\115\171\116\167\117" + + "\213\120\212\121\163\122\162\001\001\000\002\001\001" + + "\000\140\002\106\003\105\004\165\005\104\006\132\007" + + "\131\010\142\011\141\021\110\022\107\023\202\024\201" + + "\025\155\026\154\030\u01f1\031\210\040\103\041\102\042" + + "\206\043\205\044\216\045\215\046\174\047\172\053\200" + + "\054\041\057\122\060\121\061\113\062\111\063\145\064" + + "\144\065\116\066\114\070\130\071\127\073\157\074\156" + + "\102\135\113\140\114\137\115\171\116\167\117\213\120" + + "\212\121\163\122\162\001\001\000\002\001\001\000\016" + + "\014\070\015\065\017\u01f3\053\066\054\041\055\067\001" + + "\001\000\002\001\001\000\002\001\001\000\140\002\106" + + "\003\105\004\165\005\104\006\132\007\131\010\142\011" + + "\141\021\110\022\107\023\202\024\201\025\155\026\154" + + "\030\u01f6\031\210\040\103\041\102\042\206\043\205\044" + + "\216\045\215\046\174\047\172\053\200\054\041\057\122" + + "\060\121\061\113\062\111\063\145\064\144\065\116\066" + + "\114\070\130\071\127\073\157\074\156\102\135\113\140" + + "\114\137\115\171\116\167\117\213\120\212\121\163\122" + + "\162\001\001\000\002\001\001\000\140\002\106\003\105" + + "\004\165\005\104\006\132\007\131\010\142\011\141\021" + + "\110\022\107\023\202\024\201\025\155\026\154\030\u01f8" + + "\031\210\040\103\041\102\042\206\043\205\044\216\045" + + "\215\046\174\047\172\053\200\054\041\057\122\060\121" + + "\061\113\062\111\063\145\064\144\065\116\066\114\070" + + "\130\071\127\073\157\074\156\102\135\113\140\114\137" + + "\115\171\116\167\117\213\120\212\121\163\122\162\001" + + "\001\000\002\001\001\000\002\001\001\000\140\002\106" + + "\003\105\004\165\005\104\006\132\007\131\010\142\011" + + "\141\021\110\022\107\023\202\024\201\025\155\026\154" + + "\030\u01fb\031\210\040\103\041\102\042\206\043\205\044" + + "\216\045\215\046\174\047\172\053\200\054\041\057\122" + + "\060\121\061\113\062\111\063\145\064\144\065\116\066" + + "\114\070\130\071\127\073\157\074\156\102\135\113\140" + + "\114\137\115\171\116\167\117\213\120\212\121\163\122" + + "\162\001\001\000\002\001\001\000\140\002\106\003\105" + + "\004\165\005\104\006\132\007\131\010\142\011\141\021" + + "\110\022\107\023\202\024\201\025\155\026\154\030\u01fd" + + "\031\210\040\103\041\102\042\206\043\205\044\216\045" + + "\215\046\174\047\172\053\200\054\041\057\122\060\121" + + "\061\113\062\111\063\145\064\144\065\116\066\114\070" + + "\130\071\127\073\157\074\156\102\135\113\140\114\137" + + "\115\171\116\167\117\213\120\212\121\163\122\162\001" + + "\001\000\002\001\001\000\002\001\001\000\140\002\106" + + "\003\105\004\165\005\104\006\132\007\131\010\142\011" + + "\141\021\110\022\107\023\202\024\201\025\155\026\154" + + "\030\u0200\031\210\040\103\041\102\042\206\043\205\044" + + "\216\045\215\046\174\047\172\053\200\054\041\057\122" + + "\060\121\061\113\062\111\063\145\064\144\065\116\066" + + "\114\070\130\071\127\073\157\074\156\102\135\113\140" + + "\114\137\115\171\116\167\117\213\120\212\121\163\122" + + "\162\001\001\000\002\001\001\000\140\002\106\003\105" + + "\004\165\005\104\006\132\007\131\010\142\011\141\021" + + "\110\022\107\023\202\024\201\025\155\026\154\030\u0202" + + "\031\210\040\103\041\102\042\206\043\205\044\216\045" + + "\215\046\174\047\172\053\200\054\041\057\122\060\121" + + "\061\113\062\111\063\145\064\144\065\116\066\114\070" + + "\130\071\127\073\157\074\156\102\135\113\140\114\137" + + "\115\171\116\167\117\213\120\212\121\163\122\162\001" + + "\001\000\002\001\001\000\002\001\001\000\002\001\001" + + "\000\140\002\106\003\105\004\165\005\104\006\132\007" + + "\131\010\142\011\141\021\110\022\107\023\202\024\201" + + "\025\155\026\154\030\u0206\031\210\040\103\041\102\042" + + "\206\043\205\044\216\045\215\046\174\047\172\053\200" + + "\054\041\057\122\060\121\061\113\062\111\063\145\064" + + "\144\065\116\066\114\070\130\071\127\073\157\074\156" + + "\102\135\113\140\114\137\115\171\116\167\117\213\120" + + "\212\121\163\122\162\001\001\000\004\031\u0207\001\001" + + "\000\002\001\001\000\004\031\u0209\001\001\000\002\001" + + "\001\000\140\002\106\003\105\004\165\005\104\006\132" + + "\007\131\010\142\011\141\021\110\022\107\023\202\024" + + "\201\025\155\026\154\030\u0217\031\210\040\103\041\102" + + "\042\206\043\205\044\216\045\215\046\174\047\172\053" + + "\200\054\041\057\122\060\121\061\113\062\111\063\145" + + "\064\144\065\116\066\114\070\130\071\127\073\157\074" + + "\156\102\135\113\140\114\137\115\171\116\167\117\213" + + "\120\212\121\163\122\162\001\001\000\016\014\070\015" + + "\065\017\u0212\053\066\054\041\055\067\001\001\000\016" + + "\014\070\015\065\017\u020d\053\066\054\041\055\067\001" + + "\001\000\002\001\001\000\002\001\001\000\140\002\106" + + "\003\105\004\165\005\104\006\132\007\131\010\142\011" + + "\141\021\110\022\107\023\202\024\201\025\155\026\154" + + "\030\u0210\031\210\040\103\041\102\042\206\043\205\044" + + "\216\045\215\046\174\047\172\053\200\054\041\057\122" + + "\060\121\061\113\062\111\063\145\064\144\065\116\066" + + "\114\070\130\071\127\073\157\074\156\102\135\113\140" + + "\114\137\115\171\116\167\117\213\120\212\121\163\122" + + "\162\001\001\000\004\031\u0211\001\001\000\002\001\001" + + "\000\002\001\001\000\002\001\001\000\140\002\106\003" + + "\105\004\165\005\104\006\132\007\131\010\142\011\141" + + "\021\110\022\107\023\202\024\201\025\155\026\154\030" + + "\u0215\031\210\040\103\041\102\042\206\043\205\044\216" + + "\045\215\046\174\047\172\053\200\054\041\057\122\060" + + "\121\061\113\062\111\063\145\064\144\065\116\066\114" + + "\070\130\071\127\073\157\074\156\102\135\113\140\114" + + "\137\115\171\116\167\117\213\120\212\121\163\122\162" + + "\001\001\000\004\031\u0216\001\001\000\002\001\001\000" + + "\004\031\u0218\001\001\000\002\001\001\000\002\001\001" + + "\000\006\053\u0224\054\041\001\001\000\014\053\200\054" + + "\041\102\u021e\103\u021d\104\u021c\001\001\000\002\001\001" + + "\000\002\001\001\000\002\001\001\000\010\053\200\054" + + "\041\102\u0220\001\001\000\002\001\001\000\002\001\001" + + "\000\006\053\u0223\054\041\001\001\000\002\001\001\000" + + "\002\001\001\000\004\031\u0232\001\001\000\002\001\001" + + "\000\006\053\u0228\054\041\001\001\000\004\031\u0229\001" + + "\001\000\002\001\001\000\016\014\070\015\065\017\u022f" + + "\053\066\054\041\055\067\001\001\000\016\014\070\015" + + "\065\017\u022c\053\066\054\041\055\067\001\001\000\002" + + "\001\001\000\004\031\u022e\001\001\000\002\001\001\000" + + "\002\001\001\000\004\031\u0231\001\001\000\002\001\001" + + "\000\002\001\001\000\016\014\070\015\065\017\u0238\053" + + "\066\054\041\055\067\001\001\000\016\014\070\015\065" + + "\017\u0235\053\066\054\041\055\067\001\001\000\002\001" + + "\001\000\004\031\u0237\001\001\000\002\001\001\000\002" + + "\001\001\000\004\031\u023a\001\001\000\002\001\001\000" + + "\006\031\u023e\052\u023d\001\001\000\140\002\106\003\105" + + "\004\165\005\104\006\132\007\131\010\142\011\141\021" + + "\110\022\107\023\202\024\201\025\155\026\154\030\u024b" + + "\031\210\040\103\041\102\042\206\043\205\044\216\045" + + "\215\046\174\047\172\053\200\054\041\057\122\060\121" + + "\061\113\062\111\063\145\064\144\065\116\066\114\070" + + "\130\071\127\073\157\074\156\102\135\113\140\114\137" + + "\115\171\116\167\117\213\120\212\121\163\122\162\001" + + "\001\000\002\001\001\000\002\001\001\000\010\053\066" + + "\054\041\055\u0247\001\001\000\010\053\066\054\041\055" + + "\u0241\001\001\000\002\001\001\000\006\031\u023e\052\u0243" + + "\001\001\000\002\001\001\000\006\031\u023e\052\u0245\001" + + "\001\000\002\001\001\000\006\031\u023e\052\u024a\001\001" + + "\000\002\001\001\000\006\031\u023e\052\u0249\001\001\000" + + "\002\001\001\000\002\001\001\000\002\001\001\000\002" + + "\001\001\000\010\053\066\054\041\055\u024e\001\001\000" + + "\002\001\001\000\002\001\001\000\002\001\001\000\004" + + "\013\u0252\001\001\000\010\031\u0254\053\u0253\054\041\001" + + "\001\000\012\031\u027b\053\u027a\054\041\075\u027c\001\001" + + "\000\004\075\u0255\001\001\000\004\027\u0277\001\001\000" + + "\010\110\u0258\111\u025b\112\u0259\001\001\000\002\001\001" + + "\000\006\053\u0269\054\041\001\001\000\002\001\001\000" + + "\002\001\001\000\002\001\001\000\002\001\001\000\010" + + "\110\u0258\111\u025b\112\u0263\001\001\000\002\001\001\000" + + "\002\001\001\000\002\001\001\000\002\001\001\000\002" + + "\001\001\000\002\001\001\000\002\001\001\000\006\110" + + "\u0258\111\u0266\001\001\000\002\001\001\000\002\001\001" + + "\000\002\001\001\000\002\001\001\000\002\001\001\000" + + "\002\001\001\000\002\001\001\000\002\001\001\000\002" + + "\001\001\000\002\001\001\000\002\001\001\000\002\001" + + "\001\000\002\001\001\000\002\001\001\000\002\001\001" + + "\000\002\001\001\000\002\001\001\000\002\001\001\000" + + "\002\001\001\000\002\001\001\000\004\075\u0280\001\001" + + "\000\004\075\u027e\001\001\000\004\027\u027d\001\001\000" + + "\002\001\001\000\004\027\u027f\001\001\000\002\001\001" + + "\000\004\027\u0281\001\001\000\002\001\001\000\004\077" + + "\u0286\001\001\000\012\053\200\054\041\102\u028d\105\u0291" + + "\001\001\000\010\053\200\054\041\102\u0290\001\001\000" + + "\012\053\200\054\041\102\u028d\105\u028c\001\001\000\002" + + "\001\001\000\016\014\070\015\065\017\u0288\053\066\054" + + "\041\055\067\001\001\000\002\001\001\000\006\031\u028b" + + "\032\u028a\001\001\000\002\001\001\000\002\001\001\000" + + "\002\001\001\000\002\001\001\000\010\053\200\054\041" + + "\102\u028f\001\001\000\002\001\001\000\002\001\001\000" + + "\002\001\001\000\002\001\001\000\002\001\001\000\002" + + "\001\001\000\010\053\u0296\054\041\056\u0298\001\001\000" + + "\002\001\001\000\006\053\u029e\054\041\001\001\000\002" + + "\001\001\000\006\053\u029b\054\041\001\001\000\002\001" + + "\001\000\002\001\001\000\006\053\u029d\054\041\001\001" + + "\000\002\001\001\000\002\001\001\000\004\031\u02a3\001" + + "\001\000\004\031\u02a2\001\001\000\002\001\001\000\002" + + "\001\001\000\002\001\001\000\012\031\u02a9\053\u02a8\054" + + "\041\075\u02aa\001\001\000\004\075\u02a6\001\001\000\004" + + "\027\u02a7\001\001\000\002\001\001\000\004\075\u02ae\001" + + "\001\000\004\075\u02ac\001\001\000\004\027\u02ab\001\001" + + "\000\002\001\001\000\004\027\u02ad\001\001\000\002\001" + + "\001\000\004\027\u02af\001\001\000\002\001\001\000\004" + + "\031\u02b4\001\001\000\004\031\u02b3\001\001\000\002\001" + + "\001\000\002\001\001\000\002\001\001\000\002\001\001" + + "" }); + + /** Access to reduce_goto table. */ + public short[][] reduce_table() {return _reduce_table;} + + /** Instance of action encapsulation class. */ + protected CUP$CompParser$actions action_obj; + + /** Action encapsulation object initializer. */ + protected void init_actions() + { + action_obj = new CUP$CompParser$actions(this); + } + + /** Invoke a user supplied parse action. */ + public java_cup.runtime.Symbol do_action( + int act_num, + java_cup.runtime.lr_parser parser, + java.util.Stack stack, + int top) + throws java.lang.Exception + { + /* call code in generated class */ + return action_obj.CUP$CompParser$do_action(act_num, parser, stack, top); + } + + /** Indicates start state. */ + public int start_state() {return 0;} + /** Indicates start production. */ + public int start_production() {return 1;} + + /** EOF Symbol index. */ + public int EOF_sym() {return 0;} + + /** error Symbol index. */ + public int error_sym() {return 1;} + + + + + public CompModule alloymodule=null; + + @Override public Symbol parse() throws java.lang.Exception { + int act; // current action code + Symbol lhs_sym = null; // the Symbol/stack element returned by a reduce + short handle_size, lhs_sym_num; // information about production being reduced with + boolean logging = "yes".equals(System.getProperty("debug")); + production_tab = production_table(); + action_tab = action_table(); + reduce_tab = reduce_table(); + init_actions(); + user_init(); + // start + cur_token = scan(); + stack.removeAllElements(); + stack.push(getSymbolFactory().startSymbol("START", 0, start_state())); + tos = 0; + for (_done_parsing = false; !_done_parsing; ) { + act = get_action(((Symbol)stack.peek()).parse_state, cur_token.sym); + if (act > 0) { // "shift"; thus, we shift to the encoded state by pushing it on the stack + if (logging) System.out.println("shift " + cur_token.sym); + cur_token.parse_state = act-1; + stack.push(cur_token); + tos++; + cur_token = scan(); + } else if (act<0) { // "reduce" + if (logging) System.out.println("reduce " + ((-act)-1)); + lhs_sym = do_action((-act)-1, this, stack, tos); + lhs_sym_num = production_tab[(-act)-1][0]; + handle_size = production_tab[(-act)-1][1]; + for (int i = 0; i < handle_size; i++) { stack.pop(); tos--; } + act = get_reduce(((Symbol)stack.peek()).parse_state, lhs_sym_num); + lhs_sym.parse_state = act; + stack.push(lhs_sym); + tos++; + } else { // "error" + if (logging) System.out.println("error"); + syntax_error(cur_token); + done_parsing(); + } + } + return lhs_sym; + } + + public void syntax_error(Symbol x) throws Err { + Map ch = new LinkedHashMap(); + ch.put(CompSym.ARROW, "->"); + ch.put(CompSym.ANY_ARROW_SOME, "->"); + ch.put(CompSym.ANY_ARROW_ONE, "->"); + ch.put(CompSym.ANY_ARROW_LONE, "->"); + ch.put(CompSym.SOME_ARROW_ANY, "some"); + ch.put(CompSym.SOME_ARROW_SOME, "some"); + ch.put(CompSym.SOME_ARROW_ONE, "some"); + ch.put(CompSym.SOME_ARROW_LONE, "some"); + ch.put(CompSym.ONE_ARROW_ANY, "one"); + ch.put(CompSym.ONE_ARROW_SOME, "one"); + ch.put(CompSym.ONE_ARROW_ONE, "one"); + ch.put(CompSym.ONE_ARROW_LONE, "one"); + ch.put(CompSym.LONE_ARROW_ANY, "lone"); + ch.put(CompSym.LONE_ARROW_SOME, "lone"); + ch.put(CompSym.LONE_ARROW_ONE, "lone"); + ch.put(CompSym.LONE_ARROW_LONE, "lone"); + ch.put(CompSym.INTADD, "fun"); + ch.put(CompSym.INTSUB, "fun"); + ch.put(CompSym.INTMUL, "fun"); + ch.put(CompSym.INTDIV, "fun"); + ch.put(CompSym.INTREM, "fun"); + ch.put(CompSym.INTMIN, "fun"); + ch.put(CompSym.INTMAX, "fun"); + ch.put(CompSym.INTNEXT, "fun"); + ch.put(CompSym.TOTALORDER, "pred"); + ch.put(CompSym.ABSTRACT, "abstract"); + ch.put(CompSym.ALL, "all"); + ch.put(CompSym.ALL2, "all"); + ch.put(CompSym.AMPERSAND, "&"); + ch.put(CompSym.AND, "&&"); + ch.put(CompSym.AS, "as"); + ch.put(CompSym.ASSERT, "assert"); + ch.put(CompSym.AT, "@"); + ch.put(CompSym.BAR, "|"); + ch.put(CompSym.BUT, "but"); + ch.put(CompSym.CARET, "^"); + ch.put(CompSym.CHECK, "check"); + ch.put(CompSym.COLON, ":"); + ch.put(CompSym.COMMA, ", "); + ch.put(CompSym.DISJ, "disj"); + ch.put(CompSym.DOMAIN, "<:"); + ch.put(CompSym.DOT, "."); + ch.put(CompSym.ELSE, "else"); + ch.put(CompSym.ENUM, "enum"); + ch.put(CompSym.EQUALS, "="); + ch.put(CompSym.EXACTLY, "exactly"); + ch.put(CompSym.EXH, "exh"); + ch.put(CompSym.EXPECT, "expect"); + ch.put(CompSym.EXTENDS, "extends"); + ch.put(CompSym.FACT, "fact"); + ch.put(CompSym.FOR, "for"); + ch.put(CompSym.FUN, "fun"); + ch.put(CompSym.GT, ">"); + ch.put(CompSym.GTE, ">="); + ch.put(CompSym.HASH, "#"); + ch.put(CompSym.IDEN, "iden"); + ch.put(CompSym.IFF, "iff"); + ch.put(CompSym.IMPLIES, "=>"); + ch.put(CompSym.IN, "in"); + ch.put(CompSym.INT, "int"); + ch.put(CompSym.LBRACE, "{"); + ch.put(CompSym.LBRACKET, "["); + ch.put(CompSym.LET, "let"); + ch.put(CompSym.LONE2, "lone"); + ch.put(CompSym.LONE, "lone"); + ch.put(CompSym.LPAREN, "("); + ch.put(CompSym.LT, "<"); + ch.put(CompSym.LTE, "<="); + ch.put(CompSym.MINUS, "-"); + ch.put(CompSym.MODULE, "module"); + ch.put(CompSym.NO2, "no"); + ch.put(CompSym.NO, "no"); + ch.put(CompSym.NONE, "none"); + ch.put(CompSym.NOT, "!"); + ch.put(CompSym.NOTEQUALS, "!"); + ch.put(CompSym.NOTGT, "!"); + ch.put(CompSym.NOTGTE, "!"); + ch.put(CompSym.NOTIN, "!"); + ch.put(CompSym.NOTLT, "!"); + ch.put(CompSym.NOTLTE, "!"); + ch.put(CompSym.ONE2, "one"); + ch.put(CompSym.ONE, "one"); + ch.put(CompSym.OPEN, "open"); + ch.put(CompSym.OR, "||"); + ch.put(CompSym.PART, "part"); + ch.put(CompSym.PLUS, "+"); + ch.put(CompSym.PLUSPLUS, "++"); + ch.put(CompSym.PRED, "pred"); + ch.put(CompSym.PRIVATE, "private"); + ch.put(CompSym.RANGE, ":>"); + ch.put(CompSym.RBRACE, "}"); + ch.put(CompSym.RBRACKET, "]"); + ch.put(CompSym.RPAREN, ")"); + ch.put(CompSym.RUN, "run"); + ch.put(CompSym.SEQ, "seq"); + ch.put(CompSym.SET, "set"); + ch.put(CompSym.SHL, "<<"); + ch.put(CompSym.SHR, ">>>"); + ch.put(CompSym.SHA, ">>"); + ch.put(CompSym.SIG, "sig"); + ch.put(CompSym.SIGINT, "Int"); + ch.put(CompSym.SLASH, "/"); + ch.put(CompSym.SOME2, "some"); + ch.put(CompSym.SOME, "some"); + ch.put(CompSym.STAR, "*"); + ch.put(CompSym.STRING, "String"); + ch.put(CompSym.SUM2, "sum"); + ch.put(CompSym.SUM, "sum"); + ch.put(CompSym.THIS, "this"); + ch.put(CompSym.TILDE, "~"); + ch.put(CompSym.UNIV, "univ"); + ch.put(CompSym.ID, "NAME"); + ch.put(CompSym.NUMBER, "NUMBER"); + ch.put(CompSym.STR, "STRING"); + TreeSet list = new TreeSet(); + Pos p=Pos.UNKNOWN; + if (x!=null && x.value instanceof Pos) p=(Pos)(x.value); + else if (x!=null && x.value instanceof Expr) p=((Expr)(x.value)).pos; + else if (x!=null) p=x.pos; + if (!stack.empty()) for(Map.Entry e:ch.entrySet()) { + int key=e.getKey(), act=get_action(((Symbol)stack.peek()).parse_state, key); + if (act==0) continue; + try { + if (act>0 || alloy_confirm(key)) list.add(e.getValue()); + } catch(Throwable ex) { + // If the parser is really really confused, alloy_confirm() could fail with array out-of-bound exception, etc. + } + } + String result="There are "+list.size()+" possible tokens that can appear here:\n"; + for(String item:list) result=result+item+" "; + throw new ErrorSyntax(p, (list.size()!=0)?result:""); + } + + private boolean alloy_confirm(int key) { + int state = ((Symbol)stack.peek()).parse_state; + Stack newstack=new Stack(); for(Object x:stack) newstack.push(x); + while(true) { + int act = get_action(state, key); + if (act>0) return true; + if (act==0) return false; + int lhs_sym_num = production_tab[(-act)-1][0]; + int handle_size = production_tab[(-act)-1][1]; + for (int i = 0; i < handle_size; i++) { if (newstack.empty()) return false; newstack.pop(); } + if (newstack.empty()) return false; + if (newstack.peek() instanceof Symbol) state=((Symbol)newstack.peek()).parse_state; + state=get_reduce(state, lhs_sym_num); + newstack.push(null); + } + } + + static CompModule alloy_parseStream (List seenDollar, + Map loaded, Map fc, CompModule root, + int lineOffset, String filename, String prefix, int initialResolutionMode) throws Err, FileNotFoundException, IOException { + Reader isr=null; + try { + if (root==null && prefix.length()!=0) throw new ErrorFatal("Internal error (parse subfile with root==null)"); + if (root!=null && prefix.length()==0) throw new ErrorFatal("Internal error (parse topfile with root!=null)"); + CompModule u = new CompModule(root, filename, prefix); + if (root == null) + u.addOpen(null, null, ExprVar.make(null, "util/integer"), null, ExprVar.make(null, "integer")); + u.resolution = initialResolutionMode; + String content = fc!=null ? fc.get(filename) : null; + if (content==null && loaded!=null) content = loaded.get(filename); + if (content==null) content = Util.readAll(filename); + if (loaded!=null) loaded.put(filename,content); + content = Util.convertLineBreak(content); + isr = new StringReader(content); + CompFilter s = new CompFilter(u, seenDollar, filename, lineOffset, new BufferedReader(isr)); + CompParser p = new CompParser(s); + p.alloymodule=u; + try {p.parse();} catch(Throwable ex) {if (ex instanceof Err) throw (Err)ex; throw new ErrorFatal("Parser Exception", ex);} + // if no sigs are defined by the user, add one + if (root == null && u.getAllSigs().isEmpty()) { + u.addGhostSig(); + } + return u; + } finally { + Util.close(isr); + } + } + + +} + +/** Cup generated class to encapsulate user supplied action code.*/ +class CUP$CompParser$actions { + + + /** This function is needed to handle a difficult parsing ambiguity. + * + *

+ * "some EXPR", "one EXPR", and "lone EXPR" + * can be either formulas (saying the EXPR has at least 1, exactly 1, or at most 1 tuple), + * or multiplicity constraints (saying something else has this multiplicity). + * + *

+ * So we let the parser generate the former by default. + * And whenever we construct a Decl "x: y" object, + * or an binary expression "x in y", or a function return type, + * we call this method on y to convert it into a multiplicity constraint. + * + *

+ * This is safe, because in all 3 cases, a formula would be illegal. + * So the first form is always wrong. + * + *

+ * And this is sufficient, because those are the only 3 places + * where a mulitplicity constraint is allowed to appear. + * + * @return a newly formed multiplciity constraint (if this.op==SOME or LONE or ONE), + * otherwise it just returns the original node. + */ + private Expr mult(Expr x) throws Err { + if (x instanceof ExprUnary) { + ExprUnary y=(ExprUnary)x; + if (y.op==ExprUnary.Op.SOME) return ExprUnary.Op.SOMEOF.make(y.pos, y.sub); + if (y.op==ExprUnary.Op.LONE) return ExprUnary.Op.LONEOF.make(y.pos, y.sub); + if (y.op==ExprUnary.Op.ONE) return ExprUnary.Op.ONEOF.make(y.pos, y.sub); + } + return x; + } + private void nod(ExprVar name) throws Err { + if (name.label.indexOf('$')>=0) throw new ErrorSyntax(name.pos, "The name cannot contain the '$' symbol."); + } + private void nod(List names) throws Err { + if (names!=null) for(ExprVar n:names) if (n!=null && n.label.indexOf('$')>=0) throw new ErrorSyntax(n.pos, "The name cannot contain the '$' symbol."); + } + private void c(boolean follow, ExprVar o, ExprVar x, ExprVar n, Expr e, List s, ExprConstant c) throws Err { + if (n!=null) nod(n); + int bitwidth=(-1), maxseq=(-1), overall=(-1), expects=(c==null ? -1 : c.num); + Pos p = o.pos.merge(n!=null ? n.span() : e.span()); + for(int i=s.size()-1; i>=0; i--) { + Sig j=s.get(i).sig; int k=s.get(i).startingScope; + p=p.merge(j.pos); + if (j.label.equals("univ")) { overall=k; s.remove(i); continue; } + if (j.label.equals("int")) { if (bitwidth>=0) throw new ErrorSyntax(j.pos, "The bitwidth cannot be specified more than once."); bitwidth=k; s.remove(i); continue; } + if (j.label.equals("seq")) { if (maxseq>=0) throw new ErrorSyntax(j.pos, "The maximum sequence length cannot be specified more than once."); maxseq=k; s.remove(i); continue; } + } + if (n!=null) + parser.alloymodule.addCommand(follow, p, n.label, o.label.equals("c"), overall, bitwidth, maxseq, expects, s, x); + else + parser.alloymodule.addCommand(follow, p, e, o.label.equals("c"), overall, bitwidth, maxseq, expects, s, x); + } + private Expr t(Pos pos, Pos oldClosing, Expr left, Expr right, Pos close) throws Err { + if (right instanceof ExprVar) { + String n = ((ExprVar)right).label; + if (n.equals("int")) return ExprUnary.Op.CAST2INT.make(pos, left); + if (n.equals("disj")) return ExprList.makeDISJOINT(pos, close, Util.asList(left)); + if (n.equals("pred/totalOrder")) return ExprList.makeTOTALORDER(pos, close, Util.asList(left)); + } + else if (right instanceof ExprList) { + return ((ExprList)right).addArg(left); + } + return ExprBadJoin.make(pos, oldClosing, left, right); + } + + private final CompParser parser; + + /** Constructor */ + CUP$CompParser$actions(CompParser parser) { + this.parser = parser; + } + + /** Method with the actual generated action code. */ + public final java_cup.runtime.Symbol CUP$CompParser$do_action( + int CUP$CompParser$act_num, + java_cup.runtime.lr_parser CUP$CompParser$parser, + java.util.Stack CUP$CompParser$stack, + int CUP$CompParser$top) + throws java.lang.Exception + { + /* Symbol object for return from actions */ + java_cup.runtime.Symbol CUP$CompParser$result; + + /* select the action based on the action number */ + switch (CUP$CompParser$act_num) + { + /*. . . . . . . . . . . . . . . . . . . .*/ + case 349: // BaseExpr ::= LBRACE Declz RBRACE + { + Expr RESULT =null; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + List a = (List)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int cleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int cright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Pos c = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT = ExprQt.Op.COMPREHENSION.make(o, c, a, ExprConstant.TRUE); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("BaseExpr",2, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 348: // BaseExpr ::= LBRACE Declz SuperOrBar RBRACE + { + Expr RESULT =null; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).value; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + List a = (List)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int cleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int cright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Pos c = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT = ExprQt.Op.COMPREHENSION.make(o, c, a, b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("BaseExpr",2, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 347: // BaseExpr ::= Super + { + Expr RESULT =null; + int xleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int xright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr x = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT = x; + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("BaseExpr",2, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 346: // BaseExpr ::= AT Name + { + Expr RESULT =null; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int xleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int xright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + ExprVar x = (ExprVar)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + nod(x); RESULT = ExprVar.make(o.merge(x.pos), "@"+x.label); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("BaseExpr",2, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 345: // BaseExpr ::= SigRef + { + Expr RESULT =null; + int xleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int xright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + ExprVar x = (ExprVar)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT = x; + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("BaseExpr",2, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 344: // BaseExpr ::= LPAREN Expr RPAREN + { + Expr RESULT =null; + int xleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int xright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Expr x = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + RESULT = x; + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("BaseExpr",2, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 343: // BaseExpr ::= INTNEXT + { + Expr RESULT =null; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT = ExprConstant.Op.NEXT.make(o, 0); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("BaseExpr",2, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 342: // BaseExpr ::= INTMAX + { + Expr RESULT =null; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT = ExprConstant.Op.MAX.make(o, 0); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("BaseExpr",2, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 341: // BaseExpr ::= INTMIN + { + Expr RESULT =null; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT = ExprConstant.Op.MIN.make(o, 0); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("BaseExpr",2, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 340: // BaseExpr ::= THIS + { + Expr RESULT =null; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT = ExprVar.make(o, "this"); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("BaseExpr",2, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 339: // BaseExpr ::= IDEN + { + Expr RESULT =null; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT = ExprVar.make(o, "iden"); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("BaseExpr",2, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 338: // BaseExpr ::= STR + { + Expr RESULT =null; + int xleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int xright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + ExprConstant x = (ExprConstant)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT = x; + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("BaseExpr",2, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 337: // BaseExpr ::= NUMBER + { + Expr RESULT =null; + int xleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int xright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + ExprConstant x = (ExprConstant)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT = x; + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("BaseExpr",2, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 336: // UnopExprB ::= CARET UnopExprB + { + Expr RESULT =null; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprUnary.Op.CLOSURE .make(o,b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("UnopExprB",80, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 335: // UnopExprB ::= STAR UnopExprB + { + Expr RESULT =null; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprUnary.Op.RCLOSURE .make(o,b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("UnopExprB",80, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 334: // UnopExprB ::= TILDE UnopExprB + { + Expr RESULT =null; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprUnary.Op.TRANSPOSE.make(o,b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("UnopExprB",80, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 333: // UnopExprB ::= BaseExpr + { + Expr RESULT =null; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=b; + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("UnopExprB",80, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 332: // UnopExprA ::= CARET UnopExprA + { + Expr RESULT =null; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprUnary.Op.CLOSURE .make(o,b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("UnopExprA",79, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 331: // UnopExprA ::= STAR UnopExprA + { + Expr RESULT =null; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprUnary.Op.RCLOSURE .make(o,b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("UnopExprA",79, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 330: // UnopExprA ::= TILDE UnopExprA + { + Expr RESULT =null; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprUnary.Op.TRANSPOSE.make(o,b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("UnopExprA",79, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 329: // UnopExprA ::= CARET Bind + { + Expr RESULT =null; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprUnary.Op.CLOSURE .make(o,b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("UnopExprA",79, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 328: // UnopExprA ::= STAR Bind + { + Expr RESULT =null; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprUnary.Op.RCLOSURE .make(o,b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("UnopExprA",79, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 327: // UnopExprA ::= TILDE Bind + { + Expr RESULT =null; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprUnary.Op.TRANSPOSE.make(o,b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("UnopExprA",79, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 326: // DotExprB ::= BracketExprB DOT SUM + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Expr a = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + RESULT=ExprUnary.Op.CAST2SIGINT.make(o, ExprUnary.Op.CAST2INT.make(o, a)); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("DotExprB",18, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 325: // DotExprB ::= BracketExprB DOT INT + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Expr a = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + RESULT=ExprUnary.Op.CAST2SIGINT.make(o, ExprUnary.Op.CAST2INT.make(o, a)); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("DotExprB",18, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 324: // DotExprB ::= BracketExprB DOT TOTALORDER + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Expr a = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Pos b = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=t(o, null, a, ExprVar.make(b, "pred/totalOrder"), null); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("DotExprB",18, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 323: // DotExprB ::= BracketExprB DOT DISJ + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Expr a = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Pos b = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=t(o, null, a, ExprVar.make(b, "disj"), null); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("DotExprB",18, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 322: // DotExprB ::= BracketExprB DOT UnopExprB + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Expr a = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=t(o, null, a, b, null); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("DotExprB",18, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 321: // DotExprB ::= UnopExprB + { + Expr RESULT =null; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=b; + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("DotExprB",18, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 320: // DotExprA ::= BracketExprB DOT Bind + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Expr a = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=t(o, null, a, b, null); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("DotExprA",17, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 319: // DotExprA ::= UnopExprA + { + Expr RESULT =null; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=b; + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("DotExprA",17, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 318: // BracketExprB ::= SUM LBRACKET Exprs RBRACKET + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).right; + Pos a = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + List b = (List)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int cleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int cright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Pos c = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + Expr aa=ExprVar.make(a, "int"); for(Expr bb:b) aa=t(aa.span().merge(bb.span()), c, bb, aa, c); RESULT=ExprUnary.Op.CAST2SIGINT.make(a, aa); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("BracketExprB",5, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 317: // BracketExprB ::= INT LBRACKET Exprs RBRACKET + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).right; + Pos a = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + List b = (List)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int cleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int cright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Pos c = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + Expr aa=ExprVar.make(a, "int"); for(Expr bb:b) aa=t(aa.span().merge(bb.span()), c, bb, aa, c); RESULT=ExprUnary.Op.CAST2SIGINT.make(a, aa); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("BracketExprB",5, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 316: // BracketExprB ::= TOTALORDER LBRACKET Exprs RBRACKET + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).right; + Pos a = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + List b = (List)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int cleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int cright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Pos c = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + Expr aa=ExprVar.make(a, "pred/totalOrder"); for(Expr bb:b) aa=t(aa.span().merge(bb.span()), c, bb, aa, c); RESULT=aa; + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("BracketExprB",5, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 315: // BracketExprB ::= DISJ LBRACKET Exprs RBRACKET + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).right; + Pos a = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + List b = (List)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int cleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int cright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Pos c = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + Expr aa=ExprVar.make(a, "disj"); for(Expr bb:b) aa=t(aa.span().merge(bb.span()), c, bb, aa, c); RESULT=aa; + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("BracketExprB",5, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 314: // BracketExprB ::= BracketExprB LBRACKET Exprs RBRACKET + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).right; + Expr a = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + List b = (List)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int cleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int cright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Pos c = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + Expr aa=a; for(Expr bb:b) aa=t(aa.span().merge(bb.span()), c, bb, aa, c); RESULT=aa; + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("BracketExprB",5, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 313: // BracketExprB ::= DotExprB + { + Expr RESULT =null; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=b; + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("BracketExprB",5, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 312: // BracketExprA ::= DotExprA + { + Expr RESULT =null; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=b; + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("BracketExprA",4, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 311: // RangeExprB ::= RangeExprB RANGE BracketExprB + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Expr a = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprBinary.Op.RANGE.make(o, null, a, b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("RangeExprB",55, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 310: // RangeExprB ::= BracketExprB + { + Expr RESULT =null; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=b; + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("RangeExprB",55, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 309: // RangeExprA ::= RangeExprB RANGE Bind + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Expr a = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprBinary.Op.RANGE.make(o, null, a, b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("RangeExprA",54, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 308: // RangeExprA ::= BracketExprA + { + Expr RESULT =null; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=b; + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("RangeExprA",54, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 307: // DomainExprB ::= DomainExprB DOMAIN RangeExprB + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Expr a = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprBinary.Op.DOMAIN.make(o, null, a, b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("DomainExprB",16, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 306: // DomainExprB ::= RangeExprB + { + Expr RESULT =null; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=b; + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("DomainExprB",16, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 305: // DomainExprA ::= DomainExprB DOMAIN Bind + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Expr a = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprBinary.Op.DOMAIN.make(o, null, a, b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("DomainExprA",15, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 304: // DomainExprA ::= RangeExprA + { + Expr RESULT =null; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=b; + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("DomainExprA",15, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 303: // RelationExprB ::= DomainExprB RelOp RelationExprB + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Expr a = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pair o = (Pair)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=o.b.make(o.a, null, a, b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("RelationExprB",58, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 302: // RelationExprB ::= DomainExprB + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr a = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=a; + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("RelationExprB",58, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 301: // RelationExprA ::= DomainExprB RelOp Bind + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Expr a = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pair o = (Pair)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=o.b.make(o.a, null, a, b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("RelationExprA",57, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 300: // RelationExprA ::= DomainExprA + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr a = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=a; + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("RelationExprA",57, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 299: // RelOp ::= LONE_ARROW_LONE + { + Pair RESULT =null; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=new Pair(o, ExprBinary.Op.LONE_ARROW_LONE); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("RelOp",56, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 298: // RelOp ::= LONE_ARROW_ONE + { + Pair RESULT =null; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=new Pair(o, ExprBinary.Op.LONE_ARROW_ONE ); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("RelOp",56, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 297: // RelOp ::= LONE_ARROW_SOME + { + Pair RESULT =null; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=new Pair(o, ExprBinary.Op.LONE_ARROW_SOME); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("RelOp",56, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 296: // RelOp ::= LONE_ARROW_ANY + { + Pair RESULT =null; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=new Pair(o, ExprBinary.Op.LONE_ARROW_ANY ); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("RelOp",56, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 295: // RelOp ::= ONE_ARROW_LONE + { + Pair RESULT =null; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=new Pair(o, ExprBinary.Op.ONE_ARROW_LONE ); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("RelOp",56, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 294: // RelOp ::= ONE_ARROW_ONE + { + Pair RESULT =null; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=new Pair(o, ExprBinary.Op.ONE_ARROW_ONE ); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("RelOp",56, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 293: // RelOp ::= ONE_ARROW_SOME + { + Pair RESULT =null; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=new Pair(o, ExprBinary.Op.ONE_ARROW_SOME ); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("RelOp",56, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 292: // RelOp ::= ONE_ARROW_ANY + { + Pair RESULT =null; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=new Pair(o, ExprBinary.Op.ONE_ARROW_ANY ); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("RelOp",56, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 291: // RelOp ::= SOME_ARROW_LONE + { + Pair RESULT =null; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=new Pair(o, ExprBinary.Op.SOME_ARROW_LONE); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("RelOp",56, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 290: // RelOp ::= SOME_ARROW_ONE + { + Pair RESULT =null; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=new Pair(o, ExprBinary.Op.SOME_ARROW_ONE ); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("RelOp",56, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 289: // RelOp ::= SOME_ARROW_SOME + { + Pair RESULT =null; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=new Pair(o, ExprBinary.Op.SOME_ARROW_SOME); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("RelOp",56, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 288: // RelOp ::= SOME_ARROW_ANY + { + Pair RESULT =null; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=new Pair(o, ExprBinary.Op.SOME_ARROW_ANY ); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("RelOp",56, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 287: // RelOp ::= ANY_ARROW_LONE + { + Pair RESULT =null; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=new Pair(o, ExprBinary.Op.ANY_ARROW_LONE ); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("RelOp",56, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 286: // RelOp ::= ANY_ARROW_ONE + { + Pair RESULT =null; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=new Pair(o, ExprBinary.Op.ANY_ARROW_ONE ); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("RelOp",56, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 285: // RelOp ::= ANY_ARROW_SOME + { + Pair RESULT =null; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=new Pair(o, ExprBinary.Op.ANY_ARROW_SOME ); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("RelOp",56, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 284: // RelOp ::= ARROW + { + Pair RESULT =null; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=new Pair(o, ExprBinary.Op.ARROW ); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("RelOp",56, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 283: // IntersectExprB ::= IntersectExprB AMPERSAND RelationExprB + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Expr a = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprBinary.Op.INTERSECT.make(o, null, a, b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("IntersectExprB",37, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 282: // IntersectExprB ::= RelationExprB + { + Expr RESULT =null; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=b; + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("IntersectExprB",37, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 281: // IntersectExprA ::= IntersectExprB AMPERSAND Bind + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Expr a = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprBinary.Op.INTERSECT.make(o, null, a, b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("IntersectExprA",36, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 280: // IntersectExprA ::= RelationExprA + { + Expr RESULT =null; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=b; + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("IntersectExprA",36, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 279: // OverrideExprB ::= OverrideExprB PLUSPLUS IntersectExprB + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Expr a = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprBinary.Op.PLUSPLUS.make(o, null, a, b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("OverrideExprB",52, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 278: // OverrideExprB ::= IntersectExprB + { + Expr RESULT =null; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=b; + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("OverrideExprB",52, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 277: // OverrideExprA ::= OverrideExprB PLUSPLUS Bind + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Expr a = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprBinary.Op.PLUSPLUS.make(o, null, a, b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("OverrideExprA",51, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 276: // OverrideExprA ::= IntersectExprA + { + Expr RESULT =null; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=b; + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("OverrideExprA",51, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 275: // NumUnopExprB ::= INT NumUnopExprB + { + Expr RESULT =null; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprUnary.Op.CAST2SIGINT.make(o, ExprUnary.Op.CAST2INT.make(o, b)); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("NumUnopExprB",48, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 274: // NumUnopExprB ::= SUM NumUnopExprB + { + Expr RESULT =null; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprUnary.Op.CAST2SIGINT.make(o, ExprUnary.Op.CAST2INT.make(o, b)); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("NumUnopExprB",48, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 273: // NumUnopExprB ::= HASH NumUnopExprB + { + Expr RESULT =null; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprUnary.Op.CARDINALITY.make(o, b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("NumUnopExprB",48, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 272: // NumUnopExprB ::= OverrideExprB + { + Expr RESULT =null; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=b; + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("NumUnopExprB",48, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 271: // NumUnopExprA ::= INT NumUnopExprA + { + Expr RESULT =null; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprUnary.Op.CAST2SIGINT.make(o, ExprUnary.Op.CAST2INT.make(o, b)); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("NumUnopExprA",47, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 270: // NumUnopExprA ::= SUM NumUnopExprA + { + Expr RESULT =null; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprUnary.Op.CAST2SIGINT.make(o, ExprUnary.Op.CAST2INT.make(o, b)); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("NumUnopExprA",47, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 269: // NumUnopExprA ::= HASH NumUnopExprA + { + Expr RESULT =null; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprUnary.Op.CARDINALITY.make(o, b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("NumUnopExprA",47, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 268: // NumUnopExprA ::= INT Bind + { + Expr RESULT =null; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprUnary.Op.CAST2SIGINT.make(o, ExprUnary.Op.CAST2INT.make(o, b)); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("NumUnopExprA",47, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 267: // NumUnopExprA ::= SUM Bind + { + Expr RESULT =null; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprUnary.Op.CAST2SIGINT.make(o, ExprUnary.Op.CAST2INT.make(o, b)); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("NumUnopExprA",47, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 266: // NumUnopExprA ::= HASH Bind + { + Expr RESULT =null; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprUnary.Op.CARDINALITY.make(o, b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("NumUnopExprA",47, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 265: // NumUnopExprA ::= OverrideExprA + { + Expr RESULT =null; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=b; + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("NumUnopExprA",47, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 264: // MulExprB ::= MulExprB INTREM NumUnopExprB + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Expr a = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprBinary.Op.REM .make(o, null, a, b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("MulExprB",76, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 263: // MulExprB ::= MulExprB INTDIV NumUnopExprB + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Expr a = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprBinary.Op.DIV .make(o, null, a, b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("MulExprB",76, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 262: // MulExprB ::= MulExprB INTMUL NumUnopExprB + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Expr a = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprBinary.Op.MUL .make(o, null, a, b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("MulExprB",76, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 261: // MulExprB ::= NumUnopExprB + { + Expr RESULT =null; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=b; + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("MulExprB",76, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 260: // MulExprA ::= MulExprB INTREM Bind + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Expr a = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprBinary.Op.REM .make(o, null, a, b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("MulExprA",75, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 259: // MulExprA ::= MulExprB INTDIV Bind + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Expr a = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprBinary.Op.DIV .make(o, null, a, b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("MulExprA",75, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 258: // MulExprA ::= MulExprB INTMUL Bind + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Expr a = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprBinary.Op.MUL .make(o, null, a, b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("MulExprA",75, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 257: // MulExprA ::= NumUnopExprA + { + Expr RESULT =null; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=b; + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("MulExprA",75, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 256: // UnionDiffExprB ::= UnionDiffExprB INTSUB MulExprB + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Expr a = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprBinary.Op.IMINUS.make(o, null, a, b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("UnionDiffExprB",78, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 255: // UnionDiffExprB ::= UnionDiffExprB INTADD MulExprB + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Expr a = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprBinary.Op.IPLUS.make(o, null, a, b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("UnionDiffExprB",78, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 254: // UnionDiffExprB ::= UnionDiffExprB MINUS MulExprB + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Expr a = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprBinary.Op.MINUS.make(o, null, a, b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("UnionDiffExprB",78, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 253: // UnionDiffExprB ::= UnionDiffExprB PLUS MulExprB + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Expr a = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprBinary.Op.PLUS .make(o, null, a, b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("UnionDiffExprB",78, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 252: // UnionDiffExprB ::= MulExprB + { + Expr RESULT =null; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=b; + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("UnionDiffExprB",78, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 251: // UnionDiffExprA ::= UnionDiffExprB INTSUB Bind + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Expr a = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprBinary.Op.IMINUS.make(o, null, a, b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("UnionDiffExprA",77, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 250: // UnionDiffExprA ::= UnionDiffExprB INTADD Bind + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Expr a = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprBinary.Op.IPLUS.make(o, null, a, b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("UnionDiffExprA",77, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 249: // UnionDiffExprA ::= UnionDiffExprB MINUS Bind + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Expr a = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprBinary.Op.MINUS.make(o, null, a, b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("UnionDiffExprA",77, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 248: // UnionDiffExprA ::= UnionDiffExprB PLUS Bind + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Expr a = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprBinary.Op.PLUS .make(o, null, a, b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("UnionDiffExprA",77, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 247: // UnionDiffExprA ::= MulExprA + { + Expr RESULT =null; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=b; + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("UnionDiffExprA",77, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 246: // ShiftExprB ::= ShiftExprB SHA UnionDiffExprB + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Expr a = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprBinary.Op.SHA.make(o, null, a, b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("ShiftExprB",74, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 245: // ShiftExprB ::= ShiftExprB SHR UnionDiffExprB + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Expr a = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprBinary.Op.SHR.make(o, null, a, b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("ShiftExprB",74, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 244: // ShiftExprB ::= ShiftExprB SHL UnionDiffExprB + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Expr a = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprBinary.Op.SHL.make(o, null, a, b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("ShiftExprB",74, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 243: // ShiftExprB ::= UnionDiffExprB + { + Expr RESULT =null; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=b; + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("ShiftExprB",74, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 242: // ShiftExprA ::= ShiftExprB SHA Bind + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Expr a = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprBinary.Op.SHA.make(o, null, a, b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("ShiftExprA",73, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 241: // ShiftExprA ::= ShiftExprB SHR Bind + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Expr a = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprBinary.Op.SHR.make(o, null, a, b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("ShiftExprA",73, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 240: // ShiftExprA ::= ShiftExprB SHL Bind + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Expr a = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprBinary.Op.SHL.make(o, null, a, b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("ShiftExprA",73, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 239: // ShiftExprA ::= UnionDiffExprA + { + Expr RESULT =null; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=b; + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("ShiftExprA",73, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 238: // CompareExprB ::= ShiftExprB + { + Expr RESULT =null; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=b; + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("CompareExprB",7, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 237: // CompareExprB ::= SEQ ShiftExprB + { + Expr RESULT =null; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprBinary.Op.ISSEQ_ARROW_LONE.make(o, null, ExprVar.make(o,"seq/Int"), b); parser.alloymodule.addSeq(o); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("CompareExprB",7, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 236: // CompareExprB ::= SET ShiftExprB + { + Expr RESULT =null; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprUnary.Op.SETOF.make(o, b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("CompareExprB",7, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 235: // CompareExprB ::= ONE ShiftExprB + { + Expr RESULT =null; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprUnary.Op.ONE .make(o, b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("CompareExprB",7, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 234: // CompareExprB ::= LONE ShiftExprB + { + Expr RESULT =null; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprUnary.Op.LONE .make(o, b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("CompareExprB",7, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 233: // CompareExprB ::= SOME ShiftExprB + { + Expr RESULT =null; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprUnary.Op.SOME .make(o, b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("CompareExprB",7, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 232: // CompareExprB ::= NO ShiftExprB + { + Expr RESULT =null; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprUnary.Op.NO .make(o, b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("CompareExprB",7, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 231: // CompareExprB ::= ALL ShiftExprB + { + Expr RESULT =null; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + if (1==1) throw new ErrorSyntax(o,"The \"all x\" construct is no longer supported. If you know the range of possible values of x, consider rewriting it as \"x == set_of_all_possible_values\"."); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("CompareExprB",7, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 230: // CompareExprB ::= CompareExprB NOTGTE ShiftExprB + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Expr a = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprBinary.Op.NOT_GTE .make(o, null, a, b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("CompareExprB",7, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 229: // CompareExprB ::= CompareExprB NOTLTE ShiftExprB + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Expr a = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprBinary.Op.NOT_LTE .make(o, null, a, b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("CompareExprB",7, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 228: // CompareExprB ::= CompareExprB NOTGT ShiftExprB + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Expr a = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprBinary.Op.NOT_GT .make(o, null, a, b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("CompareExprB",7, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 227: // CompareExprB ::= CompareExprB NOTLT ShiftExprB + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Expr a = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprBinary.Op.NOT_LT .make(o, null, a, b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("CompareExprB",7, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 226: // CompareExprB ::= CompareExprB NOTEQUALS ShiftExprB + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Expr a = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprBinary.Op.NOT_EQUALS.make(o, null, a, b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("CompareExprB",7, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 225: // CompareExprB ::= CompareExprB NOTIN ShiftExprB + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Expr a = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprBinary.Op.NOT_IN .make(o, null, a, mult(b)); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("CompareExprB",7, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 224: // CompareExprB ::= CompareExprB GTE ShiftExprB + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Expr a = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprBinary.Op.GTE .make(o, null, a, b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("CompareExprB",7, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 223: // CompareExprB ::= CompareExprB LTE ShiftExprB + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Expr a = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprBinary.Op.LTE .make(o, null, a, b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("CompareExprB",7, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 222: // CompareExprB ::= CompareExprB GT ShiftExprB + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Expr a = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprBinary.Op.GT .make(o, null, a, b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("CompareExprB",7, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 221: // CompareExprB ::= CompareExprB LT ShiftExprB + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Expr a = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprBinary.Op.LT .make(o, null, a, b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("CompareExprB",7, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 220: // CompareExprB ::= CompareExprB EQUALS ShiftExprB + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Expr a = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprBinary.Op.EQUALS .make(o, null, a, b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("CompareExprB",7, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 219: // CompareExprB ::= CompareExprB IN ShiftExprB + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Expr a = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprBinary.Op.IN .make(o, null, a, mult(b)); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("CompareExprB",7, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 218: // CompareExprA ::= ShiftExprA + { + Expr RESULT =null; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=b; + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("CompareExprA",6, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 217: // CompareExprA ::= SEQ ShiftExprA + { + Expr RESULT =null; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprBinary.Op.ISSEQ_ARROW_LONE.make(o, null, ExprVar.make(o, "seq/Int"), b); parser.alloymodule.addSeq(o); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("CompareExprA",6, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 216: // CompareExprA ::= SET ShiftExprA + { + Expr RESULT =null; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprUnary.Op.SETOF.make(o, b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("CompareExprA",6, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 215: // CompareExprA ::= ONE ShiftExprA + { + Expr RESULT =null; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprUnary.Op.ONE .make(o, b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("CompareExprA",6, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 214: // CompareExprA ::= LONE ShiftExprA + { + Expr RESULT =null; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprUnary.Op.LONE .make(o, b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("CompareExprA",6, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 213: // CompareExprA ::= SOME ShiftExprA + { + Expr RESULT =null; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprUnary.Op.SOME .make(o, b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("CompareExprA",6, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 212: // CompareExprA ::= NO ShiftExprA + { + Expr RESULT =null; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprUnary.Op.NO .make(o, b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("CompareExprA",6, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 211: // CompareExprA ::= ALL ShiftExprA + { + Expr RESULT =null; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + if (1==1) throw new ErrorSyntax(o,"The \"all x\" construct is no longer supported. If you know the range of possible values of x, consider rewriting it as \"x == set_of_all_possible_values\"."); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("CompareExprA",6, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 210: // CompareExprA ::= CompareExprB NOTGTE ShiftExprA + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Expr a = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprBinary.Op.NOT_GTE .make(o, null, a, b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("CompareExprA",6, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 209: // CompareExprA ::= CompareExprB NOTLTE ShiftExprA + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Expr a = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprBinary.Op.NOT_LTE .make(o, null, a, b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("CompareExprA",6, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 208: // CompareExprA ::= CompareExprB NOTGT ShiftExprA + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Expr a = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprBinary.Op.NOT_GT .make(o, null, a, b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("CompareExprA",6, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 207: // CompareExprA ::= CompareExprB NOTLT ShiftExprA + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Expr a = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprBinary.Op.NOT_LT .make(o, null, a, b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("CompareExprA",6, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 206: // CompareExprA ::= CompareExprB NOTEQUALS ShiftExprA + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Expr a = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprBinary.Op.NOT_EQUALS.make(o, null, a, b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("CompareExprA",6, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 205: // CompareExprA ::= CompareExprB NOTIN ShiftExprA + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Expr a = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprBinary.Op.NOT_IN .make(o, null, a, mult(b)); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("CompareExprA",6, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 204: // CompareExprA ::= CompareExprB GTE ShiftExprA + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Expr a = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprBinary.Op.GTE .make(o, null, a, b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("CompareExprA",6, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 203: // CompareExprA ::= CompareExprB LTE ShiftExprA + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Expr a = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprBinary.Op.LTE .make(o, null, a, b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("CompareExprA",6, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 202: // CompareExprA ::= CompareExprB GT ShiftExprA + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Expr a = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprBinary.Op.GT .make(o, null, a, b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("CompareExprA",6, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 201: // CompareExprA ::= CompareExprB LT ShiftExprA + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Expr a = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprBinary.Op.LT .make(o, null, a, b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("CompareExprA",6, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 200: // CompareExprA ::= CompareExprB EQUALS ShiftExprA + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Expr a = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprBinary.Op.EQUALS .make(o, null, a, b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("CompareExprA",6, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 199: // CompareExprA ::= CompareExprB IN ShiftExprA + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Expr a = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprBinary.Op.IN .make(o, null, a, mult(b)); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("CompareExprA",6, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 198: // NegExprB ::= NOT NegExprB + { + Expr RESULT =null; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprUnary.Op.NOT.make(o, b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("NegExprB",46, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 197: // NegExprB ::= CompareExprB + { + Expr RESULT =null; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=b; + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("NegExprB",46, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 196: // NegExprA ::= NOT NegExprA + { + Expr RESULT =null; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprUnary.Op.NOT.make(o, b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("NegExprA",45, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 195: // NegExprA ::= NOT Bind + { + Expr RESULT =null; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprUnary.Op.NOT.make(o, b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("NegExprA",45, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 194: // NegExprA ::= CompareExprA + { + Expr RESULT =null; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=b; + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("NegExprA",45, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 193: // AndExprB ::= AndExprB AND NegExprB + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Expr a = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprBinary.Op.AND.make(o, null, a, b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("AndExprB",1, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 192: // AndExprB ::= NegExprB + { + Expr RESULT =null; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=b; + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("AndExprB",1, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 191: // AndExprA ::= AndExprB AND Bind + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Expr a = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprBinary.Op.AND.make(o, null, a, b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("AndExprA",0, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 190: // AndExprA ::= NegExprA + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr a = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=a; + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("AndExprA",0, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 189: // ImpliesExprOpenB ::= AndExprB IMPLIES ImpliesExprB + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Expr a = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT = ExprBinary.Op.IMPLIES.make(o, null, a, b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("ImpliesExprOpenB",35, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 188: // ImpliesExprOpenB ::= AndExprB IMPLIES ImpliesExprCloseB ELSE ImpliesExprOpenB + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).right; + Expr a = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int cleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int cright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr c = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT = ExprITE.make(o,a,b,c); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("ImpliesExprOpenB",35, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 187: // ImpliesExprCloseB ::= AndExprB IMPLIES ImpliesExprCloseB ELSE ImpliesExprCloseB + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).right; + Expr a = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int cleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int cright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr c = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT = ExprITE.make(o,a,b,c); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("ImpliesExprCloseB",33, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 186: // ImpliesExprCloseB ::= AndExprB + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr a = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=a; + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("ImpliesExprCloseB",33, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 185: // ImpliesExprB ::= ImpliesExprOpenB + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr a = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=a; + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("ImpliesExprB",31, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 184: // ImpliesExprB ::= ImpliesExprCloseB + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr a = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=a; + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("ImpliesExprB",31, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 183: // ImpliesExprOpenA ::= AndExprB IMPLIES Bind + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Expr a = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT = ExprBinary.Op.IMPLIES.make(o, null, a, b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("ImpliesExprOpenA",34, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 182: // ImpliesExprCloseA ::= AndExprB IMPLIES ImpliesExprCloseB ELSE Bind + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).right; + Expr a = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int cleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int cright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr c = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT = ExprITE.make(o,a,b,c); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("ImpliesExprCloseA",32, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 181: // ImpliesExprOpenA ::= AndExprB IMPLIES ImpliesExprA + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Expr a = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT = ExprBinary.Op.IMPLIES.make(o, null, a, b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("ImpliesExprOpenA",34, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 180: // ImpliesExprOpenA ::= AndExprB IMPLIES ImpliesExprCloseB ELSE ImpliesExprOpenA + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).right; + Expr a = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int cleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int cright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr c = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT = ExprITE.make(o,a,b,c); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("ImpliesExprOpenA",34, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 179: // ImpliesExprCloseA ::= AndExprB IMPLIES ImpliesExprCloseB ELSE ImpliesExprCloseA + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).right; + Expr a = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int cleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int cright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr c = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT = ExprITE.make(o,a,b,c); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("ImpliesExprCloseA",32, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 178: // ImpliesExprCloseA ::= AndExprA + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr a = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=a; + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("ImpliesExprCloseA",32, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 177: // ImpliesExprA ::= ImpliesExprOpenA + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr a = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=a; + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("ImpliesExprA",30, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 176: // ImpliesExprA ::= ImpliesExprCloseA + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr a = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=a; + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("ImpliesExprA",30, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 175: // EquivExprB ::= EquivExprB IFF ImpliesExprB + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Expr a = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprBinary.Op.IFF.make(o, null, a, b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("EquivExprB",20, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 174: // EquivExprB ::= ImpliesExprB + { + Expr RESULT =null; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=b; + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("EquivExprB",20, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 173: // EquivExprA ::= EquivExprB IFF Bind + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Expr a = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprBinary.Op.IFF.make(o, null, a, b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("EquivExprA",19, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 172: // EquivExprA ::= ImpliesExprA + { + Expr RESULT =null; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=b; + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("EquivExprA",19, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 171: // OrExprB ::= OrExprB OR EquivExprB + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Expr a = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprBinary.Op.OR.make(o, null, a, b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("OrExprB",50, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 170: // OrExprB ::= EquivExprB + { + Expr RESULT =null; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=b; + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("OrExprB",50, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 169: // OrExprA ::= OrExprB OR Bind + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Expr a = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprBinary.Op.OR.make(o, null, a, b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("OrExprA",49, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 168: // OrExprA ::= EquivExprA + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr a = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=a; + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("OrExprA",49, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 167: // Bind ::= SUM2 Declp SuperOrBar + { + Expr RESULT =null; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + List a = (List)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT = ExprQt.Op.SUM .make(o, null, a, b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Bind",3, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 166: // Bind ::= ONE2 Declp SuperOrBar + { + Expr RESULT =null; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + List a = (List)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT = ExprQt.Op.ONE .make(o, null, a, b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Bind",3, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 165: // Bind ::= LONE2 Declp SuperOrBar + { + Expr RESULT =null; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + List a = (List)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT = ExprQt.Op.LONE.make(o, null, a, b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Bind",3, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 164: // Bind ::= SOME2 Declp SuperOrBar + { + Expr RESULT =null; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + List a = (List)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT = ExprQt.Op.SOME.make(o, null, a, b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Bind",3, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 163: // Bind ::= NO2 Declp SuperOrBar + { + Expr RESULT =null; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + List a = (List)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT = ExprQt.Op.NO .make(o, null, a, b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Bind",3, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 162: // Bind ::= ALL2 Declp SuperOrBar + { + Expr RESULT =null; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + List a = (List)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT = ExprQt.Op.ALL .make(o, null, a, b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Bind",3, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 161: // Bind ::= LET Let + { + Expr RESULT =null; + int xleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int xright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr x = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT = x; + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Bind",3, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 160: // Expr ::= Bind + { + Expr RESULT =null; + int xleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int xright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr x = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT = x; + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Expr",22, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 159: // Expr ::= OrExprB + { + Expr RESULT =null; + int xleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int xright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr x = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT = x; + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Expr",22, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 158: // Expr ::= OrExprA + { + Expr RESULT =null; + int xleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int xright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr x = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT = x; + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Expr",22, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 157: // Exprp ::= Exprp COMMA Expr + { + List RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + List a = (List)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + a.add(b); RESULT=a; + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Exprp",28, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 156: // Exprp ::= Expr + { + List RESULT =null; + int xleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int xright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr x = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=new ArrayList(); RESULT.add(x); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Exprp",28, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 155: // Exprs ::= Exprp + { + List RESULT =null; + int xleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int xright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + List x = (List)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=x; + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Exprs",27, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 154: // Exprs ::= + { + List RESULT =null; + RESULT=new ArrayList(); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Exprs",27, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 153: // SuperOrBar ::= Super + { + Expr RESULT =null; + int xleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int xright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr x = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=x; + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("SuperOrBar",26, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 152: // SuperOrBar ::= BAR Expr + { + Expr RESULT =null; + int xleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int xright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr x = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=x; + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("SuperOrBar",26, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 151: // SuperP ::= SuperP Expr + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Expr a = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprBinary.Op.AND.make(null, null, a, b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("SuperP",25, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 150: // SuperP ::= Expr + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr a = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=a; + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("SuperP",25, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 149: // Super ::= LBRACE RBRACE + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos a = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Pos b = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprConstant.Op.TRUE.make(a.merge(b), 0); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Super",23, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 148: // Super ::= LBRACE SuperP RBRACE + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Pos a = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int xleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int xright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Expr x = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Pos b = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprUnary.Op.NOOP.make(a.merge(b), x); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Super",23, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 147: // SuperOpt ::= Super + { + Expr RESULT =null; + int xleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int xright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr x = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=x; + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("SuperOpt",24, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 146: // SuperOpt ::= + { + Expr RESULT =null; + RESULT=null; + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("SuperOpt",24, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 145: // Let ::= Name EQUALS Expr COMMA Let + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).right; + ExprVar a = (ExprVar)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int xleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int xright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr x = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + + nod(a); + if (a.label.indexOf('/')>=0) throw new ErrorSyntax(a.pos, "Let variable name cannot contain \'/\'"); + if (a.label.indexOf('@')>=0) throw new ErrorSyntax(a.pos, "Let variable name cannot contain \'@\'"); + RESULT = ExprLet.make(o, ExprVar.make(a.pos, a.label), b, x); + + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Let",38, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 144: // Let ::= Name EQUALS Expr SuperOrBar + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).right; + ExprVar a = (ExprVar)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int xleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int xright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr x = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + + nod(a); + if (a.label.indexOf('/')>=0) throw new ErrorSyntax(a.pos, "Let variable name cannot contain \'/\'"); + if (a.label.indexOf('@')>=0) throw new ErrorSyntax(a.pos, "Let variable name cannot contain \'@\'"); + RESULT = ExprLet.make(o, ExprVar.make(a.pos, a.label), b, x); + + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Let",38, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 143: // Decls ::= COMMA Decls + { + List RESULT =null; + int yleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int yright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + List y = (List)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=y; + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Decls",13, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 142: // Decls ::= Declb COMMA Decls + { + List RESULT =null; + int xleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int xright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Decl x = (Decl)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int yleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int yright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + List y = (List)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=y; RESULT.add(0,x); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Decls",13, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 141: // Decls ::= Declb + { + List RESULT =null; + int xleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int xright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Decl x = (Decl)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=new ArrayList(); RESULT.add(x); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Decls",13, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 140: // Decls ::= + { + List RESULT =null; + RESULT=new ArrayList(); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Decls",13, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 139: // Declp ::= Declb + { + List RESULT =null; + int yleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int yright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Decl y = (Decl)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=new ArrayList(); RESULT.add(y); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Declp",12, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 138: // Declp ::= Declp COMMA Declb + { + List RESULT =null; + int xleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int xright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + List x = (List)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int yleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int yright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Decl y = (Decl)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=x; RESULT.add(y); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Declp",12, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 137: // Declz ::= Decla + { + List RESULT =null; + int yleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int yright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Decl y = (Decl)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=new ArrayList(); RESULT.add(y); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Declz",14, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 136: // Declz ::= Declz COMMA Decla + { + List RESULT =null; + int xleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int xright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + List x = (List)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int yleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int yright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Decl y = (Decl)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=x; RESULT.add(y); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Declz",14, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 135: // Declb ::= Names EQUALS DISJ Expr + { + Decl RESULT =null; + int dleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int dright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos d = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + if (1==1) throw new ErrorSyntax(d, "Defined fields cannot be disjoint."); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Declb",11, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 134: // Declb ::= PRIVATE Names EQUALS DISJ Expr + { + Decl RESULT =null; + int dleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int dright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos d = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + if (1==1) throw new ErrorSyntax(d, "Defined fields cannot be disjoint."); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Declb",11, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 133: // Declb ::= PRIVATE DISJ Names EQUALS DISJ Expr + { + Decl RESULT =null; + int dleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int dright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos d = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + if (1==1) throw new ErrorSyntax(d, "Defined fields cannot be disjoint."); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Declb",11, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-5)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 132: // Declb ::= DISJ Names EQUALS DISJ Expr + { + Decl RESULT =null; + int dleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int dright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos d = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + if (1==1) throw new ErrorSyntax(d, "Defined fields cannot be disjoint."); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Declb",11, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 131: // Declb ::= EXH Names EQUALS DISJ Expr + { + Decl RESULT =null; + int kleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).left; + int kright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).right; + Pos k = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).value; + if (1==1) throw CompModule.hint(k, "exh"); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Declb",11, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 130: // Declb ::= PART Names EQUALS DISJ Expr + { + Decl RESULT =null; + int kleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).left; + int kright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).right; + Pos k = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).value; + if (1==1) throw CompModule.hint(k, "part"); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Declb",11, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 129: // Declb ::= Names EQUALS Expr + { + Decl RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + List a = (List)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=new Decl(null, null, null, a, ExprUnary.Op.EXACTLYOF.make(null, b)); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Declb",11, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 128: // Declb ::= PRIVATE Names EQUALS Expr + { + Decl RESULT =null; + int pleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).left; + int pright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).right; + Pos p = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).value; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + List a = (List)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=new Decl(p, null, null, a, ExprUnary.Op.EXACTLYOF.make(null, b)); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Declb",11, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 127: // Declb ::= PRIVATE DISJ Names EQUALS Expr + { + Decl RESULT =null; + int dleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).left; + int dright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).right; + Pos d = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).value; + if (1==1) throw new ErrorSyntax(d, "Defined fields cannot be disjoint."); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Declb",11, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 126: // Declb ::= DISJ Names EQUALS Expr + { + Decl RESULT =null; + int dleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).left; + int dright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).right; + Pos d = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).value; + if (1==1) throw new ErrorSyntax(d, "Defined fields cannot be disjoint."); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Declb",11, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 125: // Declb ::= EXH Names EQUALS Expr + { + Decl RESULT =null; + int kleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).left; + int kright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).right; + Pos k = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).value; + if (1==1) throw CompModule.hint(k, "exh"); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Declb",11, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 124: // Declb ::= PART Names EQUALS Expr + { + Decl RESULT =null; + int kleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).left; + int kright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).right; + Pos k = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).value; + if (1==1) throw CompModule.hint(k, "part"); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Declb",11, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 123: // Declb ::= Decla + { + Decl RESULT =null; + int xleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int xright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Decl x = (Decl)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=x; + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Declb",11, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 122: // Decla ::= Names COLON DISJ Expr + { + Decl RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).right; + List a = (List)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).value; + int dleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int dright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos d = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=new Decl(null, null, d, a, mult(b)); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Decla",10, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 121: // Decla ::= PRIVATE Names COLON DISJ Expr + { + Decl RESULT =null; + int pleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).left; + int pright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).right; + Pos p = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).value; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).right; + List a = (List)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).value; + int dleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int dright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos d = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=new Decl(p, null, d, a, mult(b)); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Decla",10, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 120: // Decla ::= PRIVATE DISJ Names COLON DISJ Expr + { + Decl RESULT =null; + int pleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-5)).left; + int pright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-5)).right; + Pos p = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-5)).value; + int kleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).left; + int kright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).right; + Pos k = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).value; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).right; + List a = (List)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).value; + int dleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int dright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos d = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=new Decl(p, k, d, a, mult(b)); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Decla",10, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-5)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 119: // Decla ::= DISJ Names COLON DISJ Expr + { + Decl RESULT =null; + int kleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).left; + int kright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).right; + Pos k = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).value; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).right; + List a = (List)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).value; + int dleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int dright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos d = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=new Decl(null, k, d, a, mult(b)); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Decla",10, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 118: // Decla ::= EXH Names COLON DISJ Expr + { + Decl RESULT =null; + int kleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).left; + int kright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).right; + Pos k = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).value; + if (1==1) throw CompModule.hint(k, "exh"); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Decla",10, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 117: // Decla ::= PART Names COLON DISJ Expr + { + Decl RESULT =null; + int kleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).left; + int kright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).right; + Pos k = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).value; + if (1==1) throw CompModule.hint(k, "part"); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Decla",10, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 116: // Decla ::= Names COLON Expr + { + Decl RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + List a = (List)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=new Decl(null, null, null, a, mult(b)); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Decla",10, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 115: // Decla ::= PRIVATE Names COLON Expr + { + Decl RESULT =null; + int pleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).left; + int pright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).right; + Pos p = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).value; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + List a = (List)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=new Decl(p, null, null, a, mult(b)); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Decla",10, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 114: // Decla ::= PRIVATE DISJ Names COLON Expr + { + Decl RESULT =null; + int pleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).left; + int pright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).right; + Pos p = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).value; + int kleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).left; + int kright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).right; + Pos k = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).value; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + List a = (List)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=new Decl(p, k, null, a, mult(b)); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Decla",10, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 113: // Decla ::= DISJ Names COLON Expr + { + Decl RESULT =null; + int kleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).left; + int kright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).right; + Pos k = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).value; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + List a = (List)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr b = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=new Decl(null, k, null, a, mult(b)); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Decla",10, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 112: // Decla ::= EXH Names COLON Expr + { + Decl RESULT =null; + int kleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).left; + int kright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).right; + Pos k = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).value; + if (1==1) throw CompModule.hint(k, "exh"); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Decla",10, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 111: // Decla ::= PART Names COLON Expr + { + Decl RESULT =null; + int kleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).left; + int kright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).right; + Pos k = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).value; + if (1==1) throw CompModule.hint(k, "part"); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Decla",10, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 110: // Namex ::= Namex COMMA EXACTLY Name + { + List RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).right; + List a = (List)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + ExprVar b = (ExprVar)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + nod(b); a.add(null); a.add(b); RESULT=a; + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Namex",44, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 109: // Namex ::= Namex COMMA Name + { + List RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + List a = (List)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + ExprVar b = (ExprVar)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + nod(b); a.add(b); RESULT=a; + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Namex",44, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 108: // Namex ::= EXACTLY Name + { + List RESULT =null; + int xleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int xright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + ExprVar x = (ExprVar)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + nod(x); RESULT=new ArrayList(); RESULT.add(null); RESULT.add(x); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Namex",44, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 107: // Namex ::= Name + { + List RESULT =null; + int xleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int xright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + ExprVar x = (ExprVar)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + nod(x); RESULT=new ArrayList(); RESULT.add(x); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Namex",44, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 106: // Names ::= Names COMMA Name + { + List RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + List a = (List)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + ExprVar b = (ExprVar)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + nod(b); a.add(b); RESULT=a; + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Names",43, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 105: // Names ::= Name + { + List RESULT =null; + int xleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int xright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + ExprVar x = (ExprVar)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + nod(x); RESULT=new ArrayList(); RESULT.add(x); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Names",43, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 104: // NameHelper ::= NameHelper SLASH ID + { + ExprVar RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + ExprVar a = (ExprVar)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + ExprVar b = (ExprVar)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprVar.make(a.pos.merge(b.pos), a.label+"/"+b.label); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("NameHelper",42, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 103: // NameHelper ::= ID + { + ExprVar RESULT =null; + int xleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int xright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + ExprVar x = (ExprVar)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=x; + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("NameHelper",42, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 102: // Name ::= SEQ SLASH NameHelper + { + ExprVar RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Pos a = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + ExprVar b = (ExprVar)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprVar.make(a.merge(b.pos), "seq/"+b.label); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Name",41, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 101: // Name ::= THIS SLASH NameHelper + { + ExprVar RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Pos a = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + ExprVar b = (ExprVar)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprVar.make(a.merge(b.pos), "this/"+b.label); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Name",41, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 100: // Name ::= NameHelper + { + ExprVar RESULT =null; + int xleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int xright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + ExprVar x = (ExprVar)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=x; + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Name",41, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 99: // SigRefu ::= SigRefu PLUS SigRef + { + List RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + List a = (List)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + ExprVar b = (ExprVar)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + a.add(b); RESULT=a; + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("SigRefu",67, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 98: // SigRefu ::= SigRef + { + List RESULT =null; + int xleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int xright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + ExprVar x = (ExprVar)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=new ArrayList(); RESULT.add(x); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("SigRefu",67, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 97: // SigRefp ::= SigRefp COMMA SigRef + { + List RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + List a = (List)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + ExprVar b = (ExprVar)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + a.add(b); RESULT=a; + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("SigRefp",65, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 96: // SigRefp ::= SigRef + { + List RESULT =null; + int xleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int xright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + ExprVar x = (ExprVar)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=new ArrayList(); RESULT.add(x); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("SigRefp",65, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 95: // SigRefs ::= SigRefp + { + List RESULT =null; + int xleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int xright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + List x = (List)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=x; + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("SigRefs",66, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 94: // SigRefs ::= + { + List RESULT =null; + RESULT=new ArrayList(); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("SigRefs",66, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 93: // SigRef ::= NONE + { + ExprVar RESULT =null; + int xleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int xright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Pos x = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprVar.make(x, "none"); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("SigRef",64, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 92: // SigRef ::= SEQ SLASH SIGINT + { + ExprVar RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Pos a = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Pos b = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprVar.make(a.merge(b), "seq/Int"); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("SigRef",64, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 91: // SigRef ::= SIGINT + { + ExprVar RESULT =null; + int xleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int xright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Pos x = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprVar.make(x, "Int"); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("SigRef",64, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 90: // SigRef ::= STRING + { + ExprVar RESULT =null; + int xleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int xright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Pos x = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprVar.make(x, "String"); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("SigRef",64, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 89: // SigRef ::= UNIV + { + ExprVar RESULT =null; + int xleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int xright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Pos x = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=ExprVar.make(x, "univ"); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("SigRef",64, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 88: // SigRef ::= Name + { + ExprVar RESULT =null; + int xleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int xright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + ExprVar x = (ExprVar)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=x; + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("SigRef",64, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 87: // SigIn ::= + { + List RESULT =null; + RESULT=null; + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("SigIn",61, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 86: // SigIn ::= EQUALS SigRefu + { + List RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos a = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int xleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int xright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + List x = (List)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=x; x.add(ExprVar.make(a,"=")); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("SigIn",61, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 85: // SigIn ::= IN SigRefu + { + List RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos a = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int xleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int xright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + List x = (List)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=x; x.add(ExprVar.make(a,"in")); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("SigIn",61, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 84: // SigIn ::= EXTENDS SigRef + { + List RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos a = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int xleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int xright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + ExprVar x = (ExprVar)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=new ArrayList(2); RESULT.add(x); RESULT.add(ExprVar.make(a, "extends")); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("SigIn",61, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 83: // SigQuals ::= SigQual SigQuals + { + List RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + List a = (List)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + List b = (List)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=a; for(int i=0;i<5;i++) if (a.get(i)==null) a.set(i,b.get(i)); else if (b.get(i)!=null) throw new ErrorSyntax(b.get(i), "The same qualifer cannot be specified more than once for the same sig."); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("SigQuals",63, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 82: // SigQuals ::= SIG + { + List RESULT =null; + RESULT=new ArrayList(5); RESULT.add(null); RESULT.add(null); RESULT.add(null); RESULT.add(null); RESULT.add(null); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("SigQuals",63, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 81: // SigQual ::= PRIVATE + { + List RESULT =null; + int xleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int xright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Pos x = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=new ArrayList(5); RESULT.add(null); RESULT.add(null); RESULT.add(null); RESULT.add(null); RESULT.add(x); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("SigQual",62, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 80: // SigQual ::= SOME + { + List RESULT =null; + int xleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int xright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Pos x = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=new ArrayList(5); RESULT.add(null); RESULT.add(null); RESULT.add(null); RESULT.add(x); RESULT.add(null); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("SigQual",62, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 79: // SigQual ::= ONE + { + List RESULT =null; + int xleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int xright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Pos x = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=new ArrayList(5); RESULT.add(null); RESULT.add(null); RESULT.add(x); RESULT.add(null); RESULT.add(null); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("SigQual",62, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 78: // SigQual ::= LONE + { + List RESULT =null; + int xleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int xright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Pos x = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=new ArrayList(5); RESULT.add(null); RESULT.add(x); RESULT.add(null); RESULT.add(null); RESULT.add(null); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("SigQual",62, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 77: // SigQual ::= ABSTRACT + { + List RESULT =null; + int xleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int xright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Pos x = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=new ArrayList(5); RESULT.add(x); RESULT.add(null); RESULT.add(null); RESULT.add(null); RESULT.add(null); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("SigQual",62, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 76: // Sig ::= SigQuals Names SigIn LBRACE Decls RBRACE SuperOpt + { + Object RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-6)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-6)).right; + List a = (List)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-6)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-5)).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-5)).right; + List b = (List)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-5)).value; + int cleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).left; + int cright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).right; + List c = (List)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).value; + int dleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int dright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + List d = (List)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int eleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int eright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr e = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + + if (e==null) e = ExprConstant.Op.TRUE.make(o, 0); + ExprVar cc = (c!=null && c.size()>0) ? c.remove(c.size()-1) : null; + for(ExprVar bb:b) { + parser.alloymodule.addSig(bb.label, cc, c, d, e, + AttrType.WHERE .makenull(bb.pos.merge(e==null ? o : e.span())), + AttrType.ABSTRACT.makenull(a.get(0)), + AttrType.LONE .makenull(a.get(1)), + AttrType.ONE .makenull(a.get(2)), + AttrType.SOME .makenull(a.get(3)), + AttrType.PRIVATE .makenull(a.get(4))); + } + + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Sig",60, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-6)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 75: // Vis ::= PRIVATE + { + Pos RESULT =null; + int pleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int pright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Pos p = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=p; + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Vis",81, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 74: // Vis ::= + { + Pos RESULT =null; + RESULT=null; + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Vis",81, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 73: // Predicate ::= Vis PRED SigRef DOT Name Super + { + Object RESULT =null; + int pleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-5)).left; + int pright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-5)).right; + Pos p = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-5)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).value; + int fleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).left; + int fright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).right; + ExprVar f = (ExprVar)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).value; + int nleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int nright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + ExprVar n = (ExprVar)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int vleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int vright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr v = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + nod(n); parser.alloymodule.addFunc(o.merge(v.span()), p, n.label, f , null , null, v); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Predicate",53, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-5)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 72: // Predicate ::= Vis PRED SigRef DOT Name LBRACKET Decls RBRACKET Super + { + Object RESULT =null; + int pleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-8)).left; + int pright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-8)).right; + Pos p = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-8)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-7)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-7)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-7)).value; + int fleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-6)).left; + int fright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-6)).right; + ExprVar f = (ExprVar)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-6)).value; + int nleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).left; + int nright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).right; + ExprVar n = (ExprVar)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).value; + int dleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int dright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + List d = (List)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int vleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int vright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr v = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + nod(n); parser.alloymodule.addFunc(o.merge(v.span()), p, n.label, f , d , null, v); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Predicate",53, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-8)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 71: // Predicate ::= Vis PRED SigRef DOT Name LPAREN Decls RPAREN Super + { + Object RESULT =null; + int pleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-8)).left; + int pright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-8)).right; + Pos p = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-8)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-7)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-7)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-7)).value; + int fleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-6)).left; + int fright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-6)).right; + ExprVar f = (ExprVar)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-6)).value; + int nleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).left; + int nright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).right; + ExprVar n = (ExprVar)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).value; + int dleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int dright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + List d = (List)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int vleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int vright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr v = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + nod(n); parser.alloymodule.addFunc(o.merge(v.span()), p, n.label, f , d , null, v); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Predicate",53, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-8)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 70: // Predicate ::= Vis PRED Name Super + { + Object RESULT =null; + int pleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).left; + int pright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).right; + Pos p = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int nleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int nright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + ExprVar n = (ExprVar)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int vleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int vright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr v = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + nod(n); parser.alloymodule.addFunc(o.merge(v.span()), p, n.label, null, null , null, v); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Predicate",53, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 69: // Predicate ::= Vis PRED Name LBRACKET Decls RBRACKET Super + { + Object RESULT =null; + int pleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-6)).left; + int pright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-6)).right; + Pos p = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-6)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-5)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-5)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-5)).value; + int nleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).left; + int nright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).right; + ExprVar n = (ExprVar)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).value; + int dleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int dright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + List d = (List)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int vleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int vright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr v = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + nod(n); parser.alloymodule.addFunc(o.merge(v.span()), p, n.label, null, d , null, v); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Predicate",53, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-6)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 68: // Predicate ::= Vis PRED Name LPAREN Decls RPAREN Super + { + Object RESULT =null; + int pleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-6)).left; + int pright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-6)).right; + Pos p = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-6)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-5)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-5)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-5)).value; + int nleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).left; + int nright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).right; + ExprVar n = (ExprVar)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).value; + int dleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int dright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + List d = (List)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int vleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int vright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr v = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + nod(n); parser.alloymodule.addFunc(o.merge(v.span()), p, n.label, null, d , null, v); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Predicate",53, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-6)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 67: // Function ::= Vis FUN SigRef DOT Name COLON Expr Super + { + Object RESULT =null; + int pleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-7)).left; + int pright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-7)).right; + Pos p = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-7)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-6)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-6)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-6)).value; + int fleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-5)).left; + int fright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-5)).right; + ExprVar f = (ExprVar)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-5)).value; + int nleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).left; + int nright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).right; + ExprVar n = (ExprVar)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).value; + int rleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int rright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Expr r = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int vleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int vright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr v = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + nod(n); parser.alloymodule.addFunc(o.merge(v.span()), p, n.label, f , null , mult(r), v); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Function",29, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-7)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 66: // Function ::= Vis FUN SigRef DOT Name LBRACKET Decls RBRACKET COLON Expr Super + { + Object RESULT =null; + int pleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-10)).left; + int pright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-10)).right; + Pos p = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-10)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-9)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-9)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-9)).value; + int fleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-8)).left; + int fright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-8)).right; + ExprVar f = (ExprVar)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-8)).value; + int nleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-6)).left; + int nright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-6)).right; + ExprVar n = (ExprVar)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-6)).value; + int dleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).left; + int dright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).right; + List d = (List)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).value; + int rleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int rright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Expr r = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int vleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int vright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr v = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + nod(n); parser.alloymodule.addFunc(o.merge(v.span()), p, n.label, f , d , mult(r), v); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Function",29, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-10)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 65: // Function ::= Vis FUN SigRef DOT Name LPAREN Decls RPAREN COLON Expr Super + { + Object RESULT =null; + int pleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-10)).left; + int pright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-10)).right; + Pos p = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-10)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-9)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-9)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-9)).value; + int fleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-8)).left; + int fright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-8)).right; + ExprVar f = (ExprVar)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-8)).value; + int nleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-6)).left; + int nright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-6)).right; + ExprVar n = (ExprVar)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-6)).value; + int dleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).left; + int dright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).right; + List d = (List)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).value; + int rleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int rright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Expr r = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int vleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int vright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr v = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + nod(n); parser.alloymodule.addFunc(o.merge(v.span()), p, n.label, f , d , mult(r), v); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Function",29, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-10)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 64: // Function ::= Vis FUN Name COLON Expr Super + { + Object RESULT =null; + int pleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-5)).left; + int pright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-5)).right; + Pos p = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-5)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).value; + int nleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).left; + int nright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).right; + ExprVar n = (ExprVar)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).value; + int rleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int rright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Expr r = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int vleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int vright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr v = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + nod(n); parser.alloymodule.addFunc(o.merge(v.span()), p, n.label, null, null , mult(r), v); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Function",29, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-5)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 63: // Function ::= Vis FUN Name LBRACKET Decls RBRACKET COLON Expr Super + { + Object RESULT =null; + int pleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-8)).left; + int pright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-8)).right; + Pos p = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-8)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-7)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-7)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-7)).value; + int nleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-6)).left; + int nright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-6)).right; + ExprVar n = (ExprVar)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-6)).value; + int dleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).left; + int dright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).right; + List d = (List)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).value; + int rleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int rright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Expr r = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int vleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int vright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr v = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + nod(n); parser.alloymodule.addFunc(o.merge(v.span()), p, n.label, null, d , mult(r), v); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Function",29, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-8)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 62: // Function ::= Vis FUN Name LPAREN Decls RPAREN COLON Expr Super + { + Object RESULT =null; + int pleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-8)).left; + int pright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-8)).right; + Pos p = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-8)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-7)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-7)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-7)).value; + int nleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-6)).left; + int nright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-6)).right; + ExprVar n = (ExprVar)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-6)).value; + int dleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).left; + int dright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).right; + List d = (List)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).value; + int rleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int rright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Expr r = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int vleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int vright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr v = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + nod(n); parser.alloymodule.addFunc(o.merge(v.span()), p, n.label, null, d , mult(r), v); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Function",29, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-8)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 61: // MacroBody ::= EQUALS Expr + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr a = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=a; + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("MacroBody",40, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 60: // MacroBody ::= Super + { + Expr RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr a = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=a; + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("MacroBody",40, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 59: // Macro ::= Vis LET Name MacroBody + { + Object RESULT =null; + int pleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).left; + int pright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).right; + Pos p = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int nleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int nright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + ExprVar n = (ExprVar)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int vleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int vright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr v = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + nod(n); parser.alloymodule.addMacro(o.merge(v.span()), p, n.label, null , v); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Macro",39, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 58: // Macro ::= Vis LET Name LBRACKET RBRACKET MacroBody + { + Object RESULT =null; + int pleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-5)).left; + int pright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-5)).right; + Pos p = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-5)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).value; + int nleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).left; + int nright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).right; + ExprVar n = (ExprVar)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).value; + int vleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int vright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr v = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + nod(n); parser.alloymodule.addMacro(o.merge(v.span()), p, n.label, null , v); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Macro",39, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-5)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 57: // Macro ::= Vis LET Name LBRACKET Names RBRACKET MacroBody + { + Object RESULT =null; + int pleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-6)).left; + int pright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-6)).right; + Pos p = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-6)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-5)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-5)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-5)).value; + int nleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).left; + int nright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).right; + ExprVar n = (ExprVar)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).value; + int dleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int dright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + List d = (List)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int vleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int vright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr v = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + nod(n); parser.alloymodule.addMacro(o.merge(v.span()), p, n.label, d , v); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Macro",39, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-6)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 56: // Macro ::= Vis LET Name LPAREN RPAREN MacroBody + { + Object RESULT =null; + int pleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-5)).left; + int pright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-5)).right; + Pos p = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-5)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).value; + int nleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).left; + int nright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).right; + ExprVar n = (ExprVar)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).value; + int vleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int vright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr v = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + nod(n); parser.alloymodule.addMacro(o.merge(v.span()), p, n.label, null , v); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Macro",39, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-5)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 55: // Macro ::= Vis LET Name LPAREN Names RPAREN MacroBody + { + Object RESULT =null; + int pleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-6)).left; + int pright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-6)).right; + Pos p = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-6)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-5)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-5)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-5)).value; + int nleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).left; + int nright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).right; + ExprVar n = (ExprVar)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).value; + int dleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int dright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + List d = (List)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int vleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int vright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr v = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + nod(n); parser.alloymodule.addMacro(o.merge(v.span()), p, n.label, d , v); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Macro",39, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-6)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 54: // TypeNumber ::= NUMBER COLON NUMBER + { + CommandScope RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + ExprConstant a = (ExprConstant)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int ileft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int iright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + ExprConstant i = (ExprConstant)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + if (!Version.experimental) throw new ErrorSyntax(a.pos, "Syntax error here."); RESULT = new CommandScope(a.pos.merge(i.pos), Sig.NONE, false, a.num, Integer.MAX_VALUE, i.num); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("TypeNumber",70, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 53: // TypeNumber ::= NUMBER DOT DOT NUMBER COLON NUMBER + { + CommandScope RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-5)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-5)).right; + ExprConstant a = (ExprConstant)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-5)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + ExprConstant b = (ExprConstant)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int ileft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int iright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + ExprConstant i = (ExprConstant)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + if (!Version.experimental) throw new ErrorSyntax(a.pos, "Syntax error here."); RESULT = new CommandScope(a.pos.merge(i.pos), Sig.NONE, false, a.num, b.num, i.num); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("TypeNumber",70, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-5)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 52: // TypeNumber ::= NUMBER DOT DOT NUMBER + { + CommandScope RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).right; + ExprConstant a = (ExprConstant)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + ExprConstant b = (ExprConstant)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + if (!Version.experimental) throw new ErrorSyntax(a.pos, "Syntax error here."); RESULT = new CommandScope(a.pos.merge(b.pos), Sig.NONE, false, a.num, b.num, 1 ); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("TypeNumber",70, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 51: // TypeNumber ::= NUMBER + { + CommandScope RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + ExprConstant a = (ExprConstant)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT = new CommandScope(a.pos , Sig.NONE, false, a.num, a.num, 1 ); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("TypeNumber",70, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 50: // TypeNumber ::= EXACTLY NUMBER COLON NUMBER + { + CommandScope RESULT =null; + int eleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).left; + int eright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).right; + Pos e = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).value; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + ExprConstant a = (ExprConstant)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int ileft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int iright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + ExprConstant i = (ExprConstant)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + if (!Version.experimental) throw new ErrorSyntax(a.pos, "Syntax error here."); RESULT = new CommandScope( e.merge(i.pos), Sig.NONE, true, a.num, Integer.MAX_VALUE, i.num); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("TypeNumber",70, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 49: // TypeNumber ::= EXACTLY NUMBER DOT DOT NUMBER COLON NUMBER + { + CommandScope RESULT =null; + int eleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-6)).left; + int eright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-6)).right; + Pos e = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-6)).value; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-5)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-5)).right; + ExprConstant a = (ExprConstant)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-5)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + ExprConstant b = (ExprConstant)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int ileft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int iright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + ExprConstant i = (ExprConstant)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + if (!Version.experimental) throw new ErrorSyntax(a.pos, "Syntax error here."); RESULT = new CommandScope( e.merge(i.pos), Sig.NONE, true, a.num, b.num, i.num); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("TypeNumber",70, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-6)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 48: // TypeNumber ::= EXACTLY NUMBER DOT DOT NUMBER + { + CommandScope RESULT =null; + int eleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).left; + int eright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).right; + Pos e = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).value; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).right; + ExprConstant a = (ExprConstant)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + ExprConstant b = (ExprConstant)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + if (!Version.experimental) throw new ErrorSyntax(a.pos, "Syntax error here."); RESULT = new CommandScope( e.merge(b.pos), Sig.NONE, true, a.num, b.num, 1 ); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("TypeNumber",70, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 47: // TypeNumber ::= EXACTLY NUMBER + { + CommandScope RESULT =null; + int eleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int eright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos e = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + ExprConstant a = (ExprConstant)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT = new CommandScope( e.merge(a.pos), Sig.NONE, true, a.num, a.num, 1 ); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("TypeNumber",70, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 46: // Typescope ::= TypeNumber NONE + { + CommandScope RESULT =null; + int eleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int eright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + CommandScope e = (CommandScope)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int fleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int fright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Pos f = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + if (1==1) throw new ErrorSyntax(e.pos.merge(f), "You cannot set a scope on none."); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Typescope",71, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 45: // Typescope ::= TypeNumber STRING + { + CommandScope RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + CommandScope a = (CommandScope)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Pos b = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT = new CommandScope(a.pos.merge(b), new PrimSig("String", AttrType.WHERE.make(a.pos.merge(b))), a.isExact, a.startingScope, a.endingScope, a.increment); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Typescope",71, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 44: // Typescope ::= TypeNumber UNIV + { + CommandScope RESULT =null; + int eleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int eright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + CommandScope e = (CommandScope)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int fleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int fright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Pos f = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + if (1==1) throw new ErrorSyntax(e.pos.merge(f), "You cannot set a scope on univ."); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Typescope",71, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 43: // Typescope ::= TypeNumber SEQ + { + CommandScope RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + CommandScope a = (CommandScope)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Pos b = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + + Pos p = a.pos.merge(b); + if (a.endingScope>a.startingScope) throw new ErrorSyntax(p, "Cannot specify a growing scope for \"seq\""); + if (a.isExact) throw new ErrorSyntax(p, "The exactly keyword is redundant here since the number of sequence index has to be exact."); + RESULT = new CommandScope(p, new PrimSig("seq", AttrType.WHERE.make(p)), a.isExact, a.startingScope, a.startingScope, 1); + + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Typescope",71, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 42: // Typescope ::= TypeNumber INT + { + CommandScope RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + CommandScope a = (CommandScope)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Pos b = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + + Pos p = a.pos.merge(b); + if (a.endingScope>a.startingScope) throw new ErrorSyntax(p, "Cannot specify a growing scope for \"Int\""); + if (a.isExact) throw new ErrorSyntax(p, "The exactly keyword is redundant here since the integer bitwidth must be exact."); + RESULT = new CommandScope(p, new PrimSig("int", AttrType.WHERE.make(p)), a.isExact, a.startingScope, a.startingScope, 1); + + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Typescope",71, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 41: // Typescope ::= TypeNumber SIGINT + { + CommandScope RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + CommandScope a = (CommandScope)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Pos b = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + + Pos p = a.pos.merge(b); + if (a.endingScope>a.startingScope) throw new ErrorSyntax(p, "Cannot specify a growing scope for \"Int\""); + if (a.isExact) throw new ErrorSyntax(p, "The exactly keyword is redundant here since the integer bitwidth must be exact."); + RESULT = new CommandScope(p, new PrimSig("int", AttrType.WHERE.make(p)), a.isExact, a.startingScope, a.startingScope, 1); + + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Typescope",71, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 40: // Typescope ::= TypeNumber Name + { + CommandScope RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + CommandScope a = (CommandScope)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + ExprVar b = (ExprVar)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + + nod(b); + RESULT = new CommandScope(a.pos.merge(b.pos), new PrimSig(b.label, AttrType.WHERE.make(a.pos.merge(b.pos))), a.isExact, a.startingScope, a.endingScope, a.increment); + + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Typescope",71, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 39: // Typescopes ::= Typescopes COMMA Typescope + { + List RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + List a = (List)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + CommandScope b = (CommandScope)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=a; a.add(b); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Typescopes",72, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 38: // Typescopes ::= Typescope + { + List RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + CommandScope a = (CommandScope)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=new ArrayList(); RESULT.add(a); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Typescopes",72, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 37: // Scope ::= + { + List RESULT =null; + RESULT=new ArrayList(); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Scope",59, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 36: // Scope ::= FOR Typescopes + { + List RESULT =null; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + List b = (List)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=b; + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Scope",59, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 35: // Scope ::= FOR NUMBER BUT Typescopes + { + List RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + ExprConstant a = (ExprConstant)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + List b = (List)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=b; b.add(new CommandScope(a.pos, new PrimSig("univ", AttrType.WHERE.make(a.pos)), true, a.num, a.num, 1)); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Scope",59, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 34: // Scope ::= FOR NUMBER + { + List RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + ExprConstant a = (ExprConstant)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=new ArrayList(); RESULT.add(new CommandScope(a.pos, new PrimSig("univ", AttrType.WHERE.make(a.pos)), true, a.num, a.num, 1)); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Scope",59, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 33: // Expects ::= EXPECT NUMBER + { + ExprConstant RESULT =null; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + ExprConstant a = (ExprConstant)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT=a; + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Expects",21, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 32: // Expects ::= + { + ExprConstant RESULT =null; + RESULT=null; + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Expects",21, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 31: // Command ::= Command IMPLIES CommandPrefix Name Scope Expects + { + Object RESULT =null; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).right; + ExprVar o = (ExprVar)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).value; + int nleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int nright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + ExprVar n = (ExprVar)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int sleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int sright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + List s = (List)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int cleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int cright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + ExprConstant c = (ExprConstant)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + c(true ,o,null,n ,null,s,c); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Command",8, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-5)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 30: // Command ::= Command IMPLIES CommandPrefix Name Name Scope Expects + { + Object RESULT =null; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).right; + ExprVar o = (ExprVar)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).value; + int xleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).left; + int xright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).right; + ExprVar x = (ExprVar)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).value; + int nleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int nright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + ExprVar n = (ExprVar)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int sleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int sright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + List s = (List)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int cleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int cright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + ExprConstant c = (ExprConstant)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + c(true ,o,x ,n ,null,s,c); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Command",8, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-6)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 29: // Command ::= CommandPrefix Name Scope Expects + { + Object RESULT =null; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).right; + ExprVar o = (ExprVar)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).value; + int nleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int nright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + ExprVar n = (ExprVar)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int sleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int sright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + List s = (List)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int cleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int cright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + ExprConstant c = (ExprConstant)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + c(false,o,null,n ,null,s,c); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Command",8, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 28: // Command ::= CommandPrefix Name Name Scope Expects + { + Object RESULT =null; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).right; + ExprVar o = (ExprVar)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).value; + int xleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).left; + int xright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).right; + ExprVar x = (ExprVar)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).value; + int nleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int nright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + ExprVar n = (ExprVar)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int sleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int sright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + List s = (List)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int cleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int cright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + ExprConstant c = (ExprConstant)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + c(false,o,x ,n ,null,s,c); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Command",8, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 27: // Command ::= Command IMPLIES CommandPrefix Super Scope Expects + { + Object RESULT =null; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).right; + ExprVar o = (ExprVar)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).value; + int eleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int eright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Expr e = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int sleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int sright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + List s = (List)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int cleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int cright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + ExprConstant c = (ExprConstant)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + c(true ,o,null,null,e ,s,c); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Command",8, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-5)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 26: // Command ::= Command IMPLIES CommandPrefix Name Super Scope Expects + { + Object RESULT =null; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).right; + ExprVar o = (ExprVar)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).value; + int xleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).left; + int xright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).right; + ExprVar x = (ExprVar)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).value; + int eleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int eright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Expr e = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int sleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int sright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + List s = (List)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int cleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int cright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + ExprConstant c = (ExprConstant)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + c(true ,o,x ,null,e ,s,c); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Command",8, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-6)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 25: // Command ::= CommandPrefix Super Scope Expects + { + Object RESULT =null; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).right; + ExprVar o = (ExprVar)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).value; + int eleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int eright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Expr e = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int sleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int sright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + List s = (List)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int cleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int cright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + ExprConstant c = (ExprConstant)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + c(false,o,null,null,e ,s,c); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Command",8, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 24: // Command ::= CommandPrefix Name Super Scope Expects + { + Object RESULT =null; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).right; + ExprVar o = (ExprVar)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).value; + int xleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).left; + int xright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).right; + ExprVar x = (ExprVar)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).value; + int eleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int eright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Expr e = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int sleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int sright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + List s = (List)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int cleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int cright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + ExprConstant c = (ExprConstant)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + c(false,o,x ,null,e ,s,c); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Command",8, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 23: // CommandPrefix ::= RUN + { + ExprVar RESULT =null; + int rleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int rright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Pos r = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT = ExprVar.make(r, "r"); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("CommandPrefix",9, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 22: // CommandPrefix ::= CHECK + { + ExprVar RESULT =null; + int cleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int cright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Pos c = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + RESULT = ExprVar.make(c, "c"); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("CommandPrefix",9, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 21: // Spec ::= + { + Object RESULT =null; + + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Spec",69, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 20: // Spec ::= Spec Command + { + Object RESULT =null; + + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Spec",69, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 19: // Spec ::= Spec Macro + { + Object RESULT =null; + + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Spec",69, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 18: // Spec ::= Spec Predicate + { + Object RESULT =null; + + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Spec",69, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 17: // Spec ::= Spec Function + { + Object RESULT =null; + + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Spec",69, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 16: // Spec ::= Spec Sig + { + Object RESULT =null; + + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Spec",69, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 15: // Spec ::= Spec ASSERT STR Super + { + Object RESULT =null; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int nleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int nright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + ExprConstant n = (ExprConstant)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int eleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int eright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr e = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + parser.alloymodule.addAssertion (o , n.string , e); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Spec",69, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 14: // Spec ::= Spec ASSERT Name Super + { + Object RESULT =null; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int nleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int nright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + ExprVar n = (ExprVar)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int eleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int eright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr e = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + nod(n); parser.alloymodule.addAssertion (o , n.label , e); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Spec",69, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 13: // Spec ::= Spec ASSERT Super + { + Object RESULT =null; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int eleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int eright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr e = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + parser.alloymodule.addAssertion (o , "" , e); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Spec",69, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 12: // Spec ::= Spec FACT STR Super + { + Object RESULT =null; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int nleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int nright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + ExprConstant n = (ExprConstant)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int eleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int eright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr e = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + parser.alloymodule.addFact (o , n.string , e); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Spec",69, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 11: // Spec ::= Spec FACT Name Super + { + Object RESULT =null; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int nleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int nright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + ExprVar n = (ExprVar)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int eleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int eright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr e = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + nod(n); parser.alloymodule.addFact (o , n.label , e); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Spec",69, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 10: // Spec ::= Spec FACT Super + { + Object RESULT =null; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int eleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int eright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Expr e = (Expr)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + parser.alloymodule.addFact (o , "" , e); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Spec",69, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 9: // Spec ::= Spec Vis ENUM Name LBRACE RBRACE + { + Object RESULT =null; + int pleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).left; + int pright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).right; + Pos p = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).value; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + ExprVar a = (ExprVar)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int cleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int cright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Pos c = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + nod(a); parser.alloymodule.addEnum(o.merge(c), p, a, null, c); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Spec",69, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-5)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 8: // Spec ::= Spec Vis ENUM Name LBRACE Names RBRACE + { + Object RESULT =null; + int pleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-5)).left; + int pright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-5)).right; + Pos p = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-5)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).value; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).right; + ExprVar a = (ExprVar)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).value; + int nleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int nright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + List n = (List)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int cleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int cright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Pos c = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + nod(a); parser.alloymodule.addEnum(o.merge(c), p, a, n, c); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Spec",69, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-6)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 7: // Spec ::= Spec Vis OPEN Name LBRACKET SigRefs RBRACKET AS Name + { + Object RESULT =null; + int pleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-7)).left; + int pright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-7)).right; + Pos p = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-7)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-6)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-6)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-6)).value; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-5)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-5)).right; + ExprVar a = (ExprVar)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-5)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).right; + List b = (List)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).value; + int cleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int cright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + ExprVar c = (ExprVar)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + nod(a); nod(c); parser.alloymodule.addOpen(o.merge(c.pos), p, a, b, c); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Spec",69, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-8)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 6: // Spec ::= Spec Vis OPEN Name LBRACKET SigRefs RBRACKET + { + Object RESULT =null; + int pleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-5)).left; + int pright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-5)).right; + Pos p = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-5)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).value; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).right; + ExprVar a = (ExprVar)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + List b = (List)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int cleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int cright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Pos c = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + nod(a); parser.alloymodule.addOpen(o.merge(c), p, a, b, null); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Spec",69, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-6)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 5: // Spec ::= Spec Vis OPEN Name AS Name + { + Object RESULT =null; + int pleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).left; + int pright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).right; + Pos p = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).value; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + ExprVar a = (ExprVar)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int cleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int cright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + ExprVar c = (ExprVar)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + nod(a); nod(c); parser.alloymodule.addOpen(o.merge(c.pos), p, a, null, c); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Spec",69, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-5)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 4: // Spec ::= Spec Vis OPEN Name + { + Object RESULT =null; + int pleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).left; + int pright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).right; + Pos p = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)).value; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int aleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int aright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + ExprVar a = (ExprVar)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + nod(a); parser.alloymodule.addOpen(o.merge(a.pos), p, a, null, null); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Spec",69, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 3: // Spec ::= Spec MODULE Name LBRACKET Namex RBRACKET + { + Object RESULT =null; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-4)).value; + int nleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).left; + int nright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).right; + ExprVar n = (ExprVar)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-3)).value; + int bleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int bright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + List b = (List)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int rleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int rright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + Pos r = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + nod(n); nod(b); parser.alloymodule.addModelName(o.merge(r) , n.label , b ); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Spec",69, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-5)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 2: // Spec ::= Spec MODULE Name + { + Object RESULT =null; + int oleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int oright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Pos o = (Pos)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + int nleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).left; + int nright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()).right; + ExprVar n = (ExprVar)((java_cup.runtime.Symbol) CUP$CompParser$stack.peek()).value; + nod(n); parser.alloymodule.addModelName(o.merge(n.pos) , n.label , new ArrayList()); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("Spec",69, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-2)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 1: // $START ::= File EOF + { + Object RESULT =null; + int start_valleft = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).left; + int start_valright = ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).right; + Object start_val = (Object)((java_cup.runtime.Symbol) CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)).value; + RESULT = start_val; + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("$START",0, ((java_cup.runtime.Symbol)CUP$CompParser$stack.elementAt(CUP$CompParser$top-1)), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + /* ACCEPT */ + CUP$CompParser$parser.done_parsing(); + return CUP$CompParser$result; + + /*. . . . . . . . . . . . . . . . . . . .*/ + case 0: // File ::= Spec + { + Object RESULT =null; + parser.alloymodule.doneParsing(); + CUP$CompParser$result = parser.getSymbolFactory().newSymbol("File",68, ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), ((java_cup.runtime.Symbol)CUP$CompParser$stack.peek()), RESULT); + } + return CUP$CompParser$result; + + /* . . . . . .*/ + default: + throw new Exception( + "Invalid action number found in internal parse table"); + + } + } +} + diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/parser/CompSym.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/parser/CompSym.java new file mode 100644 index 00000000..243199db --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/parser/CompSym.java @@ -0,0 +1,129 @@ + +//---------------------------------------------------- +// The following code was generated by CUP v0.11a beta 20060608 +// Tue Nov 08 16:09:13 EST 2011 +//---------------------------------------------------- + +package edu.mit.csail.sdg.alloy4compiler.parser; + +/** CUP generated class containing symbol constants. */ +public class CompSym { + /* terminals */ + public static final int AT = 34; + public static final int AS = 32; + public static final int ANY_ARROW_SOME = 3; + public static final int GT = 54; + public static final int ARROW = 2; + public static final int NOTLTE = 81; + public static final int INTSUB = 19; + public static final int ONE_ARROW_ANY = 10; + public static final int ONE2 = 82; + public static final int ENUM = 45; + public static final int IDEN = 57; + public static final int SIG = 101; + public static final int EXH = 48; + public static final int INTMIN = 23; + public static final int COMMA = 40; + public static final int SUM = 109; + public static final int RBRACE = 92; + public static final int RPAREN = 94; + public static final int LONE_ARROW_LONE = 17; + public static final int RUN = 95; + public static final int NOTGTE = 78; + public static final int ALL = 28; + public static final int LT = 68; + public static final int INTMUL = 20; + public static final int LBRACE = 62; + public static final int SOME_ARROW_ANY = 6; + public static final int LPAREN = 67; + public static final int SHR = 99; + public static final int NOT = 75; + public static final int TILDE = 111; + public static final int PART = 86; + public static final int SHL = 98; + public static final int NOTEQUALS = 76; + public static final int STR = 115; + public static final int SHA = 100; + public static final int LONE_ARROW_ONE = 16; + public static final int SOME_ARROW_LONE = 9; + public static final int INTADD = 18; + public static final int INTDIV = 21; + public static final int INTMAX = 24; + public static final int NUMBER = 114; + public static final int ABSTRACT = 27; + public static final int INTNEXT = 25; + public static final int SLASH = 103; + public static final int NO2 = 72; + public static final int TOTALORDER = 26; + public static final int ONE_ARROW_LONE = 13; + public static final int NOTIN = 79; + public static final int UNIV = 112; + public static final int PLUS = 87; + public static final int FACT = 51; + public static final int LONE = 66; + public static final int ONE = 83; + public static final int MODULE = 71; + public static final int EXTENDS = 50; + public static final int DOMAIN = 42; + public static final int EXACTLY = 47; + public static final int NONE = 74; + public static final int SOME2 = 104; + public static final int FOR = 52; + public static final int STAR = 106; + public static final int LONE_ARROW_SOME = 15; + public static final int ELSE = 44; + public static final int FUN = 53; + public static final int DOT = 43; + public static final int AMPERSAND = 30; + public static final int INT = 61; + public static final int LTE = 69; + public static final int SIGINT = 102; + public static final int DISJ = 41; + public static final int EOF = 0; + public static final int THIS = 110; + public static final int INTREM = 22; + public static final int ANY_ARROW_LONE = 5; + public static final int BUT = 36; + public static final int LONE_ARROW_ANY = 14; + public static final int ALL2 = 29; + public static final int MINUS = 70; + public static final int SOME_ARROW_SOME = 7; + public static final int SUM2 = 108; + public static final int IN = 60; + public static final int OR = 85; + public static final int SET = 97; + public static final int error = 1; + public static final int NOTGT = 77; + public static final int SEQ = 96; + public static final int GTE = 55; + public static final int ID = 113; + public static final int ONE_ARROW_SOME = 11; + public static final int ONE_ARROW_ONE = 12; + public static final int COLON = 39; + public static final int CHECK = 38; + public static final int ASSERT = 33; + public static final int SOME = 105; + public static final int LONE2 = 65; + public static final int RBRACKET = 93; + public static final int CARET = 37; + public static final int EXPECT = 49; + public static final int PLUSPLUS = 88; + public static final int STRING = 107; + public static final int RANGE = 91; + public static final int NO = 73; + public static final int SOME_ARROW_ONE = 8; + public static final int AND = 31; + public static final int ANY_ARROW_ONE = 4; + public static final int PRIVATE = 90; + public static final int OPEN = 84; + public static final int BAR = 35; + public static final int NOTLT = 80; + public static final int IMPLIES = 59; + public static final int LBRACKET = 63; + public static final int PRED = 89; + public static final int LET = 64; + public static final int IFF = 58; + public static final int EQUALS = 46; + public static final int HASH = 56; +} + diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/parser/CompUtil.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/parser/CompUtil.java new file mode 100644 index 00000000..6dd5d5a8 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/parser/CompUtil.java @@ -0,0 +1,263 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4compiler.parser; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import edu.mit.csail.sdg.alloy4.A4Reporter; +import edu.mit.csail.sdg.alloy4.ConstList; +import edu.mit.csail.sdg.alloy4.Err; +import edu.mit.csail.sdg.alloy4.ErrorFatal; +import edu.mit.csail.sdg.alloy4.ErrorSyntax; +import edu.mit.csail.sdg.alloy4.Pos; +import edu.mit.csail.sdg.alloy4.Util; +import edu.mit.csail.sdg.alloy4compiler.ast.Command; +import edu.mit.csail.sdg.alloy4compiler.ast.Expr; +import edu.mit.csail.sdg.alloy4compiler.ast.Module; +import edu.mit.csail.sdg.alloy4compiler.parser.CompModule.Open; + +/** This class provides convenience methods for calling the parser and the compiler. */ + +public final class CompUtil { + + /** Constructor is private, since this class never needs to be instantiated. */ + private CompUtil() { } + + //=============================================================================================================// + + /** Go up the directory hierachy 0 or more times. + *
For example, on a UNIX machine, goUp("/home/abc/def",1) will return "/home/abc" + *
For example, on a UNIX machine, goUp("/home/abc/def",2) will return "/home" + * @param filepath - this must be an absolute path + * @param numberOfSteps - the number of steps to go up + */ + private static String up(String filepath, int numberOfSteps) { + while(numberOfSteps > 0) { + numberOfSteps--; + int i=filepath.lastIndexOf(File.separatorChar); + if (i<=0) return ""; + filepath=filepath.substring(0,i); + } + return filepath; + } + + //=============================================================================================================// + + /** Given the name of a module, and the filename for that module, compute the filename for another module + * @param moduleA - must be a legal Alloy modulepath (eg. name) (eg. name/name/name) (must not start or end in '/') + * @param fileA - the filename corresponding to moduleA + * @param moduleB - must be a legal Alloy modulepath (eg. name) (eg. name/name/name) (must not start or end in '/') + * @return the filename corresponding to moduleB + */ + private static String computeModulePath(String moduleA, String fileA, String moduleB) { + fileA=Util.canon(fileA); // Make sure it's a canonical absolute path + if (moduleA.length()==0) moduleA="anything"; // Harmonizes the boundary case + while(moduleA.length()>0 && moduleB.length()>0) { + int a=moduleA.indexOf('/'), b=moduleB.indexOf('/'); + String headOfA = (a>=0) ? moduleA.substring(0,a) : moduleA; + String headOfB = (b>=0) ? moduleB.substring(0,b) : moduleB; + if (!headOfA.equals(headOfB) || a<0 || b<0) { + // eg. util/boolean==/home/models/util/boolean.als, then test=>/home/models/test.als" + // eg. util/boolean==/home/models/util/boolean.als, then sub/test=>/home/models/sub/test.als + // eg. main==/home/models/main.als, then test=>/home/models/test.als + // eg. main==/home/models/main.als, then sub/test=>/home/models/sub/test.als" + int numberOfSlash=0; + for(int i=0; i seenDollar, Map loaded, Map fc, Pos pos, String filename, CompModule root, String prefix, Set thispath, int initialResolution) + throws Err, FileNotFoundException, IOException { + // Add the filename into a ArrayList, so that we can detect cycles in the module import graph + // How? I'll argue that (filename appears > 1 time along a chain) <=> (infinite loop in the import graph) + // => As you descend down the chain via OPEN, if you see the same FILE twice, then + // you will go into an infinite loop (since, regardless of the instantiating parameter, + // that file will attempt to OPEN the exact same set of files. leading back to itself, etc. etc.) + // <= If there is an infinite loop, that means there is at least 1 infinite chain of OPEN (from root). + // Since the number of files is finite, at least 1 filename will be repeated. + if (thispath.contains(filename)) + throw new ErrorSyntax(pos, + "Circular dependency in module import. The file \""+(new File(filename)).getName()+"\" is imported infinitely often."); + thispath.add(filename); + // No cycle detected so far. So now we parse the file. + CompModule u = CompParser.alloy_parseStream(seenDollar, loaded, fc, root, 0, filename, prefix, initialResolution); + if (prefix.length()==0) root = u; + + // Here, we recursively open the included files + for(Open x: u.getOpens()) { + String cp=Util.canon(computeModulePath(u.getModelName(), filename, x.filename)), content=fc.get(cp); + try { + if (content==null) { content=loaded.get(cp); } + if (content==null) { content=fc.get(x.filename); if (content!=null) cp=x.filename; } + if (content==null) { content=loaded.get(x.filename); if (content!=null) cp=x.filename; } + if (content==null) { content=Util.readAll(cp); } + } catch(IOException ex1) { + try { + String newCp = (Util.jarPrefix()+"models/"+x.filename+".als").replace('/', File.separatorChar); + content = Util.readAll(newCp); + cp = newCp; + } catch(IOException ex) { + throw new ErrorSyntax(x.pos, + "This module cannot be found.\nIt is not a built-in library module, and it cannot be found at \""+cp+"\".\n"); + } + } + loaded.put(cp, content); + CompModule y = parseRecursively(seenDollar, loaded, fc, x.pos, cp, root, (prefix.length()==0 ? x.alias : prefix+"/"+x.alias), thispath, initialResolution); + x.connect(y); + } + thispath.remove(filename); // Remove this file from the CYCLE DETECTION LIST. + return u; + } + + //=============================================================================================================// + + /** Parses 1 module from the input string (without loading any subfiles) + * @return an array of 0 or more Command if no error occurred + */ + public static ConstList parseOneModule_fromString(String content) throws Err { + CompModule u = parseOneModule(content); + return ConstList.make(u.getAllCommands()); + } + + public static CompModule parseOneModule(String content) throws Err { + try { + Map fc = new LinkedHashMap(); + fc.put("", content); + return CompParser.alloy_parseStream(new ArrayList(), null, fc, null, 0, "", "", 1); + } catch(IOException ex) { + throw new ErrorFatal("IOException occurred: "+ex.getMessage(), ex); + } catch(Throwable ex) { + if (ex instanceof Err) throw (Err)ex; else throw new ErrorFatal("Unknown exception occurred: "+ex, ex); + } + } + + //=============================================================================================================// + + /** Parses 1 module from the file (without loading any subfiles) + * @return an array of 0 or more Command if no error occurred + */ + public static ConstList parseOneModule_fromFile(String filename) throws Err { + try { + CompModule u = CompParser.alloy_parseStream(new ArrayList(), null, null, null, 0, filename, "", 1); + return ConstList.make(u.getAllCommands()); + } catch(IOException ex) { + throw new ErrorFatal("IOException occurred: "+ex.getMessage(), ex); + } catch(Throwable ex) { + if (ex instanceof Err) throw (Err)ex; else throw new ErrorFatal("Unknown exception occurred: "+ex, ex); + } + } + + //=============================================================================================================// + + /** Parses then typecheck the given input String as an Alloy expression from that world + * @return the fully-typechecked expression if no error occurred + * @throws Err if world==null or if any other error occurred + */ + public static Expr parseOneExpression_fromString (Module world, String input) throws Err { + try { + if (world==null) throw new ErrorFatal("Cannot parse an expression with null world."); + return world.parseOneExpressionFromString(input); + } catch(IOException ex) { + throw new ErrorFatal("IOException occurred: "+ex.getMessage(), ex); + } catch(Throwable ex) { + if (ex instanceof Err) throw (Err)ex; else throw new ErrorFatal("Unknown exception occurred: "+ex, ex); + } + } + + //=============================================================================================================// + + /** Read everything from "file" and parse it; if it mentions submodules, open them and parse them too. + * @param rep - if nonnull, we will report compilation progress messages to it + * @param loaded - a cache of files that have been pre-fetched (can be null if there were no prefetching) + * @param filename - the main module we are parsing + * @return the root Module which contains pointers to all submodules + * @throws Err if an error occurred + *

And if loaded!=null, it will contain all the files needed for this parse, and furthermore, other entries will be deleted. + */ + public static CompModule parseEverything_fromFile (A4Reporter rep, Map loaded, String filename) throws Err { + try { + filename = Util.canon(filename); + Set thispath = new LinkedHashSet(); + if (loaded==null) loaded = new LinkedHashMap(); + Map fc = new LinkedHashMap(loaded); + loaded.clear(); + List seenDollar = new ArrayList(); + CompModule root = parseRecursively(seenDollar, loaded, fc, new Pos(filename,1,1), filename, null, "", thispath, 1); + root.seenDollar = seenDollar.size()>0; + return CompModule.resolveAll(rep==null ? A4Reporter.NOP : rep, root); + } catch(FileNotFoundException ex) { + throw new ErrorSyntax("File cannot be found.\n"+ex.getMessage(), ex); + } catch(IOException ex) { + throw new ErrorFatal("IOException occurred: "+ex.getMessage(), ex); + } catch(Throwable ex) { + if (ex instanceof Err) throw (Err)ex; else throw new ErrorFatal("Unknown exception occurred: "+ex, ex); + } + } + + /** Read everything from "file" and parse it; if it mentions submodules, open them and parse them too. + * @param rep - if nonnull, we will report compilation progress messages to it + * @param loaded - a cache of files that have been pre-fetched (can be null if there were no prefetching) + * @param filename - the main module we are parsing + * @param initialResolutionMode - use 1 for the historical behavior, and 2 for Alloy 4.2's new "universal implicit this" name resolution behavior + * @return the root CompModule which contains pointers to all submodules + * @throws Err if an error occurred + *

And if loaded!=null, it will contain all the files needed for this parse, and furthermore, other entries will be deleted. + */ + public static CompModule parseEverything_fromFile (A4Reporter rep, Map loaded, String filename, int initialResolutionMode) throws Err { + try { + filename = Util.canon(filename); + Set thispath = new LinkedHashSet(); + if (loaded==null) loaded = new LinkedHashMap(); + Map fc = new LinkedHashMap(loaded); + loaded.clear(); + List seenDollar = new ArrayList(); + CompModule root = parseRecursively(seenDollar, loaded, fc, new Pos(filename,1,1), filename, null, "", thispath, initialResolutionMode); + root.seenDollar = seenDollar.size()>0; + return CompModule.resolveAll(rep==null ? A4Reporter.NOP : rep, root); + } catch(FileNotFoundException ex) { + throw new ErrorSyntax("File cannot be found.\n"+ex.getMessage(), ex); + } catch(IOException ex) { + throw new ErrorFatal("IOException occurred: "+ex.getMessage(), ex); + } catch(Throwable ex) { + if (ex instanceof Err) throw (Err)ex; else throw new ErrorFatal("Unknown exception occurred: "+ex, ex); + } + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/parser/Macro.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/parser/Macro.java new file mode 100644 index 00000000..e2e78b8f --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/parser/Macro.java @@ -0,0 +1,126 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4compiler.parser; + +import java.util.ArrayList; +import java.util.List; +import edu.mit.csail.sdg.alloy4.ConstList; +import edu.mit.csail.sdg.alloy4.Err; +import edu.mit.csail.sdg.alloy4.ErrorFatal; +import edu.mit.csail.sdg.alloy4.ErrorType; +import edu.mit.csail.sdg.alloy4.ErrorWarning; +import edu.mit.csail.sdg.alloy4.Pos; +import edu.mit.csail.sdg.alloy4.Util; +import edu.mit.csail.sdg.alloy4compiler.ast.Browsable; +import edu.mit.csail.sdg.alloy4compiler.ast.Expr; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprBad; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprCustom; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprVar; +import edu.mit.csail.sdg.alloy4compiler.parser.CompModule.Context; + +/** Immutable; this class represents a macro. */ + +final class Macro extends ExprCustom { + + /** If nonnull, this is a private macro. */ + final Pos isPrivate; + + /** The module that defined this. */ + private final CompModule realModule; + + /** The name of the macro. */ + private final String name; + + /** The list of parameters (can be an empty list) */ + private final ConstList params; + + /** The list of arguments (can be an empty list) (must be equal or shorter than this.params) */ + private final ConstList args; + + /** The macro body. */ + private final Expr body; + + /** Construct a new Macro object. */ + private Macro(Pos pos, Pos isPrivate, CompModule realModule, String name, List params, List args, Expr body) { + super(pos, new ErrorFatal(pos, "Incomplete call on the macro \""+name+"\"")); + this.realModule = realModule; + this.isPrivate = isPrivate; + this.name = name; + this.params = ConstList.make(params); + this.args = ConstList.make(args); + this.body = body; + } + + /** Construct a new Macro object. */ + Macro(Pos pos, Pos isPrivate, CompModule realModule, String name, List params, Expr body) { + this(pos, isPrivate, realModule, name, params, null, body); + } + + Macro addArg(Expr arg) { + return new Macro(pos, isPrivate, realModule, name, params, Util.append(args,arg), body); + } + + Expr changePos(Pos pos) { + return new Macro(pos, isPrivate, realModule, name, params, args, body); + } + + /** Instantiate it. + * + * @param warnings - the list that will receive any warning we generate; can be null if we wish to ignore warnings + */ + Expr instantiate(Context cx, List warnings) throws Err { + if (cx.unrolls<=0) { + Pos p = span(); + return new ExprBad(p, toString(), new ErrorType(p, "Macro substitution too deep; possibly indicating an infinite recursion.")); + } + if (params.size() != args.size()) return this; + Context cx2 = new Context(realModule, warnings, cx.unrolls-1); + for(int n=params.size(), i=0; ierror (parser or typechecker failed)"; } + + /** {@inheritDoc} */ + @Override public List getSubnodes() { return new ArrayList(0); } + + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/parser/generate-lexer.sh b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/parser/generate-lexer.sh new file mode 100644 index 00000000..de50c662 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/parser/generate-lexer.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +java -cp $LIB_SDG/jars-external/JFlex.jar JFlex.Main --nobak -d . Alloy.lex + +sed -i 's/public java_cup.runtime.Symbol next_token() throws java.io.IOException/public java_cup.runtime.Symbol next_token() throws java.io.IOException, Err/' CompLexer.java + diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/parser/generate-parser.sh b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/parser/generate-parser.sh new file mode 100644 index 00000000..f27fddeb --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/parser/generate-parser.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +java -cp $LIB_SDG/jars-external/java-cup-11a.jar java_cup.Main \ + -package edu.mit.csail.sdg.alloy4compiler.parser \ + -parser CompParser \ + -progress -time -compact_red \ + -symbols CompSym < Alloy.cup \ No newline at end of file diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/parser/generate-parsing-trace-from-log.sh b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/parser/generate-parsing-trace-from-log.sh new file mode 100644 index 00000000..4c640e8d --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/parser/generate-parsing-trace-from-log.sh @@ -0,0 +1,48 @@ +#!/bin/bash + +## ---------------------------------------------------------------------- +## +## This script takes a parsing log and from it generates a human readable +## parsing trace, i.g. a sequence of shifts and reduces that took place. +## +## To get the parsing log just set the "debug" environment variable to +## "yes" before runngin Alloy (or add "-Ddebug=yes" to the list of JVM +## arguments) +## +## This script must be invoked from exactly the folder where it resides +## because it needs to find the CompParser.java and CompSym.java files +## in the same folder. +## +## ---------------------------------------------------------------------- + +logFile=$1 + +if [[ -z $logFile ]] +then + echo "usage: $0 " + exit +fi + +if [[ ! -f $logFile ]] +then + echo "file $logFile doesn't exist" + exit +fi + +while read line +do + if [[ ! -z $(echo $line | grep "^reduce ") ]] + then + act=$(echo $line | sed 's/reduce //') + echo "reduce" + grep "case $act:" CompParser.java + elif [[ ! -z $(echo $line | grep "^shift ") ]] + then + sym=$(echo $line | sed 's/shift //') + sgn=$(grep " = $sym;" CompSym.java | sed 's/ public static final int //' | sed 's/ = '$sym';//') + echo "shift" + echo " $sgn" + else + echo $line + fi +done < $logFile \ No newline at end of file diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/parser/package.html b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/parser/package.html new file mode 100644 index 00000000..cbf480d3 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/parser/package.html @@ -0,0 +1,5 @@ + + +This package contains the compiler + + diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/sim/SimAtom.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/sim/SimAtom.java new file mode 100644 index 00000000..23781016 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/sim/SimAtom.java @@ -0,0 +1,146 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4compiler.sim; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.IOException; +import java.lang.ref.WeakReference; +import java.util.WeakHashMap; + +/** Immutable; represents an atom. + * + *

Outside of this class, we guarantee for any SimAtom x and y, then "x.equals(y) iff x==y". + *
Even though that means "equals()" and "==" are equivalent, + *
the equals() method is much slower than "==" for SimAtom, + *
so you should always try to use "==" on SimAtom. + * + *

Thread Safety: Safe. + */ + +public final class SimAtom { + + /** This map is used to canonicalize the atoms. */ + private static final WeakHashMap> map = new WeakHashMap>(); + + /** The String label for the atom; all distinct atoms have distinct labels. */ + private String string; + + /** Construct a SimAtom; this constructor must only be called by make() since we want to canonicalize all SimAtom instances out there. */ + private SimAtom(String x) { this.string = x; } + + /** Construct a SimAtom for the given label, or if an existing SimAtom hasn't been garbage collected yet then return that instead. */ + public static SimAtom make(String label) { + synchronized(map) { + SimAtom x = new SimAtom(label); + WeakReference ans = map.get(x); + if (ans != null) { SimAtom y = ans.get(); if (y!=null) return y; } + map.put(x, new WeakReference(x)); + return x; + } + } + + /** Construct a SimAtom for the given integer, or if an existing SimAtom hasn't been garbage collected yet then return that instead. */ + public static SimAtom make(int i) { return make(String.valueOf(i)); } + + /** Construct a SimAtom for the given integer, or if an existing SimAtom hasn't been garbage collected yet then return that instead. */ + public static SimAtom make(long i) { return make(String.valueOf(i)); } + + /** Preconstructed atom representing emptystring. */ + public static final SimAtom EMPTYSTRING = make(""); + + /** Preconstructed atom representing 0. */ + public static final SimAtom ZERO = make("0"); + + /** Preconstructed atom representing 1. */ + public static final SimAtom ONE = make("1"); + + /** Write this atom as "..". */ + void write(BufferedOutputStream out) throws IOException { + byte array[] = string.getBytes("UTF-8"); + out.write('\"'); + for(int n=array.length, i=0; i0 && b<=' ') out.write(' '); + else out.write(b); + } + out.write('\"'); + } + + /** Read a "..." atom assuming the leading " has already been consumed. */ + static SimAtom read(BufferedInputStream in) throws IOException { + byte temp[] = new byte[64]; // to ensure proper detection of out-of-memory error, this number must be 2^n for some n>=0 + int n = 0; + while(true) { + int c = in.read(); + if (c<0) throw new IOException("Unexpected EOF"); + if (c=='\"') break; + if (c=='\\') { + c=in.read(); + if (c<0) throw new IOException("Unexpected EOF"); + if (c=='n') c='\n'; + } + while (n >= temp.length) { + byte temp2[] = new byte[temp.length * 2]; + System.arraycopy(temp, 0, temp2, 0, temp.length); + temp = temp2; + } + temp[n]=(byte)c; + n++; + } + return make(new String(temp, 0, n, "UTF-8")); + } + + /** If the atom starts with "-" or "0-9" then convert it into a 32-bit int (here we assume that it came from a 32-bit int) + *

+ * If the atom does not start with "-" or "0-9", then return defaultValue. + */ + public Integer toInt(Integer defaultValue) throws NumberFormatException { + int ans=0, i=0, n=string.length(); + if (n==0) return defaultValue; + // Due to Java's 2's complement arithmetic, this will successfully + // convert all integers ranging from Integer.MIN to Integer.MAX + if (string.charAt(0)=='-') i++; + if (string.charAt(i)<'0' || string.charAt(i)>'9') return defaultValue; + for(;i arguments) throws Exception; +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/sim/SimInstance.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/sim/SimInstance.java new file mode 100644 index 00000000..9a1614dd --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/sim/SimInstance.java @@ -0,0 +1,883 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4compiler.sim; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import edu.mit.csail.sdg.alloy4.Env; +import edu.mit.csail.sdg.alloy4.Err; +import edu.mit.csail.sdg.alloy4.ErrorAPI; +import edu.mit.csail.sdg.alloy4.ErrorFatal; +import edu.mit.csail.sdg.alloy4.ErrorSyntax; +import edu.mit.csail.sdg.alloy4.ErrorType; +import edu.mit.csail.sdg.alloy4.Pair; +import edu.mit.csail.sdg.alloy4.Util; +import edu.mit.csail.sdg.alloy4.ConstList.TempList; +import edu.mit.csail.sdg.alloy4compiler.ast.Decl; +import edu.mit.csail.sdg.alloy4compiler.ast.Expr; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprBinary; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprCall; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprConstant; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprHasName; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprITE; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprLet; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprList; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprQt; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprUnary; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprVar; +import edu.mit.csail.sdg.alloy4compiler.ast.Func; +import edu.mit.csail.sdg.alloy4compiler.ast.Sig; +import edu.mit.csail.sdg.alloy4compiler.ast.VisitReturn; +import edu.mit.csail.sdg.alloy4compiler.ast.Sig.Field; +import edu.mit.csail.sdg.alloy4compiler.ast.Sig.PrimSig; +import edu.mit.csail.sdg.alloy4compiler.ast.Sig.SubsetSig; +import edu.mit.csail.sdg.alloy4compiler.ast.Module; + +/** Mutable; represents an instance. */ + +public final class SimInstance extends VisitReturn { + + /** The root module associated with this instance. */ + public final Module root; + + /** This maps the current local variables (LET, QUANT, Function Param) to the actual SimTupleset/Integer/Boolean */ + private Env env = new Env(); + + /** This stores the "call backs" where you can supply Java code to efficiently handle certain functions/predicates. */ + private final Map callbacks; + + /** The exact values of each sig, field, and skolem (Note: it must not cache the value of any "defined" field, nor any builtin sig) */ + private final Map sfs = new LinkedHashMap(); + + /** If nonnull, it caches the current value of STRING (this value must be cleared or updated when you change the value of sigs/fields/vars) */ + private SimTupleset cacheSTRING = null; + + /** If nonnull, it caches the current value of UNIV (this value must be cleared or updated when you change the value of sigs) */ + private SimTupleset cacheUNIV = null; + + /** Caches parameter-less functions to a Boolean, Integer, or SimTupleset. */ + private final Map cacheForConstants = new IdentityHashMap(); + + /** This is used to detect "function recursion" (which we currently do not allow). */ + private final List current_function = new ArrayList(); + + /** The chosen bitwidth */ + private final int bitwidth; + + /** The chosen maxseq length. */ + private final int maxseq; + + /** The shiftmask based on the chosen bitwidth. */ + private final int shiftmask; + + /** The minimum allowed integer based on the chosen bitwidth. */ + private final int min; + + /** The maximum allowed integer based on the chosen bitwidth. */ + private final int max; + + /** Whether the was overflow the last time "trunc" was called */ + private boolean wasOverflow; + public boolean wasOverflow() { return wasOverflow; } + + /** Helper method that encodes the given string using UTF-8 and write to the output stream. */ + private static void write(BufferedOutputStream out, String string) throws IOException { + out.write(string.getBytes("UTF-8")); + } + + /** Write the bitwidth, maxseq, set of all atoms, and map of all sig/field/var into the given file. */ + public void write(String filename) throws IOException { + FileOutputStream fos = null; + BufferedOutputStream bos = null; + try { + fos = new FileOutputStream(filename); + bos = new BufferedOutputStream(fos); + write(bos); + bos.flush(); + bos.close(); + bos = null; + fos.close(); + fos = null; + } finally { + Util.close(bos); + Util.close(fos); + } + } + + /** Write the bitwidth, maxseq, set of all atoms, and map of all sig/field/var into the given file. */ + private void write(BufferedOutputStream out) throws IOException { + write(out, "maxseq = " + maxseq + ("\n" + "bitwidth = ") + bitwidth + "\n"); + for(Map.Entry entry: sfs.entrySet()) { + Expr e = entry.getKey(); + if (e instanceof Sig) write(out, "sig " + ((Sig)e).label + " = "); + else if (e instanceof Field) write(out, "field " + ((Field)e).sig.label + " " + ((Field)e).label + " = "); + else if (e instanceof ExprVar) write(out, "var " + ((ExprVar)e).label + " = "); + else continue; + entry.getValue().write(out); + out.write('\n'); + } + } + + /** Temporary buffer used by the parser; access to this buffer must be synchronized on SimInstance's class. */ + private static byte[] readcache = null; + + /** Helper method that read a non-negative integer followed by a line break. */ + private static int readNonNegativeIntThenLinebreak(BufferedInputStream bis) throws IOException { + int n = 0; + while(true) { + int c = bis.read(); + if (c<0) throw new IOException("Unexpected EOF"); + if (c=='\n') return n; + if (c>0 && c<=' ') continue; // skip white space + if (!(c>='0' && c<='9')) throw new IOException("Expects a DIGIT or a white space!"); + n = n*10 + (c-'0'); + } + } + + /** Helper method that read "key =" then return the key part (with leading and trailing spaces removed). + * This method can only be called after you've synchronized on SimInstance's class. + */ + private static String readkey(BufferedInputStream bis) throws IOException { + int n = 0; + while(true) { + int c = bis.read(); + if (c<0) return ""; + if (c=='=') break; + if (readcache==null) readcache = new byte[64]; // to ensure proper detection of out-of-memory error, this number must be 2^n for some n>=0 + while(n >= readcache.length) { + byte[] readcache2 = new byte[readcache.length * 2]; + System.arraycopy(readcache, 0, readcache2, 0, readcache.length); + readcache = readcache2; + } + readcache[n] = (byte)c; + n++; + } + while(n>0 && readcache[n-1]>0 && readcache[n-1]<=' ') n--; // skip trailing spaces + int i = 0; + while(i0 && readcache[i]<=' ') i++; // skip leading space + return new String(readcache, i, n-i, "UTF-8"); + } + + /** Construct a new simulation context by reading the given file. */ + public static synchronized SimInstance read(Module root, String filename, List vars) throws Err, IOException { + FileInputStream fis = null; + BufferedInputStream bis = null; + try { + fis = new FileInputStream(filename); + bis = new BufferedInputStream(fis); + // read maxseq + if (!readkey(bis).equals("maxseq")) throw new IOException("Expecting maxseq = ..."); + int maxseq = readNonNegativeIntThenLinebreak(bis); + // read bitwidth + if (!readkey(bis).equals("bitwidth")) throw new IOException("Expecting bitwidth = ..."); + int bitwidth = readNonNegativeIntThenLinebreak(bis); + // construct the SimInstance object with no atoms and no relations + SimInstance ans = new SimInstance(root, bitwidth, maxseq); + // parse all the relations + Map sfs = new HashMap(); + while(true) { + String key = readkey(bis); + if (key.length() == 0) break; // we don't expect any more data after this + sfs.put(key, SimTupleset.read(bis)); + } + // now for each user-supplied sig, if we saw its value earlier, then assign its value in the new SimInstance's sfs map + for(final Sig s: root.getAllReachableSigs()) if (!s.builtin) { + SimTupleset ts = sfs.get("sig " + s.label); + if (ts!=null) ans.sfs.put(s, ts); + for(final Field f: s.getFields()) if (!f.defined) { + ts = sfs.get("field " + s.label + " " + f.label); + if (ts!=null) ans.sfs.put(f, ts); + } + } + // now for each user-supplied var, if we saw its value earlier, then assign its value in the new SimInstance's sfs map + if (vars!=null) for(ExprVar v: vars) { + SimTupleset ts = sfs.get("var " + v.label); + if (ts!=null) ans.sfs.put(v, ts); + } + // close the files then return the answer + bis.close(); + bis = null; + fis.close(); + fis = null; + return ans; + } finally { + // free the temporary array + readcache = null; + // if an exception occurred, we'll try to close to files anyway, since open file descriptors is a scarce resource + Util.close(bis); + Util.close(fis); + } + } + + /** Construct a new simulation context with the given bitwidth and the given maximum sequence length. */ + public SimInstance(Module root, int bitwidth, int maxseq) throws Err { + if (bitwidth<0 || bitwidth>32) throw new ErrorType("Bitwidth must be between 0 and 32."); + this.root = root; + this.bitwidth = bitwidth; + this.maxseq = maxseq; + this.callbacks = new HashMap(); + if (bitwidth==32) { max=Integer.MAX_VALUE; min=Integer.MIN_VALUE; } else { max=Util.max(bitwidth); min=(0-max)-1; } + if (maxseq < 0) throw new ErrorSyntax("The maximum sequence length cannot be negative."); + if (maxseq > 0 && maxseq > max) throw new ErrorSyntax("With integer bitwidth of "+bitwidth+", you cannot have sequence length longer than "+max); + shiftmask = Util.shiftmask(bitwidth); + } + + /** Construct a deep copy of this instance (except that it shares the same root Module object as the old instance) */ + public SimInstance(SimInstance old) throws Err { + root = old.root; + bitwidth = old.bitwidth; + maxseq = old.maxseq; + min = old.min; + max = old.max; + shiftmask = old.shiftmask; + env = old.env.dup(); + cacheUNIV = old.cacheUNIV; + cacheSTRING = old.cacheSTRING; + callbacks = new HashMap(old.callbacks); + for(Map.Entry e: old.sfs.entrySet()) sfs.put(e.getKey(), e.getValue()); + } + + /** Register a callback. */ + public void addCallback(Func predicateOrFunction, SimCallback callback) { + callbacks.put(predicateOrFunction, callback); + } + + /** Returns true if the given atom is an Int atom, or String atom, or is in at least one of the sig. */ + public boolean hasAtom(SimAtom atom) { + if (atom.toString().length()>0) { + char c = atom.toString().charAt(0); + if (c=='-' || (c>='0' && c<='9') || c=='\"') return true; + } + for(Map.Entry e: sfs.entrySet()) { + if (e.getKey() instanceof PrimSig && ((PrimSig)(e.getKey())).isTopLevel() && e.getValue().has(atom)) return true; + } + return false; + } + + /** Create a fresh atom for the given sig, then return the newly created atom. + * @throws ErrorAPI if attempting to add an atom to an abstract sig with children, or a builtin sig, or a subset sig. + */ + public SimAtom makeAtom(Sig sig) throws Err { + if (sig.builtin) throw new ErrorAPI("Cannot add an atom to a builtin sig."); + if (!(sig instanceof PrimSig)) throw new ErrorAPI("Cannot add an atom to a subset sig."); + PrimSig s = (PrimSig)sig; + if (s.isAbstract!=null && !s.children().isEmpty()) throw new ErrorAPI("Cannot add an atom to an abstract parent sig."); + String label = sig.label + "$"; + if (label.startsWith("this/")) label=label.substring(5); + for(int i=0; ;i++) { + SimAtom atom = SimAtom.make(label + i); + if (hasAtom(atom)) continue; + SimTupleset add = SimTupleset.make(SimTuple.make(atom)); + if (cacheUNIV!=null) cacheUNIV = cacheUNIV.union(add); + for(; s!=null; s=s.parent) if (!s.builtin) { + SimTupleset old = sfs.get(s); + if (old==null || old.empty()) sfs.put(s, add); else if (!add.in(old)) sfs.put(s, old.union(add)); else break; + } + return atom; + } + } + + /** Delete an atom from all sigs/fields/skolem... + *

The resulting instance may or may not satisfy all facts, and should be checked for consistency. + *

Returns true if at least one sig/field/var changed its value as a result of this deletion. + * @throws ErrorAPI if attempting to delete from "Int". + */ + public boolean deleteAtom(SimAtom atom) throws Err { + if (atom.toString().length()>0) { + char c = atom.toString().charAt(0); + if (c=='-' || (c>='0' && c<='9') || c=='\"') return false; + } + boolean changed = false; + for(Map.Entry x: sfs.entrySet()) { + SimTupleset oldvalue = x.getValue(); + SimTupleset newvalue = oldvalue.removeAll(atom); + if (oldvalue.longsize() != newvalue.longsize()) { changed=true; x.setValue(newvalue); } + } + if (changed) { cacheUNIV=null; return true; } else { return false; } + } + + /** Initializes the given sig to be associated with the given unary value; should only be called at the beginning. + *

The resulting instance may or may not satisfy all facts, and should be checked for consistency. + */ + public void init(Sig sig, SimTupleset value) throws Err { + if (value==null) { sfs.remove(sig); return; } + if (value.arity()>1) throw new ErrorType("Evaluator encountered an error: sig "+sig.label+" arity must not be " + value.arity()); + if (sig.builtin) throw new ErrorAPI("Evaluator cannot prebind the builtin sig \"" + sig.label + "\""); + sfs.put(sig, value); + cacheUNIV = null; + cacheSTRING = null; + cacheForConstants.clear(); + } + + /** Initializes the given field to be associated with the given unary value; should only be called at the beginning. + *

The resulting instance may or may not satisfy all facts, and should be checked for consistency. + */ + public void init(Field field, SimTupleset value) throws Err { + if (value==null) { sfs.remove(field); return; } + if (!value.empty() && value.arity()!=field.type().arity()) throw new ErrorType("Evaluator encountered an error: field "+field.label+" arity must not be " + value.arity()); + if (field.defined) throw new ErrorAPI("Evaluator cannot prebind the value of a defined field."); + sfs.put(field, value); + cacheUNIV = null; + cacheSTRING = null; + cacheForConstants.clear(); + } + + /** Initializes the given var to be associated with the given unary value; should only be called at the beginning. + *

The resulting instance may or may not satisfy all facts, and should be checked for consistency. + */ + public void init(ExprVar var, SimTupleset value) throws Err { + if (value==null) { sfs.remove(var); return; } + if (!value.empty() && value.arity()!=var.type().arity()) throw new ErrorType("Evaluator encountered an error: skolem "+var.label+" arity must not be " + value.arity()); + sfs.put(var, value); + cacheUNIV = null; + cacheSTRING = null; + cacheForConstants.clear(); + } + + /** Truncate the given integer based on the current chosen bitwidth (as string) plus + * a flag to indicate overflow */ + private int trunc(int i) { + int ret = (i<<(32-bitwidth)) >> (32-bitwidth); + wasOverflow = ret != i; + return ret; + } + + /** Convenience method that evalutes x and casts the result to be a boolean. + * @return the boolean - if x evaluates to a boolean + * @throws ErrorFatal - if x does not evaluate to a boolean + */ + public boolean cform(Expr x) throws Err { + if (!x.errors.isEmpty()) throw x.errors.pick(); + Object y = visitThis(x); + if (y instanceof Boolean) return Boolean.TRUE.equals(y); + throw new ErrorFatal(x.span(), "This should have been a formula.\nInstead it is "+y); + } + + /** Convenience method that evalutes x and cast the result to be a int. + * @return the int - if x evaluates to an int + * @throws ErrorFatal - if x does not evaluate to an int + */ + public int cint(Expr x) throws Err { + if (!x.errors.isEmpty()) throw x.errors.pick(); + Object y = visitThis(x); + if (y instanceof Integer) return (Integer)y; + if (y instanceof SimTupleset) return ((SimTupleset) y).sum(); + throw new ErrorFatal(x.span(), "This should have been an integer expression.\nInstead it is "+y); + } + + /** Convenience method that evalutes x and cast the result to be a tupleset + * @return the tupleset - if x evaluates to a tupleset + * @throws ErrorFatal - if x does not evaluate to a tupleset + */ + public SimTupleset cset(Expr x) throws Err { + if (!x.errors.isEmpty()) throw x.errors.pick(); + Object y = visitThis(x); + if (y instanceof SimTupleset) return (SimTupleset)y; + if (y instanceof Integer) return SimTupleset.make(SimTuple.make(SimAtom.make(((Integer) y).intValue()))); + throw new ErrorFatal(x.span(), "This should have been a set or a relation.\nInstead it is "+y); + } + + /** {@inheritDoc} */ + @Override public Object visit(ExprBinary x) throws Err { + Expr a=x.left, b=x.right; + switch(x.op) { + case ARROW: case ANY_ARROW_LONE: case ANY_ARROW_ONE: case ANY_ARROW_SOME: + case LONE_ARROW_ANY: case LONE_ARROW_LONE: case LONE_ARROW_ONE: case LONE_ARROW_SOME: + case ONE_ARROW_ANY: case ONE_ARROW_LONE: case ONE_ARROW_ONE: case ONE_ARROW_SOME: + case SOME_ARROW_ANY: case SOME_ARROW_LONE: case SOME_ARROW_ONE: case SOME_ARROW_SOME: + case ISSEQ_ARROW_LONE: + return cset(x.left).product(cset(x.right)); + case JOIN: + if (x.left.isSame(Sig.UNIV)) { + SimTupleset tp = cset(x.right); + return tp.tail(tp.arity()-1); + } + if (x.right.isSame(Sig.UNIV)) { + SimTupleset tp = cset(x.left); + return tp.head(tp.arity()-1); + } + return cset(x.left).join(cset(x.right)); + case IMPLIES: return !cform(x.left) || cform(x.right); + case AND: return cform(x.left) && cform(x.right); + case OR: return cform(x.left) || cform(x.right); + case IFF: return cform(x.left) == cform(x.right); + case SHA: return trunc(cint(x.left) >> (shiftmask & cint(x.right))); + case SHR: return trunc(cint(x.left) >>> (shiftmask & cint(x.right))); + case SHL: return trunc(cint(x.left) << (shiftmask & cint(x.right))); + case INTERSECT: return cset(x.left).intersect(cset(x.right)); + case GT: return cint(x.left) > cint(x.right); + case GTE: return cint(x.left) >= cint(x.right); + case LT: return cint(x.left) < cint(x.right); + case LTE: return cint(x.left) <= cint(x.right); + case NOT_GT: return !(cint(x.left) > cint(x.right)); + case NOT_GTE: return !(cint(x.left) >= cint(x.right)); + case NOT_LT: return !(cint(x.left) < cint(x.right)); + case NOT_LTE: return !(cint(x.left) <= cint(x.right)); + case DOMAIN: return cset(x.left).domain(cset(x.right)); + case RANGE: return cset(x.left).range(cset(x.right)); + case EQUALS: return equal(x.left, x.right); + case NOT_EQUALS: return !equal(x.left, x.right); + case IN: return isIn(x.left, x.right); + case NOT_IN: return !isIn(x.left, x.right); + case MINUS: + // Special exception to allow "0-8" to not throw an exception, where 7 is the maximum allowed integer (when bitwidth==4) + // (likewise, when bitwidth==5, then +15 is the maximum allowed integer, and we want to allow 0-16 without throwing an exception) + if (a instanceof ExprConstant && ((ExprConstant)a).op==ExprConstant.Op.NUMBER && ((ExprConstant)a).num()==0) + if (b instanceof ExprConstant && ((ExprConstant)b).op==ExprConstant.Op.NUMBER && ((ExprConstant)b).num()==max+1) + return min; + //[AM] +// if (x.left.type().is_int()) return trunc(cint(x.left)-cint(x.right)); else return cset(x.left).difference(cset(x.right)); + return cset(x.left).difference(cset(x.right)); + case IMINUS: + return trunc(cint(x.left)-cint(x.right)); + case PLUS: + return cset(x.left).union(cset(x.right)); + //[AM] +// if (x.left.type().is_int()) return trunc(cint(x.left)+cint(x.right)); else return cset(x.left).union(cset(x.right)); + case IPLUS: + return trunc(cint(x.left)+cint(x.right)); + case PLUSPLUS: + return cset(x.left).override(cset(x.right)); + case MUL: + return trunc(cint(x.left) * cint(x.right)); + case DIV: + { int p=cint(x.left), q=cint(x.right), r=(p==0 ? 0 : (q==0 ? (p<0 ? 1 : -1) : (p/q))); return trunc(r); } + case REM: + { int p=cint(x.left), q=cint(x.right), r=(p==0 ? 0 : (q==0 ? (p<0 ? 1 : -1) : (p/q))); return trunc(p-r*q); } + } + throw new ErrorFatal(x.pos, "Unsupported operator ("+x.op+") encountered during ExprBinary.accept()"); + } + + /** {@inheritDoc} */ + @Override public Object visit(ExprList x) throws Err { + if (x.op==ExprList.Op.AND) { + for(Expr e:x.args) if (!cform(e)) return false; + return true; + } + if (x.op==ExprList.Op.OR) { + for(Expr e:x.args) if (cform(e)) return true; + return false; + } + if (x.op==ExprList.Op.TOTALORDER) { + SimTupleset elem = cset(x.args.get(0)), first = cset(x.args.get(1)), next = cset(x.args.get(2)); + return next.totalOrder(elem, first); + } + SimTupleset[] ans = new SimTupleset[x.args.size()]; + for(int i=1; i newenv = new Env(); + List list = new ArrayList(x.args.size()); + for(int i=0; i oldenv = env; + env = newenv; + current_function.add(f); + Object ans = visitThis(body); + env = oldenv; + current_function.remove(current_function.size()-1); + if (f.count()==0) cacheForConstants.put(f, ans); + return ans; + } + + /** {@inheritDoc} */ + @Override public Object visit(ExprConstant x) throws Err { + switch(x.op) { + case NUMBER: + int n = x.num(); + //[am] const +// if (nmax) throw new ErrorType(x.pos, "Current bitwidth is set to "+bitwidth+", thus this integer constant "+n+" is bigger than the maximum integer "+max); + return n; + case FALSE: return Boolean.FALSE; + case TRUE: return Boolean.TRUE; + case MIN: return min; + case MAX: return max; + case EMPTYNESS: return SimTupleset.EMPTY; + case STRING: return SimTupleset.make(x.string); + case NEXT: return SimTupleset.makenext(min, max); + case IDEN: return cset(Sig.UNIV).iden(); + } + throw new ErrorFatal(x.pos, "Unsupported operator ("+x.op+") encountered during ExprConstant.accept()"); + } + + /** {@inheritDoc} */ + @Override public Object visit(ExprITE x) throws Err { + if (cform(x.cond)) return visitThis(x.left); else return visitThis(x.right); + } + + /** {@inheritDoc} */ + @Override public Object visit(ExprLet x) throws Err { + env.put(x.var, visitThis(x.expr)); + Object ans = visitThis(x.sub); + env.remove(x.var); + return ans; + } + + /** {@inheritDoc} */ + @Override public Object visit(ExprUnary x) throws Err { + switch(x.op) { + case EXACTLYOF: + case LONEOF: + case ONEOF: + case SETOF: + case SOMEOF: return cset(x.sub); + case NOOP: return visitThis(x.sub); + case CARDINALITY: return trunc(cset(x.sub).size()); + case NO: return cset(x.sub).empty(); + case LONE: return cset(x.sub).longsize()<=1; + case ONE: return cset(x.sub).longsize()==1; + case SOME: return cset(x.sub).longsize()>=1; + case NOT: return cform(x.sub) ? Boolean.FALSE : Boolean.TRUE; + case CAST2SIGINT: return SimTupleset.make(SimTuple.make(SimAtom.make(cint(x.sub)))); + case CAST2INT: return trunc(cset(x.sub).sum()); + case CLOSURE: return cset(x.sub).closure(); + case RCLOSURE: return cset(x.sub).closure().union(cset(ExprConstant.IDEN)); + case TRANSPOSE: return cset(x.sub).transpose(); + } + throw new ErrorFatal(x.pos, "Unsupported operator ("+x.op+") encountered during ExprUnary.accept()"); + } + + /** {@inheritDoc} */ + @Override public Object visit(ExprVar x) throws Err { + Object ans = env.get(x); + if (ans==null) ans = sfs.get(x); + if (ans==null) { + SimAtom a = SimAtom.make(x.label); + if (!hasAtom(a)) throw new ErrorFatal(x.pos, "Variable \""+x+"\" is not bound to a legal value during translation.\n"); + ans = SimTupleset.make(SimTuple.make(a)); + } + return ans; + } + + /** {@inheritDoc} */ + @Override public SimTupleset visit(Sig x) throws Err { + if (x.isSame(Sig.NONE)) return SimTupleset.EMPTY; + if (x.isSame(Sig.SEQIDX)) return SimTupleset.make(0, maxseq-1); + if (x.isSame(Sig.SIGINT)) return SimTupleset.make(min, max); + if (x.isSame(Sig.STRING)) { + if (cacheSTRING == null) { + cacheSTRING = SimTupleset.EMPTY; + for(Map.Entry e: sfs.entrySet()) if (e.getKey() instanceof Field || e.getKey() instanceof ExprVar) { + for(SimTuple t: e.getValue()) for(int i=t.arity()-1; i>=0; i--) { + String a = t.get(i).toString(); + if (a.length()>0 && a.charAt(0)=='"') cacheSTRING = cacheSTRING.union(SimTuple.make(t.get(i))); + } + } + } + return cacheSTRING; + } + if (x==Sig.UNIV) { + if (cacheUNIV == null) { + cacheUNIV = SimTupleset.make(min, max); + for(Map.Entry e: sfs.entrySet()) + if (e.getKey() instanceof PrimSig && ((PrimSig)(e.getKey())).isTopLevel()) + cacheUNIV = cacheUNIV.union(e.getValue()); + cacheUNIV = cacheUNIV.union(visit(Sig.STRING)); + } + return cacheUNIV; + } + Object ans = sfs.get(x); + if (ans instanceof SimTupleset) return (SimTupleset)ans; else throw new ErrorFatal("Unknown sig "+x+" encountered during evaluation."); + } + + /** {@inheritDoc} */ + @Override public SimTupleset visit(Field x) throws Err { + if (x.defined) { + final ExprVar v = (ExprVar)(x.sig.decl.get()); + final Expr b = x.decl().expr; + final Env oldenv = env; + env = new Env(); + if (!b.hasVar(v)) { SimTupleset ans=cset(x.sig).product(cset(b)); env=oldenv; return ans; } + SimTupleset ans = SimTupleset.EMPTY; + for(SimTuple a: visit(x.sig)) { + SimTupleset left = SimTupleset.make(a); + env.put(v, left); + SimTupleset right = cset(b); + env.remove(v); + ans = left.product(right).union(ans); + } + env = oldenv; + return ans; + } + Object ans = sfs.get(x); + if (ans instanceof SimTupleset) return (SimTupleset)ans; else throw new ErrorFatal("Unknown field "+x+" encountered during evaluation."); + } + + /** Helper method for enumerating all possibilties for a quantification-expression. */ + private int enumerate(final TempList store, int sum, final ExprQt x, final Expr body, final int i) throws Err { // if op is ALL NO SOME ONE LONE then it always returns 0 1 2 + final int n = x.count(); + final ExprVar v = x.get(i); + final Expr bound = x.getBound(i); + final SimTupleset e = cset(bound); + final Iterator it; + switch(bound.mult()) { + case LONEOF: it = e.loneOf(); break; + case ONEOF: it = e.oneOf(); break; + case SOMEOF: it = e.someOf(); break; + default: it = e.setOf(); + } + while(it.hasNext()) { + final SimTupleset binding = it.next(); + if (bound.mult==2 && !isIn(binding, bound)) continue; + env.put(v, binding); + if (i=2 && x.op!=ExprQt.Op.COMPREHENSION && x.op!=ExprQt.Op.SUM) return 2; // no need to enumerate further + } + return sum; + } + + /** {@inheritDoc} */ + @Override public Object visit(ExprQt x) throws Err { + Expr xx = x.desugar(); + if (xx instanceof ExprQt) x = (ExprQt)xx; else return visitThis(xx); + if (x.op == ExprQt.Op.COMPREHENSION) { + TempList ans = new TempList(); + enumerate(ans, 0, x, x.sub, 0); + return SimTupleset.make(ans.makeConst()); + } + if (x.op == ExprQt.Op.ALL) return enumerate(null, 0, x, x.sub.not(), 0) == 0; + if (x.op == ExprQt.Op.NO) return enumerate(null, 0, x, x.sub, 0) == 0; + if (x.op == ExprQt.Op.SOME) return enumerate(null, 0, x, x.sub, 0) >= 1; + if (x.op == ExprQt.Op.LONE) return enumerate(null, 0, x, x.sub, 0) <= 1; + if (x.op == ExprQt.Op.ONE) return enumerate(null, 0, x, x.sub, 0) == 1; + if (x.op == ExprQt.Op.SUM) return trunc(enumerate(null, 0, x, x.sub, 0)); + throw new ErrorFatal(x.pos, "Unsupported operator ("+x.op+") encountered during ExprQt.accept()"); + } + + /** Helper method that evaluates the formula "a in b" where b.mult==0 */ + public boolean isIn(SimTuple a, Expr b) throws Err { + b = b.deNOP(); + if (b instanceof PrimSig && ((PrimSig)b).builtin) { + if (a.arity()!=1) return false; + if (b.isSame(Sig.UNIV)) return true; + if (b.isSame(Sig.NONE)) return false; + if (b.isSame(Sig.SEQIDX)) { Integer i = a.get(0).toInt(null); return i!=null && i>=0 && i0 && (at.charAt(0)=='\"'); } + } + if (b instanceof ExprBinary && ((ExprBinary)b).op==ExprBinary.Op.ARROW) { + Expr left = ((ExprBinary)b).left, right = ((ExprBinary)b).right; + int ll = left.type().arity(), rr = right.type().arity(); + if (ll <= rr) return isIn(a.head(ll), left) && isIn(a.tail(rr), right); + return isIn(a.tail(rr), right) && isIn(a.head(ll), left); + } + if (b instanceof ExprBinary && ((ExprBinary)b).op==ExprBinary.Op.PLUS) { + return isIn(a, ((ExprBinary)b).left) || isIn(a, ((ExprBinary)b).right); + } + if (b instanceof ExprBinary && ((ExprBinary)b).op==ExprBinary.Op.MINUS) { + return isIn(a, ((ExprBinary)b).left) && !isIn(a, ((ExprBinary)b).right); + } + return cset(b).has(a); + } + + /** Helper method that evaluates the formula "a = b" */ + public boolean equal(Expr a, Expr b) throws Err { + if (a.type().is_bool) return cform(a)==cform(b); + //[AM] if (a.type().is_int()) return cint(a)==cint(b); + if (a.type().arity()<=0 || a.type().arity()!=b.type().arity()) return false; // type mismatch + if (a.isSame(b)) return true; else return cset(a).equals(cset(b)); + } + + /** Helper method that evaluates the formula "a in b" */ + public boolean isIn(Expr a, Expr b) throws Err { + if (a.type().arity()<=0 || a.type().arity()!=b.type().arity()) return false; // type mismatch + if (b.isSame(Sig.UNIV)) return true; // everything is a subset of UNIV + if (a.isSame(b)) return true; // if a==b then a is a subset of b + return isIn(cset(a), b); + } + + /** Helper method that evaluates the formula "a in b" */ + private boolean isIn(SimTupleset a, Expr b) throws Err { + b = b.deNOP(); + if (b instanceof ExprBinary && b.mult!=0 && ((ExprBinary)b).op.isArrow) { + // Handles possible "binary" or higher-arity multiplicity + return isInBinary(a, (ExprBinary)b); + } + if (b instanceof ExprUnary) { + // Handles possible "unary" multiplicity + ExprUnary y = (ExprUnary)b; + if (y.op==ExprUnary.Op.EXACTLYOF) { b = y.sub.deNOP(); return a.equals(cset(b)); } + else if (y.op==ExprUnary.Op.ONEOF) { b = y.sub.deNOP(); if (!(a.longsize()==1)) return false; } + else if (y.op==ExprUnary.Op.LONEOF) { b = y.sub.deNOP(); if (!(a.longsize()<=1)) return false; } + else if (y.op==ExprUnary.Op.SOMEOF) { b = y.sub.deNOP(); if (!(a.longsize()>=1)) return false; } + else if (y.op!=ExprUnary.Op.SETOF) { b = y.sub.deNOP(); } + } + for(SimTuple t:a) if (!isIn(t, b)) return false; + return true; + } + + /** Helper method that evaluates the formula "r in (a ?->? b)" */ + private boolean isInBinary(SimTupleset R, ExprBinary ab) throws Err { + // Special check for ISSEQ_ARROW_LONE + if (ab.op == ExprBinary.Op.ISSEQ_ARROW_LONE) { + List list = R.getAllAtoms(0); + int next = 0; + SimAtom nextStr = SimAtom.make("0"); + while (list.size() > 0) { + for (int n=list.size(), i=0; ; i++) if (i>=n) return false; else if (nextStr==list.get(i)) { list.set(i, list.get(n-1)); list.remove(n-1); next++; break; } + if (list.size()==0) break; + if (next<0 || next>=maxseq) return false; // list.size()>0 and yet we've exhausted 0..maxseq-1, so indeed there are illegal atoms + nextStr = SimAtom.make(next); + } + } + // "R in A ->op B" means for each tuple a in A, there are "op" tuples in r that begins with a. + for(SimTuple left: cset(ab.left)) { + SimTupleset ans = R.beginWith(left); + switch(ab.op) { + case ISSEQ_ARROW_LONE: + case ANY_ARROW_LONE: case SOME_ARROW_LONE: case ONE_ARROW_LONE: case LONE_ARROW_LONE: if (!(ans.longsize()<=1)) return false; else break; + case ANY_ARROW_ONE: case SOME_ARROW_ONE: case ONE_ARROW_ONE: case LONE_ARROW_ONE: if (!(ans.longsize()==1)) return false; else break; + case ANY_ARROW_SOME: case SOME_ARROW_SOME: case ONE_ARROW_SOME: case LONE_ARROW_SOME: if (!(ans.longsize()>=1)) return false; else break; + } + if (!isIn(ans, ab.right)) return false; + } + // "R in A op-> B" means for each tuple b in B, there are "op" tuples in r that end with b. + for(SimTuple right: cset(ab.right)) { + SimTupleset ans = R.endWith(right); + switch(ab.op) { + case LONE_ARROW_ANY: case LONE_ARROW_SOME: case LONE_ARROW_ONE: case LONE_ARROW_LONE: if (!(ans.longsize()<=1)) return false; else break; + case ONE_ARROW_ANY: case ONE_ARROW_SOME: case ONE_ARROW_ONE: case ONE_ARROW_LONE: if (!(ans.longsize()==1)) return false; else break; + case SOME_ARROW_ANY: case SOME_ARROW_SOME: case SOME_ARROW_ONE: case SOME_ARROW_LONE: if (!(ans.longsize()>=1)) return false; else break; + } + if (!isIn(ans, ab.left)) return false; + } + return true; + } + + /** Checks whether this instance satisfies every fact defined in the given model. + * @param world - this must be the root of the Alloy model + * @return an empty String if yes, nonempty String if no + */ + public String validate(Module world) { + try { + for(Sig s: world.getAllReachableSigs()) if (!s.builtin) { + if (s.isLone!=null && !(visit(s).longsize()<=1)) return "There can be at most one "+s; + if (s.isOne !=null && !(visit(s).longsize()==1)) return "There must be exactly one "+s; + if (s.isSome!=null && !(visit(s).longsize()>=1)) return "There must be at least one "+s; + if (s instanceof SubsetSig) { + SubsetSig p = (SubsetSig)s; + Expr sum = null; + for(Sig par: p.parents) sum = par.plus(sum); + if (p.exact) { + if (!equal(s, sum)) return "Sig "+s+" must be equal to the union of its parents "+p.parents; + } else { + if (!isIn(s, sum)) return "Sig "+s+" must be equal or subset of its parents "+p.parents; + } + } else if (s != Sig.UNIV && s != Sig.NONE) { + PrimSig p = (PrimSig)s; + if (!isIn(s, p.parent)) return "Sig "+s+" must be equal or subset of its parent "+p.parent; + } + if (s.isAbstract!=null) { + Expr sum = null; + for(Sig x: ((PrimSig)s).children()) sum = x.plus(sum); + if (sum!=null && !equal(s, sum)) return "Abstract sig "+s+" must be equal to the union of its subsigs"; + } + for(Decl d: s.getFieldDecls()) for(ExprHasName f: d.names) if (!((Field)f).defined) { + if (!cform(s.decl.get().join(f).in(d.expr).forAll(s.decl))) { + return "Field "+f+" violated its bound: " + visit((Field)f) + "\n" + d.expr; + } + SimTupleset setS = visit(s); + SimTupleset setF = visit((Field)f); + for(SimAtom x:setF.getAllAtoms(0)) if (!setS.has(x)) return "Field "+f+" first column has extra atom: "+setF+" not in "+setS; + } + for(Decl d: s.getFieldDecls()) { + if (d.disjoint!=null && d.names.size()>0) { + if (!cform(ExprList.makeDISJOINT(null, null, d.names))) return "Fields must be disjoint."; + } + if (d.disjoint2!=null) for(ExprHasName f: d.names) { + Decl that = s.oneOf("that"); + Expr formula = s.decl.get().equal(that.get()).not().implies(s.decl.get().join(f).intersect(that.get().join(f)).no()); + if (!cform(formula.forAll(that).forAll(s.decl))) return "Fields must be disjoint."; + } + } + for(Expr f: s.getFacts()) { + if (!cform(f.forAll(s.decl))) { + f = f.deNOP(); if (f instanceof ExprUnary) { + ExprUnary u = (ExprUnary)f; + f = u.sub.deNOP(); + if (f instanceof ExprBinary) { + ExprBinary b = (ExprBinary)f; + if (b.op == ExprBinary.Op.JOIN && b.left.isSame(s.decl.get()) && b.right.deNOP() instanceof Field) { + String n = ((Field)(b.right.deNOP())).label; + if (u.op == ExprUnary.Op.SOME) return "The "+n+" cannot be empty."; + if (u.op == ExprUnary.Op.LONE) return "The "+n+" cannot have more than one value."; + if (u.op == ExprUnary.Op.ONE) return "The "+n+" must have exactly one value."; + if (u.op == ExprUnary.Op.NO) return "The "+n+" must be empty."; + } + } + } + return "Cannot violate a consistency constraint"; + } + } + } + for(Module m: world.getAllReachableModules()) for(Pair f: m.getAllFacts()) if (!cform(f.b)) { + String err = f.a; + if (err.matches("^fact\\$[0-9][0-9]*")) err = f.b.toString(); + if (err.length()>=2 && err.startsWith("\"") && err.endsWith("\"")) err = err.substring(1, err.length()-1); + return "Violation: " + err; + } + return ""; + } catch(Err ex) { + return "An internal error has occured:\n" + ex.dump(); + } + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/sim/SimTuple.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/sim/SimTuple.java new file mode 100644 index 00000000..5e071096 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/sim/SimTuple.java @@ -0,0 +1,242 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4compiler.sim; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; + +/** Immutable; represents a tuple. + * + *

Thread Safety: Safe. + */ + +public final class SimTuple implements Iterable { + + /** Stores the tuple. */ + private SimAtom[] array; + + /** If nonzero, it caches the hash code. */ + private int hashCode = 0; + + /** Construct a tuple backed by the given array as-is; thus the caller must not modify it any more. */ + private SimTuple(SimAtom[] array) { + if (array.length==0) throw new IllegalArgumentException(); + this.array=array; + } + + /** Construct the n-ary tuple; throws an exception if the given list is empty. */ + public static SimTuple make(List list) { + if (list.size()==0) throw new IllegalArgumentException(); + SimAtom[] array = new SimAtom[list.size()]; + for(int i=0, n=list.size(); i0) out.write(' '); + array[i].write(out); + } + out.write(')'); + } + + /** Read a (".." ".." "..") tuple assuming the leading "(" has already been consumed. */ + static SimTuple read(BufferedInputStream in) throws IOException { + List list = new ArrayList(); + while(true) { + int c = in.read(); + if (c<0) throw new IOException("Unexpected EOF"); + if (c>0 && c<=' ') continue; // skip whitespace + if (c==')') break; + if (c!='\"') throw new IOException("Expecting start of atom"); + list.add(SimAtom.read(in)); + c = in.read(); + if (c<0) throw new IOException("Unexpected EOF"); + if (c==')') break; + if (!(c<=' ')) throw new IOException("Expecting \')\' or white space after an atom."); + } + if (list.size()==0) throw new IOException("Tuple arity cannot be 0."); + return make(list); + } + + /** Returns the arity of this tuple. */ + public int arity() { return array.length; } + + /** Return the i-th atom from this tuple. */ + public SimAtom get(int i) { return array[i]; } + + /** Returns true if this tuple contains at least one occurrence of the given atom. */ + public boolean has(SimAtom atom) { + for(int i=array.length-1; i>=0; i--) if (array[i]==atom) return true; + return false; + } + + /** Replace the i-th atom, and return the resulting SimTuple. */ + public SimTuple replace(int i, SimAtom newAtom) { + if (array[i] == newAtom) return this; + SimAtom ar[] = new SimAtom[array.length]; + for(int j=0; jSimAtom map; any atom not in the map will stay unchanged. */ + public SimTuple replace(Map map) { + SimAtom[] newarray = new SimAtom[array.length]; + for(int i=array.length-1; i>=0; i--) { + SimAtom oldX = array[i]; + SimAtom newX = map.get(oldX); + newarray[i] = (newX==null ? oldX : newX); + } + return new SimTuple(newarray); + } + + /** Prepend the given atom to the front of this tuple, then return the resulting new Tuple. */ + public SimTuple prepend(SimAtom atom) { + SimAtom[] newarray = new SimAtom[array.length+1]; + newarray[0] = atom; + for(int i=0; i=0; i--) ans = ans*31 + System.identityHashCode(array[i]); + if (ans==0) ans++; // so that we don't end up re-computing this SimTuple's hashcode over and over again + hashCode = ans; + } + return ans; + } + + /** {@inheritDoc} */ + @Override public boolean equals(Object that) { + if (this==that) return true; else if (!(that instanceof SimTuple)) return false; + SimAtom[] other = ((SimTuple)that).array; + if (array==other) return true; else if (array.length != other.length) return false; + if (hashCode() != that.hashCode()) return false; + for(int i=array.length-1; i>=0; i--) if (array[i]!=other[i]) return false; + array=other; + // Change it so we share the same array; this is thread safe since these array contents are never mutated, + // so it doesn't matter if some thread sees the old array and some sees the new array. + // JLS 3rd Edition 17.7 guarantees that writes and reads of references are atomic though not necessarily visible, + // so another thread will either see the old array or the new array. + return true; + } + + /** Returns the first atom of this tuple. */ + public SimAtom head() { return array[0]; } + + /** Returns the last atom of this tuple. */ + public SimAtom tail() { return array[array.length-1]; } + + /** Returns the subtuple containing the first n atoms (n must be between 1 and arity) */ + public SimTuple head(int n) { + if (n<=0 || n>array.length) throw new IllegalArgumentException(); else if (n==array.length) return this; + SimAtom newtuple[] = new SimAtom[n]; + for(int c=0; carray.length) throw new IllegalArgumentException(); else if (n==array.length) return this; + SimAtom newtuple[] = new SimAtom[n]; + for(int a=array.length-n, c=0; c0) sb.append("->"); + sb.append(array[i]); + } + } + + /** {@inheritDoc} */ + @Override public String toString() { + StringBuilder sb = new StringBuilder(); + toString(sb); + return sb.toString(); + } + + /** {@inheritDoc} */ + public Iterator iterator() { + return new Iterator() { + private int i = 0; // the next index to read out + public boolean hasNext() { return i < array.length; } + public SimAtom next() { if (i < array.length) {i++; return array[i-1];} else throw new NoSuchElementException(); } + public void remove() { throw new UnsupportedOperationException(); } + }; + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/sim/SimTupleset.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/sim/SimTupleset.java new file mode 100644 index 00000000..6a792ab3 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/sim/SimTupleset.java @@ -0,0 +1,615 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4compiler.sim; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.IdentityHashMap; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.NoSuchElementException; +import edu.mit.csail.sdg.alloy4.ConstList; +import edu.mit.csail.sdg.alloy4.ErrorAPI; +import edu.mit.csail.sdg.alloy4.ErrorType; +import edu.mit.csail.sdg.alloy4.Util; +import edu.mit.csail.sdg.alloy4.ConstList.TempList; + +/** Immutable; represents a tupleset. */ + +public final class SimTupleset implements Iterable { + + // if (min Invariant: If nonempty, it must contain only same-arity tuples. + *
Invariant: It must not contain duplicate tuples. + */ + private final ConstList tuples; + private final int min; + private final int max; + private final boolean next; + + /** Construct a tupleset with the given 4 values (Note: caller MUST make sure there are no duplicates, even between (min,max) and tuples, and that all tuples are of same arity!) */ + private SimTupleset(Collection tuples, int min, int max, boolean next) { + this.tuples = ConstList.make(tuples); + this.min = min; + this.max = max; + this.next = next; + } + + /** Construct a tupleset containing the given list of tuples (Note: caller MUST make sure there are no duplicates, and all tuples are of same arity!) */ + private SimTupleset(Collection tuples) { + this.tuples = ConstList.make(tuples); + this.min = 0; + this.max = 0; + this.next = false; + } + + /** The tupleset containing no tuples. */ + public static final SimTupleset EMPTY = new SimTupleset(new TempList(0).makeConst()); + + /** Construct the set containing integers between min and max (inclusively). */ + public static SimTupleset make(int min, int max) { + if (min>max) return EMPTY; + if (min==max) return new SimTupleset(Util.asList(SimTuple.make(SimAtom.make(min))), 0, 0, false); + return new SimTupleset(EMPTY.tuples, min, max, false); + } + + /** Construct the set containing (min,min+1)...(max-1,max) */ + public static SimTupleset makenext(int min, int max) { + return min>=max ? EMPTY : (new SimTupleset(EMPTY.tuples, min, max, true)); + } + + /** Construct a tupleset containing the given tuple. */ + public static SimTupleset make(SimTuple tuple) { + return new SimTupleset(Util.asList(tuple)); + } + + /** Make a tupleset containing the given atom. */ + public static SimTupleset make(String atom) { + return make(SimTuple.make(atom)); + } + + /** Make a tupleset containing a deep copy of the given list of tuples (Note: caller MUST make sure there are no duplicates, and all tuples are of same arity!) */ + public static SimTupleset make(Collection tuples) { + return tuples.size()==0 ? EMPTY : new SimTupleset(tuples); + } + + /** If this tupleset is empty, then return 0, else return the arity of every tuple in this tupleset. */ + public int arity() { + return min=max && tuples.size()==0; + } + + /** Returns the number of tuples in this tupleset (this answer may be truncated if it cannot fit in a 32-bit integer) */ + public int size() { + return (int)longsize(); + } + + /** Returns the number of tuples in this tupleset (this answer will never overflow) */ + public long longsize() { + long ans = (min=0; i--) if (tuples.get(i).get(0)==that) return true; + return false; + } + + /** Returns an arbitrary atom from an arbitrary tuple. + * @throws - ErrorAPI if this tupleset is empty + */ + public SimAtom getAtom() throws ErrorAPI { + if (tuples.size()>0) return tuples.get(0).get(0); + if (min>=max) throw new ErrorAPI("This tupleset is empty"); + return SimAtom.make(min); + } + + /** Returns an arbitrary tuple. + * @throws - ErrorAPI if this tupleset is empty + */ + public SimTuple getTuple() throws ErrorAPI { + if (tuples.size()>0) return tuples.get(0); + if (min>=max) throw new ErrorAPI("This tupleset is empty"); + SimAtom a = SimAtom.make(min); + if (next) return SimTuple.make(a, SimAtom.make(min+1)); else return SimTuple.make(a); + } + + /** Return the union of this and that; (if this tupleset and that tupleset does not have compatible arity, then we return this tupleset as is). + *
Note: the tuples in the result will be ordered as follows: + * first comes the tuples in "this" in original order, + * then the tuples that are in "that" but not in "this". + */ + public SimTupleset union(SimTupleset that) { + if (this.empty() || this==that) return that; + if (that.empty() || arity()!=that.arity()) return this; + TempList ans = null; // when null, it means we haven't found any new tuple to add yet + for(SimTuple x: that) if (!has(x)) { + if (ans == null) ans = new TempList(tuples); + ans.add(x); + } + return ans==null ? this : new SimTupleset(ans.makeConst(), min, max, next); + } + + /** Return the union of this and that; (if this tupleset and that tuple does not have compatible arity, then we return this tupleset as is). + *
Note: if this operation is a no-op, we guarantee we'll return this SimTupleset as is. + */ + public SimTupleset union(SimTuple that) { + if (empty()) return make(that); + if (arity()!=that.arity() || has(that)) return this; + TempList ans = new TempList(tuples.size()+1); + ans.addAll(tuples).add(that); + return new SimTupleset(ans.makeConst(), min, max, next); + } + + //================================================================================================================// + + /** Return the tupleset where each tuple is truncated to the first N atoms; if n is zero or negative, we return the emptyset; if n >= this.arity, we return this as is. */ + public SimTupleset head(int n) { + if (n<=0 || empty()) return EMPTY; else if (arity() <= n) return this; + if (min ans = new TempList(tuples.size()); + for(SimTuple x: tuples) { + Integer a = x.head().toInt(null); + if (a!=null && a>=min && a ans = new TempList(tuples.size()); + for(SimTuple x: this) { SimTuple y = x.head(n); if (!ans.contains(y)) ans.add(y); } + return new SimTupleset(ans.makeConst()); + } + + /** Return the tupleset where each tuple is truncated to the last N atoms; if n is zero or negative, we return the emptyset; if n >= this.arity, we return this as is. */ + public SimTupleset tail(int n) { + if (n<=0 || empty()) return EMPTY; else if (arity() <= n) return this; + if (min ans = new TempList(tuples.size()); + for(SimTuple x: tuples) { + Integer a = x.tail().toInt(null); + if (a!=null && a>min && a<=max) continue; + SimTuple y = SimTuple.make(x.tail()); + if (!ans.contains(y)) ans.add(y); + } + return new SimTupleset(ans.makeConst(), min+1, max, false); + } + TempList ans = new TempList(tuples.size()); + for(SimTuple x: this) { SimTuple y = x.tail(n); if (!ans.contains(y)) ans.add(y); } + return new SimTupleset(ans.makeConst()); + } + + /** Returns a read-only iterator over the tuples. */ + public Iterator iterator() { + return new Iterator() { + private long n = longsize(); + private long i = 0; + public SimTuple next() { if (i>=n) throw new NoSuchElementException(); else {i++; return get(i-1);} } + public boolean hasNext() { return i0 && c<=' ') continue; // skip whitespace + if (c=='{') break; else throw new IOException("Expecting start of tupleset"); + } + LinkedHashSet list = new LinkedHashSet(); + while(true) { + int c = in.read(); + if (c<0) throw new IOException("Unexpected EOF"); + if (c>0 && c<=' ') continue; // skip whitespace + if (c=='}') break; + if (c!='(') throw new IOException("Expecting start of tuple"); + list.add(SimTuple.read(in)); + c = in.read(); + if (c<0) throw new IOException("Unexpected EOF"); + if (c=='}') break; + if (!(c<=' ')) throw new IOException("Expecting \')\' or white space after a tuple."); + } + return make(list); + } + + /** Returns the index position if the list of tuples contains the tuple (a,b) (or return -1 if not found). */ + private static int find(TempList tuples, SimAtom a, SimAtom b) { + if (tuples.size() == 0 || tuples.get(0).arity() != 2) return -1; + for(int i=tuples.size()-1; i >= 0; i--) { + SimTuple x = tuples.get(i); + if (x.head()==a && x.tail()==b) return i; + } + return -1; + } + + /** {@inheritDoc} */ + @Override public String toString() { + StringBuilder sb = null; + for(SimTuple x: this) { + if (sb==null) sb = new StringBuilder("{"); else sb.append(", "); + x.toString(sb); + } + return sb==null ? "{}" : (sb.append("}").toString()); + } + + /** Returns a hashcode consistent with the equals() method. */ + @Override public int hashCode() { + int ans = 0; + for(SimTuple t: this) ans = ans + t.hashCode(); + return ans; + } + + /** Returns true if this contains the same tuples as that. */ + @Override public boolean equals(Object that) { + return this==that || (that instanceof SimTupleset && equals((SimTupleset)that)); + } + + /** Returns true if this contains the same tuples as that. */ + public boolean equals(SimTupleset that) { + // since SimTupleset must not contain duplicates, and this.size()==that.size(), comparing one way is sufficient + return this==that || (that!=null && longsize()==that.longsize() && in(that)); + } + + /** Returns true if this is a subset of that. */ + public boolean in(SimTupleset that) { + if (empty() || this==that) return true; + if (longsize()>that.longsize() || arity()!=that.arity()) return false; + for(SimTuple t: this) if (!that.has(t)) return false; + return true; + } + + /** Sum up all the integer atoms in this tupleset; (if this tupleset's arity is not 1, then we return 0) */ + public int sum() { + if (arity() != 1) return 0; + Integer zero = 0; + int ans = 0; + for(SimTuple t: this) ans = ans + t.get(0).toInt(zero); + return ans; + } + + /** Return the identity over this tupleset; (if this tupleset's arity is not 1, then we return an emptyset) + *
Note: the result's tuple order is the same as this tupleset's tuple order. + */ + public SimTupleset iden() { + if (arity() != 1) return EMPTY; + TempList ans = new TempList(size()); + for(SimTuple x: this) ans.add(SimTuple.make(x.head(), x.head())); // since "this" has no duplicate tuples, "ans" will not have duplicate tuples either + return new SimTupleset(ans.makeConst()); + } + + /** Return the relational override of this and that; (if this tupleset and that tuple does not have compatible arity, then we return this tupleset as is). + *
Note: in general, the tuples may be ordered arbitrarily in the result. + *
Note: if this operation is a no-op, we guarantee we'll return this SimTupleset as is. + */ + public SimTupleset override(final SimTuple that) { + if (arity()==1) return union(that); + if (empty()) return SimTupleset.make(that); + if (arity()!=that.arity()) return this; + boolean added = false, same = false; + TempList ans = new TempList(size()); + SimAtom head = that.get(0); + for(SimTuple x: this) { + if (x.get(0)!=head) { ans.add(x); continue; } + if (x.equals(that)) same = true; + if (!added) { ans.add(that); added=true; } + } + if (!added) ans.add(that); else if (same && longsize()==ans.size()) return this; + return new SimTupleset(ans.makeConst()); + } + + /** Return the relational override of this and that; (if this tupleset and that tupleset does not have compatible arity, then we return this tupleset as is). + *
Note: in general, the tuples may be ordered arbitrarily in the result. + */ + public SimTupleset override(SimTupleset that) throws ErrorAPI { + if (arity()==1) return union(that); + if (this.empty() || this==that) return that; + if (that.empty() || this.arity()!=that.arity()) return this; + if (that.longsize()==1) return override(that.getTuple()); // very common case, so let's optimize it + TempList ans = new TempList(size()); + for(SimTuple x: this) if (!that.has(x)) ans.add(x); + ans.addAll(that); + return new SimTupleset(ans.makeConst()); + } + + /** Return this minus that; (if this tupleset and that tupleset does not have compatible arity, then we return this tupleset as is). + *
Note: The resulting tuples will keep their original order. + */ + public SimTupleset difference(SimTupleset that) { + if (this.empty() || this==that) return EMPTY; + if (that.empty() || arity()!=that.arity()) return this; + TempList ans = new TempList(size()-1); + for(SimTuple x: this) if (!that.has(x)) ans.add(x); + return ans.size()==longsize() ? this : (ans.size()==0 ? EMPTY : new SimTupleset(ans.makeConst())); + } + + /** Return this minus that; (if this tupleset and that tuple does not have compatible arity, then we return this tupleset as is). + *
Note: The resulting tuples will keep their original order. + *
Note: if this operation is a no-op, we guarantee we'll return this SimTupleset as is. + */ + public SimTupleset difference(SimTuple that) { + if (empty() || arity()!=that.arity()) return this; + TempList ans = new TempList(size()-1); + for(SimTuple x: this) { + if (that==null || !x.equals(that)) ans.add(x); else that=null; + } + return that!=null ? this : (ans.size()==0 ? EMPTY : new SimTupleset(ans.makeConst())); + } + + /** Return this minus any tuple that contains the given atom. + *
Note: The resulting tuples will keep their original order. + */ + public SimTupleset removeAll(SimAtom that) { + if (empty()) return EMPTY; + TempList ans = new TempList(size()-1); + again: + for(SimTuple x: this) { + for(int i=x.arity()-1; i>=0; i--) if (x.get(i)==that) continue again; + ans.add(x); + } + return ans.size()==longsize() ? this : (ans.size()==0 ? EMPTY : new SimTupleset(ans.makeConst())); + } + + /** Return the transpose of this tupleset; (if this tupleset's arity is not 2, we'll return an empty set instead) */ + public SimTupleset transpose() { + if (empty() || arity()!=2) return EMPTY; + TempList ans = new TempList(size()); + for(SimTuple x: this) ans.add(SimTuple.make(x.tail(), x.head())); // since "this" has no duplicate tuples, "ans" will not have duplicate tuples either + return new SimTupleset(ans.makeConst()); + } + + /** Return the cartesian product of this and that. */ + public SimTupleset product(SimTupleset that) { + if (empty() || that.empty()) return EMPTY; + TempList ans = new TempList(size() * that.size()); + for(SimTuple a: this) for(SimTuple b: that) { + ans.add(a.product(b)); // We don't have to check for duplicates, because we assume every tuple in "this" has same arity, and every tuple in "that" has same arity + } + return new SimTupleset(ans.makeConst()); + } + + /** Return the relational join between this and that (throws ErrorType if this.arity==1 and that.arity==1) */ + public SimTupleset join(SimTupleset that) throws ErrorType { + if (empty() || that.empty()) return EMPTY; + if (arity()==1 && that.arity()==1) throw new ErrorType("Cannot join two unary relations."); + TempList ans = new TempList(); + for(SimTuple a: this) for(SimTuple b: that) if (a.tail()==b.head()) { + SimTuple c = a.join(b); + if (!ans.contains(c)) ans.add(c); + } + return ans.size()==0 ? EMPTY : new SimTupleset(ans.makeConst()); + } + + /** Return the intersection of this and that. */ + public SimTupleset intersect(SimTupleset that) { + if (this==that) return this; else if (empty() || that.empty()) return EMPTY; + TempList ans = new TempList(size() < that.size() ? size() : that.size()); + for(SimTuple x: that) if (has(x)) ans.add(x); + if (ans.size()==0) return EMPTY; + if (ans.size()==this.longsize()) return this; + if (ans.size()==that.longsize()) return that; else return new SimTupleset(ans.makeConst()); + } + + /** Return true if the intersection of this and that is nonempty. */ + public boolean intersects(SimTupleset that) { + if (empty()) return false; + if (this==that) return true; + for(SimTuple x: that) if (has(x)) return true; + return false; + } + + /** Returns this<:that (NOTE: if this.arity!=1, then we return the empty set) */ + public SimTupleset domain(SimTupleset that) { + if (arity()!=1 || that.empty()) return EMPTY; + TempList ans = new TempList(that.size()); + for(SimTuple x: that) if (has(x.head())) ans.add(x); + return ans.size()==that.longsize() ? that : (ans.size()==0 ? EMPTY : new SimTupleset(ans.makeConst())); + } + + /** Returns this:>that (NOTE: if that.arity!=1, then we return the empty set) */ + public SimTupleset range(SimTupleset that) { + if (that.arity()!=1 || this.empty()) return EMPTY; + TempList ans = new TempList(this.size()); + for(SimTuple x: this) if (that.has(x.head())) ans.add(x); + return ans.size()==this.longsize() ? this : (ans.size()==0 ? EMPTY : new SimTupleset(ans.makeConst())); + } + + /** Returns the closure of this tupleset (NOTE: if this.arity!=2, we will return an empty set) */ + public SimTupleset closure() { + if (arity()!=2) return EMPTY; + TempList ar = new TempList(size()); + ar.addAll(this); + while(true) { + int n = ar.size(); + for(int i=0; i ans = new TempList(); + again: + for(SimTuple r: this) { + for(int i=0; i ans = new TempList(); + again: + for(SimTuple r: this) { + for(int i=0; i getAllAtoms(int column) throws ErrorAPI { + if (empty()) return new ArrayList(0); + if (column<0 || column>=arity()) throw new ErrorAPI("This tupleset does not have an \""+column+"th\" column."); + IdentityHashMap ans = new IdentityHashMap(); + for(SimTuple x: this) ans.put(x.get(column), Boolean.TRUE); + return new ArrayList(ans.keySet()); + } + + /** Return true if this is a total ordering over "elem", with "first" being the first element of the total order. */ + public boolean totalOrder(SimTupleset elem, SimTupleset first) throws ErrorAPI { + if (elem.empty()) return false; + if (elem.longsize()==1) return elem.arity()==1 && first.equals(elem) && empty(); + if (first.longsize()!=1 || first.arity()!=1 || elem.arity()!=1 || arity()!=2 || longsize()!=elem.longsize()-1) return false; + SimAtom e = first.getAtom(); + List elems = elem.getAllAtoms(0); + if (longsize() > Integer.MAX_VALUE) throw new OutOfMemoryError(); + List next = new ArrayList(size()); + for(SimTuple x: this) next.add(x); + while(true) { + // "e" must be in elems; remove it from elems + for(int n=elems.size(), i=0; ; i++) + if (i>=n) return false; + else if (elems.get(i)==e) { elems.set(i, elems.get(n-1)); elems.remove(n-1); break; } + // if "e" was the last element, then "next" must be empty as well + if (elems.size()==0) return next.size()==0; + // remove (e,e') from next and let e' be the new e + // (if there was a cycle, we would eventually detect that since the repeated element would no longer be in "elems") + for(int n=next.size(), i=0; ; i++) + if (i>=n) return false; + else if (e==next.get(i).head()) { e=next.get(i).tail(); next.set(i, next.get(n-1)); next.remove(n-1); break; } + } + } + + /** Return an iterator over all subset x of this where x.size<=1 */ + public Iterator loneOf() { + return new Iterator() { + private long i = (-1); // -1 if we haven't started yet; otherwise it is the next element to return + public SimTupleset next() { + if (i<0) {i++; return EMPTY;} else if (i>=longsize()) throw new NoSuchElementException(); + SimTupleset ans = make(get(i)); + i++; + return ans; + } + public boolean hasNext() { return i < longsize(); } + public void remove() { throw new UnsupportedOperationException(); } + }; + } + + /** Return an iterator over all subset x of this where x.size==1 */ + public Iterator oneOf() { + Iterator ans = loneOf(); + ans.next(); // here, we depend on our knowledge that loneOf() will always return the emptyset first, so we can skip it up front + return ans; + } + + /** Return an iterator over all subset x of this */ + public Iterator setOf() { + if (longsize() > Integer.MAX_VALUE) throw new OutOfMemoryError(); + return new Iterator() { + private boolean in[] = new boolean[size()]; // indicates whether each tuple should appear in the upcoming tupleset; if null, it means no more results + public SimTupleset next() { + if (in==null) throw new NoSuchElementException(); + TempList ans = new TempList(); + for(int i=0; i=1 */ + public Iterator someOf() { + Iterator ans = setOf(); + ans.next(); // here, we depend on our knowledge that setOf() will always return the emptyset first, so we can skip it up front + return ans; + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/sim/package.html b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/sim/package.html new file mode 100644 index 00000000..e717547f --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/sim/package.html @@ -0,0 +1,5 @@ + + +This package contains a pure-Java evaluator/simulator for Alloy4 instances. + + diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/translator/A4Options.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/translator/A4Options.java new file mode 100644 index 00000000..8fe00e6d --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/translator/A4Options.java @@ -0,0 +1,198 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4compiler.translator; + +import java.io.Serializable; +import java.util.prefs.Preferences; +import edu.mit.csail.sdg.alloy4.ErrorAPI; +import edu.mit.csail.sdg.alloy4.SafeList; +import edu.mit.csail.sdg.alloy4.Util; + +/** Mutable; this class encapsulates the customizable options of the Alloy-to-Kodkod translator. */ + +public final class A4Options implements Serializable { + + /** This enum defines the set of possible SAT solvers. */ + public static final class SatSolver implements Serializable { + /** This ensures the class can be serialized reliably. */ + private static final long serialVersionUID = 0; + /** List of all existing SatSolver values. */ + private static final SafeList values = new SafeList(); + /** This is a unique String for this value; it should be kept consistent in future versions. */ + private final String id; + /** This is the label that the toString() method will return. */ + private final String toString; + /** If not null, this is the external command-line solver to use. */ + private final String external; + /** If not null, this is the set of options to use with the command-line solver. */ + private final String[] options; + /** Constructs a new SatSolver value. */ + private SatSolver(String id, String toString, String external, String[] options, boolean add) { + this.id=id; + this.toString=toString; + this.external=external; + this.options=new String[options!=null ? options.length : 0]; + for(int i=0; i values() { + SafeList ans; + synchronized(SatSolver.class) { ans=values.dup(); } + return ans; + } + /** Returns the human-readable label for this enum value. */ + @Override public String toString() { return toString; } + /** Ensures we can use == to do comparison. */ + private Object readResolve() { + synchronized(SatSolver.class) { + for(SatSolver x:values) if (x.id.equals(id)) return x; + values.add(this); + } + return this; + } + /** Given an id, return the enum value corresponding to it (if there's no match, then return SAT4J). */ + public static SatSolver parse(String id) { + synchronized(SatSolver.class) { for(SatSolver x:values) if (x.id.equals(id)) return x; } + return SAT4J; + } + /** Saves this value into the Java preference object. */ + public void set() { Preferences.userNodeForPackage(Util.class).put("SatSolver2",id); } + /** Reads the current value of the Java preference object (if it's not set, then return SAT4J). */ + public static SatSolver get() { return parse(Preferences.userNodeForPackage(Util.class).get("SatSolver2","")); } + /** BerkMin via pipe */ + public static final SatSolver BerkMinPIPE = new SatSolver("berkmin", "BerkMin", "berkmin", null, true); + /** Spear via pipe */ + public static final SatSolver SpearPIPE = new SatSolver("spear", "Spear", "spear", new String[]{"--model", "--dimacs"}, true); + /** MiniSat1 via JNI */ + public static final SatSolver MiniSatJNI = new SatSolver("minisat(jni)", "MiniSat", null, null, true); + /** MiniSatProver1 via JNI */ + public static final SatSolver MiniSatProverJNI = new SatSolver("minisatprover(jni)", "MiniSat with Unsat Core", null, null, true); + /** ZChaff via JNI */ + public static final SatSolver ZChaffJNI = new SatSolver("zchaff(jni)", "ZChaff", null, null, true); + /** SAT4J using native Java */ + public static final SatSolver SAT4J = new SatSolver("sat4j", "SAT4J", null, null, true); + /** Outputs the raw CNF file only */ + public static final SatSolver CNF = new SatSolver("cnf", "Output CNF to file", null, null, true); + /** Outputs the raw Kodkod file only */ + public static final SatSolver KK = new SatSolver("kodkod", "Output Kodkod to file", null, null, true); + } + + /** This ensures the class can be serialized reliably. */ + private static final long serialVersionUID = 0; + + /** Constructs an A4Options object with default values for everything. */ + public A4Options() { } + + /** This option specifies the amount of symmetry breaking to do (when symmetry breaking isn't explicitly disabled). + * + *

If a formula is unsatisfiable, then in general, the higher this value, + * the faster you finish the solving. But if this value is too high, it will instead slow down the solving. + * + *

If a formula is satisfiable, then in general, the lower this value, the faster you finish the solving. + * Setting this value to 0 usually gives the fastest solve. + * + *

Default value is 20. + */ + public int symmetry = 20; + + /** This option specifies the maximum skolem-function depth. + *

Default value is 0, which means it will only generate skolem constants, and will not generate skolem functions. + */ + public int skolemDepth = 0; + + /** This option specifies the unsat core minimization strategy (0=GuaranteedLocalMinimum 1=FasterButLessAccurate 2=EvenFaster...) + *

Default value is set to the fastest current strategy. + */ + public int coreMinimization = 2; + + /** Unsat core granularity, default is 0 (only top-level conjuncts are considered), 3 expands all quantifiers */ + public int coreGranularity = 0; + + /** This option specifies the SAT solver to use (SAT4J, MiniSatJNI, MiniSatProverJNI, ZChaffJNI...) + *

Default value is SAT4J. + */ + public SatSolver solver = SatSolver.SAT4J; + + /** When this.solver is external, and the solver filename is a relative filename, then this option specifies + * the directory that the solver filename is relative to. + */ + public String solverDirectory = ""; + + /** This specifies the directory where we may write temporary files to. */ + public String tempDirectory = System.getProperty("java.io.tmpdir"); + + /** This option tells the compiler the "original filename" that these AST nodes came from; + * it is only used for generating comments and other diagnostic messages. + *

Default value is "". + */ + public String originalFilename = ""; + + /** This option specifies whether the compiler should record + * the original Kodkod formula being generated and the resulting Kodkod instances. + *

Default value is false. + */ + public boolean recordKodkod = false; + + /** This option specifies whether the solver should report only solutions + * that don't cause any overflows. */ + public boolean noOverflow = false; + + /** This option constrols how deep we unroll loops and unroll recursive predicate/function/macros (negative means it's disallowed) */ + public int unrolls = (-1); + + /** This method makes a copy of this Options object. */ + public A4Options dup() { + A4Options x = new A4Options(); + x.unrolls = unrolls; + x.symmetry = symmetry; + x.skolemDepth = skolemDepth; + x.coreMinimization = coreMinimization; + x.solver = solver; + x.solverDirectory = solverDirectory; + x.tempDirectory = tempDirectory; + x.originalFilename = originalFilename; + x.recordKodkod = recordKodkod; + x.noOverflow = noOverflow; + x.coreGranularity = coreGranularity; + return x; + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/translator/A4Solution.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/translator/A4Solution.java new file mode 100644 index 00000000..be6dab64 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/translator/A4Solution.java @@ -0,0 +1,1134 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4compiler.translator; + +import static edu.mit.csail.sdg.alloy4compiler.ast.Sig.UNIV; +import static edu.mit.csail.sdg.alloy4compiler.ast.Sig.SIGINT; +import static edu.mit.csail.sdg.alloy4compiler.ast.Sig.SEQIDX; +import static edu.mit.csail.sdg.alloy4compiler.ast.Sig.STRING; +import static edu.mit.csail.sdg.alloy4compiler.ast.Sig.NONE; +import static kodkod.engine.Solution.Outcome.UNSATISFIABLE; +import java.io.File; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import kodkod.ast.BinaryExpression; +import kodkod.ast.BinaryFormula; +import kodkod.ast.Decl; +import kodkod.ast.Expression; +import kodkod.ast.Formula; +import kodkod.ast.IntExpression; +import kodkod.ast.Node; +import kodkod.ast.Relation; +import kodkod.ast.Variable; +import kodkod.ast.operator.ExprOperator; +import kodkod.ast.operator.FormulaOperator; +import kodkod.engine.CapacityExceededException; +import kodkod.engine.Evaluator; +import kodkod.engine.Proof; +import kodkod.engine.Solution; +import kodkod.engine.Solver; +import kodkod.engine.config.AbstractReporter; +import kodkod.engine.config.Options; +import kodkod.engine.config.Reporter; +import kodkod.engine.fol2sat.TranslationRecord; +import kodkod.engine.fol2sat.Translator; +import kodkod.engine.satlab.SATFactory; +import kodkod.engine.ucore.HybridStrategy; +import kodkod.engine.ucore.RCEStrategy; +import kodkod.instance.Bounds; +import kodkod.instance.Instance; +import kodkod.instance.Tuple; +import kodkod.instance.TupleFactory; +import kodkod.instance.TupleSet; +import kodkod.instance.Universe; +import kodkod.util.ints.IndexedEntry; +import edu.mit.csail.sdg.alloy4.A4Reporter; +import edu.mit.csail.sdg.alloy4.ConstList; +import edu.mit.csail.sdg.alloy4.ConstMap; +import edu.mit.csail.sdg.alloy4.Err; +import edu.mit.csail.sdg.alloy4.ErrorAPI; +import edu.mit.csail.sdg.alloy4.ErrorFatal; +import edu.mit.csail.sdg.alloy4.ErrorSyntax; +import edu.mit.csail.sdg.alloy4.Pair; +import edu.mit.csail.sdg.alloy4.Pos; +import edu.mit.csail.sdg.alloy4.SafeList; +import edu.mit.csail.sdg.alloy4.UniqueNameGenerator; +import edu.mit.csail.sdg.alloy4.Util; +import edu.mit.csail.sdg.alloy4compiler.ast.Command; +import edu.mit.csail.sdg.alloy4compiler.ast.Expr; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprBinary; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprConstant; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprUnary; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprVar; +import edu.mit.csail.sdg.alloy4compiler.ast.Func; +import edu.mit.csail.sdg.alloy4compiler.ast.Sig; +import edu.mit.csail.sdg.alloy4compiler.ast.Type; +import edu.mit.csail.sdg.alloy4compiler.ast.Sig.Field; +import edu.mit.csail.sdg.alloy4compiler.ast.Sig.PrimSig; +import edu.mit.csail.sdg.alloy4compiler.translator.A4Options.SatSolver; + +/** This class stores a SATISFIABLE or UNSATISFIABLE solution. + * It is also used as a staging area for the solver before generating the solution. + * Once solve() has been called, then this object becomes immutable after that. + */ + +public final class A4Solution { + + //====== static immutable fields ====================================================================// + + /** The constant unary relation representing the smallest Int atom. */ + static final Relation KK_MIN = Relation.unary("Int/min"); + + /** The constant unary relation representing the Int atom "0". */ + static final Relation KK_ZERO = Relation.unary("Int/zero"); + + /** The constant unary relation representing the largest Int atom. */ + static final Relation KK_MAX = Relation.unary("Int/max"); + + /** The constant binary relation representing the "next" relation from each Int atom to its successor. */ + static final Relation KK_NEXT = Relation.binary("Int/next"); + + /** The constant unary relation representing the set of all seq/Int atoms. */ + static final Relation KK_SEQIDX = Relation.unary("seq/Int"); + + /** The constant unary relation representing the set of all String atoms. */ + static final Relation KK_STRING = Relation.unary("String"); + + //====== immutable fields ===========================================================================// + + /** The original Alloy options that generated this solution. */ + private final A4Options originalOptions; + + /** The original Alloy command that generated this solution; can be "" if unknown. */ + private final String originalCommand; + + /** The bitwidth; always between 1 and 30. */ + private final int bitwidth; + + /** The maximum allowed sequence length; always between 0 and 2^(bitwidth-1)-1. */ + private final int maxseq; + + /** The maximum allowed number of loop unrolling and recursion level. */ + private final int unrolls; + + /** The list of all atoms. */ + private final ConstList kAtoms; + + /** The Kodkod TupleFactory object. */ + private final TupleFactory factory; + + /** The set of all Int atoms; immutable. */ + private final TupleSet sigintBounds; + + /** The set of all seq/Int atoms; immutable. */ + private final TupleSet seqidxBounds; + + /** The set of all String atoms; immutable. */ + private final TupleSet stringBounds; + + /** The Kodkod Solver object. */ + private final Solver solver; + + //====== mutable fields (immutable after solve() has been called) ===================================// + + /** True iff the problem is solved. */ + private boolean solved = false; + + /** The Kodkod Bounds object. */ + private Bounds bounds; + + /** The list of Kodkod formulas; can be empty if unknown; once a solution is solved we must not modify this anymore */ + private ArrayList formulas = new ArrayList(); + + /** The list of known Alloy4 sigs. */ + private SafeList sigs; + + /** If solved==true and is satisfiable, then this is the list of known skolems. */ + private SafeList skolems = new SafeList(); + + /** If solved==true and is satisfiable, then this is the list of actually used atoms. */ + private SafeList atoms = new SafeList(); + + /** If solved==true and is satisfiable, then this maps each Kodkod atom to a short name. */ + private Map atom2name = new LinkedHashMap(); + + /** If solved==true and is satisfiable, then this maps each Kodkod atom to its most specific sig. */ + private Map atom2sig = new LinkedHashMap(); + + /** If solved==true and is satisfiable, then this is the Kodkod evaluator. */ + private Evaluator eval = null; + + /** If not null, you can ask it to get another solution. */ + private Iterator kEnumerator = null; + + /** The map from each Sig/Field/Skolem/Atom to its corresponding Kodkod expression. */ + private Map a2k; + + /** The map from each String literal to its corresponding Kodkod expression. */ + private final ConstMap s2k; + + /** The map from each kodkod Formula to Alloy Expr or Alloy Pos (can be empty if unknown) */ + private Map k2pos; + + /** The map from each Kodkod Relation to Alloy Type (can be empty or incomplete if unknown) */ + private Map rel2type; + + /** The map from each Kodkod Variable to an Alloy Type and Alloy Pos. */ + private Map> decl2type; + + //===================================================================================================// + + /** Construct a blank A4Solution containing just UNIV, SIGINT, SEQIDX, STRING, and NONE as its only known sigs. + * @param originalCommand - the original Alloy command that generated this solution; can be "" if unknown + * @param bitwidth - the bitwidth; must be between 1 and 30 + * @param maxseq - the maximum allowed sequence length; must be between 0 and (2^(bitwidth-1))-1 + * @param atoms - the set of atoms + * @param rep - the reporter that will receive diagnostic and progress messages + * @param opt - the Alloy options that will affect the solution and the solver + * @param expected - whether the user expected an instance or not (1 means yes, 0 means no, -1 means the user did not express an expectation) + */ + A4Solution(String originalCommand, int bitwidth, int maxseq, Set stringAtoms, Collection atoms, final A4Reporter rep, A4Options opt, int expected) throws Err { + opt = opt.dup(); + this.unrolls = opt.unrolls; + this.sigs = new SafeList(Arrays.asList(UNIV, SIGINT, SEQIDX, STRING, NONE)); + this.a2k = Util.asMap(new Expr[]{UNIV, SIGINT, SEQIDX, STRING, NONE}, Relation.INTS.union(KK_STRING), Relation.INTS, KK_SEQIDX, KK_STRING, Relation.NONE); + this.k2pos = new LinkedHashMap(); + this.rel2type = new LinkedHashMap(); + this.decl2type = new LinkedHashMap>(); + this.originalOptions = opt; + this.originalCommand = (originalCommand==null ? "" : originalCommand); + this.bitwidth = bitwidth; + this.maxseq = maxseq; + if (bitwidth < 0) throw new ErrorSyntax("Cannot specify a bitwidth less than 0"); + if (bitwidth > 30) throw new ErrorSyntax("Cannot specify a bitwidth greater than 30"); + if (maxseq < 0) throw new ErrorSyntax("The maximum sequence length cannot be negative."); + if (maxseq > 0 && maxseq > max()) throw new ErrorSyntax("With integer bitwidth of "+bitwidth+", you cannot have sequence length longer than "+max()); + if (atoms.isEmpty()) { + atoms = new ArrayList(1); + atoms.add(""); + } + kAtoms = ConstList.make(atoms); + bounds = new Bounds(new Universe(kAtoms)); + factory = bounds.universe().factory(); + TupleSet sigintBounds = factory.noneOf(1); + TupleSet seqidxBounds = factory.noneOf(1); + TupleSet stringBounds = factory.noneOf(1); + final TupleSet next = factory.noneOf(2); + int min=min(), max=max(); + if (max >= min) for(int i=min; i<=max; i++) { // Safe since we know 1 <= bitwidth <= 30 + Tuple ii = factory.tuple(""+i); + TupleSet is = factory.range(ii, ii); + bounds.boundExactly(i, is); + sigintBounds.add(ii); + if (i>=0 && i s2k = new HashMap(); + for(String e: stringAtoms) { + Relation r = Relation.unary(""); + Tuple t = factory.tuple(e); + s2k.put(e, r); + bounds.boundExactly(r, factory.range(t, t)); + stringBounds.add(t); + } + this.s2k = ConstMap.make(s2k); + this.stringBounds = stringBounds.unmodifiableView(); + bounds.boundExactly(KK_STRING, this.stringBounds); + int sym = (expected==1 ? 0 : opt.symmetry); + solver = new Solver(); + solver.options().setNoOverflow(opt.noOverflow); + solver.options().setFlatten(false); // added for now, since multiplication and division circuit takes forever to flatten + if (opt.solver.external()!=null) { + String ext = opt.solver.external(); + if (opt.solverDirectory.length()>0 && ext.indexOf(File.separatorChar)<0) ext=opt.solverDirectory+File.separatorChar+ext; + try { + File tmp = File.createTempFile("tmp", ".cnf", new File(opt.tempDirectory)); + tmp.deleteOnExit(); + solver.options().setSolver(SATFactory.externalFactory(ext, tmp.getAbsolutePath(), "", opt.solver.options())); + //solver.options().setSolver(SATFactory.externalFactory(ext, tmp.getAbsolutePath(), opt.solver.options())); + } catch(IOException ex) { throw new ErrorFatal("Cannot create temporary directory.", ex); } + } else if (opt.solver.equals(A4Options.SatSolver.ZChaffJNI)) { + solver.options().setSolver(SATFactory.ZChaff); + } else if (opt.solver.equals(A4Options.SatSolver.MiniSatJNI)) { + solver.options().setSolver(SATFactory.MiniSat); + } else if (opt.solver.equals(A4Options.SatSolver.MiniSatProverJNI)) { + sym=20; + solver.options().setSolver(SATFactory.MiniSatProver); + solver.options().setLogTranslation(2); + solver.options().setCoreGranularity(opt.coreGranularity); + } else { + solver.options().setSolver(SATFactory.DefaultSAT4J); // Even for "KK" and "CNF", we choose SAT4J here; later, just before solving, we'll change it to a Write2CNF solver + } + solver.options().setSymmetryBreaking(sym); + solver.options().setSkolemDepth(opt.skolemDepth); + solver.options().setBitwidth(bitwidth > 0 ? bitwidth : (int) Math.ceil(Math.log(atoms.size())) + 1); + solver.options().setIntEncoding(Options.IntEncoding.TWOSCOMPLEMENT); + } + + /** Construct a new A4Solution that is the continuation of the old one, but with the "next" instance. */ + private A4Solution(A4Solution old) throws Err { + if (!old.solved) throw new ErrorAPI("This solution is not yet solved, so next() is not allowed."); + if (old.kEnumerator==null) throw new ErrorAPI("This solution was not generated by an incremental SAT solver.\n" + "Solution enumeration is currently only implemented for MiniSat and SAT4J."); + if (old.eval==null) throw new ErrorAPI("This solution is already unsatisfiable, so you cannot call next() to get the next solution."); + Instance inst = old.kEnumerator.next().instance(); + unrolls = old.unrolls; + originalOptions = old.originalOptions; + originalCommand = old.originalCommand; + bitwidth = old.bitwidth; + maxseq = old.maxseq; + kAtoms = old.kAtoms; + factory = old.factory; + sigintBounds = old.sigintBounds; + seqidxBounds = old.seqidxBounds; + stringBounds = old.stringBounds; + solver = old.solver; + bounds = old.bounds; + formulas = old.formulas; + sigs = old.sigs; + kEnumerator = old.kEnumerator; + k2pos = old.k2pos; + rel2type = old.rel2type; + decl2type = old.decl2type; + if (inst!=null) { + eval = new Evaluator(inst, old.solver.options()); + a2k = new LinkedHashMap(); + for(Map.Entry e: old.a2k.entrySet()) + if (e.getKey() instanceof Sig || e.getKey() instanceof Field) + a2k.put(e.getKey(), e.getValue()); + UniqueNameGenerator un = new UniqueNameGenerator(); + rename(this, null, null, un); + a2k = ConstMap.make(a2k); + } else { + skolems = old.skolems; + eval = null; + a2k = old.a2k; + } + s2k = old.s2k; + atoms = atoms.dup(); + atom2name = ConstMap.make(atom2name); + atom2sig = ConstMap.make(atom2sig); + solved = true; + } + + /** Turn the solved flag to be true, and make all remaining fields immutable. */ + private void solved() { + if (solved) return; // already solved + bounds = bounds.clone().unmodifiableView(); + sigs = sigs.dup(); + skolems = skolems.dup(); + atoms = atoms.dup(); + atom2name = ConstMap.make(atom2name); + atom2sig = ConstMap.make(atom2sig); + a2k = ConstMap.make(a2k); + k2pos = ConstMap.make(k2pos); + rel2type = ConstMap.make(rel2type); + decl2type = ConstMap.make(decl2type); + solved = true; + } + + //===================================================================================================// + + /** Returns the bitwidth; always between 1 and 30. */ + public int getBitwidth() { return bitwidth; } + + /** Returns the maximum allowed sequence length; always between 0 and 2^(bitwidth-1)-1. */ + public int getMaxSeq() { return maxseq; } + + /** Returns the largest allowed integer, or -1 if no integers are allowed. */ + public int max() { return Util.max(bitwidth); } + + /** Returns the smallest allowed integer, or 0 if no integers are allowed */ + public int min() { return Util.min(bitwidth); } + + /** Returns the maximum number of allowed loop unrolling or recursion level. */ + public int unrolls() { return unrolls; } + + //===================================================================================================// + + /** Returns the original Alloy file name that generated this solution; can be "" if unknown. */ + public String getOriginalFilename() { return originalOptions.originalFilename; } + + /** Returns the original command that generated this solution; can be "" if unknown. */ + public String getOriginalCommand() { return originalCommand; } + + //===================================================================================================// + + /** Returns the Kodkod input used to generate this solution; returns "" if unknown. */ + public String debugExtractKInput() { + if (solved) + return TranslateKodkodToJava.convert(Formula.and(formulas), bitwidth, kAtoms, bounds, atom2name); + else + return TranslateKodkodToJava.convert(Formula.and(formulas), bitwidth, kAtoms, bounds.unmodifiableView(), null); + } + + //===================================================================================================// + + /** Returns the Kodkod TupleFactory object. */ + TupleFactory getFactory() { return factory; } + + /** Returns a modifiable copy of the Kodkod Bounds object. */ + Bounds getBounds() { return bounds.clone(); } + + /** Add a new relation with the given label and the given lower and upper bound. + * @param label - the label for the new relation; need not be unique + * @param lower - the lowerbound; can be null if you want it to be the empty set + * @param upper - the upperbound; cannot be null; must contain everything in lowerbound + */ + Relation addRel(String label, TupleSet lower, TupleSet upper) throws ErrorFatal { + if (solved) throw new ErrorFatal("Cannot add a Kodkod relation since solve() has completed."); + Relation rel = Relation.nary(label, upper.arity()); + if (lower == upper) { + bounds.boundExactly(rel, upper); + } else if (lower == null) { + bounds.bound(rel, upper); + } else { + if (lower.arity() != upper.arity()) throw new ErrorFatal("Relation "+label+" must have same arity for lowerbound and upperbound."); + bounds.bound(rel, lower, upper); + } + return rel; + } + + /** Add a new sig to this solution and associate it with the given expression (and if s.isTopLevel then add this expression into Sig.UNIV). + *
The expression must contain only constant Relations or Relations that are already bound in this solution. + *
(If the sig was already added by a previous call to addSig(), then this call will return immediately without altering what it is associated with) + */ + void addSig(Sig s, Expression expr) throws ErrorFatal { + if (solved) throw new ErrorFatal("Cannot add an additional sig since solve() has completed."); + if (expr.arity()!=1) throw new ErrorFatal("Sig "+s+" must be associated with a unary relational value."); + if (a2k.containsKey(s)) return; + a2k.put(s, expr); + sigs.add(s); + if (s.isTopLevel()) a2k.put(UNIV, a2k.get(UNIV).union(expr)); + } + + /** Add a new field to this solution and associate it with the given expression. + *
The expression must contain only constant Relations or Relations that are already bound in this solution. + *
(If the field was already added by a previous call to addField(), then this call will return immediately without altering what it is associated with) + */ + void addField(Field f, Expression expr) throws ErrorFatal { + if (solved) throw new ErrorFatal("Cannot add an additional field since solve() has completed."); + if (expr.arity()!=f.type().arity()) throw new ErrorFatal("Field "+f+" must be associated with an "+f.type().arity()+"-ary relational value."); + if (a2k.containsKey(f)) return; + a2k.put(f, expr); + } + + /** Add a new skolem to this solution and associate it with the given expression. + *
The expression must contain only constant Relations or Relations that are already bound in this solution. + */ + private ExprVar addSkolem(String label, Type type, Expression expr) throws Err { + if (solved) throw new ErrorFatal("Cannot add an additional skolem since solve() has completed."); + int a = type.arity(); + if (a<1) throw new ErrorFatal("Skolem "+label+" must be associated with a relational value."); + if (a!=expr.arity()) throw new ErrorFatal("Skolem "+label+" must be associated with an "+a+"-ary relational value."); + ExprVar v = ExprVar.make(Pos.UNKNOWN, label, type); + a2k.put(v, expr); + skolems.add(v); + return v; + } + + /** Returns an unmodifiable copy of the map from each Sig/Field/Skolem/Atom to its corresponding Kodkod expression. */ + ConstMap a2k() { return ConstMap.make(a2k); } + + /** Returns an unmodifiable copy of the map from each String literal to its corresponding Kodkod expression. */ + ConstMap s2k() { return s2k; } + + /** Returns the corresponding Kodkod expression for the given Sig, or null if it is not associated with anything. */ + Expression a2k(Sig sig) { return a2k.get(sig); } + + /** Returns the corresponding Kodkod expression for the given Field, or null if it is not associated with anything. */ + Expression a2k(Field field) { return a2k.get(field); } + + /** Returns the corresponding Kodkod expression for the given Atom/Skolem, or null if it is not associated with anything. */ + Expression a2k(ExprVar var) { return a2k.get(var); } + + /** Returns the corresponding Kodkod expression for the given String constant, or null if it is not associated with anything. */ + Expression a2k(String stringConstant) { return s2k.get(stringConstant); } + + /** Returns the corresponding Kodkod expression for the given expression, or null if it is not associated with anything. */ + Expression a2k(Expr expr) throws ErrorFatal { + while(expr instanceof ExprUnary) { + if (((ExprUnary)expr).op==ExprUnary.Op.NOOP) { expr = ((ExprUnary)expr).sub; continue; } + if (((ExprUnary)expr).op==ExprUnary.Op.EXACTLYOF) { expr = ((ExprUnary)expr).sub; continue; } + break; + } + if (expr instanceof ExprConstant && ((ExprConstant)expr).op==ExprConstant.Op.EMPTYNESS) return Expression.NONE; + if (expr instanceof ExprConstant && ((ExprConstant)expr).op==ExprConstant.Op.STRING) return s2k.get(((ExprConstant)expr).string); + if (expr instanceof Sig || expr instanceof Field || expr instanceof ExprVar) return a2k.get(expr); + if (expr instanceof ExprBinary) { + Expr a=((ExprBinary)expr).left, b=((ExprBinary)expr).right; + switch(((ExprBinary)expr).op) { + case ARROW: return a2k(a).product(a2k(b)); + case PLUS: return a2k(a).union(a2k(b)); + case MINUS: return a2k(a).difference(a2k(b)); + //TODO: IPLUS, IMINUS??? + } + } + return null; // Current only UNION, PRODUCT, and DIFFERENCE of Sigs and Fields and ExprConstant.EMPTYNESS are allowed in a defined field's definition. + } + + /** Return a modifiable TupleSet representing a sound overapproximation of the given expression. */ + TupleSet approximate(Expression expression) { + return factory.setOf(expression.arity(), Translator.approximate(expression, bounds, solver.options()).denseIndices()); + } + + /** Query the Bounds object to find the lower/upper bound; throws ErrorFatal if expr is not Relation, nor a {union, product} of Relations. */ + TupleSet query(boolean findUpper, Expression expr, boolean makeMutable) throws ErrorFatal { + if (expr==Relation.NONE) return factory.noneOf(1); + if (expr==Relation.INTS) return makeMutable ? sigintBounds.clone() : sigintBounds; + if (expr==KK_SEQIDX) return makeMutable ? seqidxBounds.clone() : seqidxBounds; + if (expr==KK_STRING) return makeMutable ? stringBounds.clone() : stringBounds; + if (expr instanceof Relation) { + TupleSet ans = findUpper ? bounds.upperBound((Relation)expr) : bounds.lowerBound((Relation)expr); + if (ans!=null) return makeMutable ? ans.clone() : ans; + } + else if (expr instanceof BinaryExpression) { + BinaryExpression b = (BinaryExpression)expr; + if (b.op() == ExprOperator.UNION) { + TupleSet left = query(findUpper, b.left(), true); + TupleSet right = query(findUpper, b.right(), false); + left.addAll(right); + return left; + } else if (b.op() == ExprOperator.PRODUCT) { + TupleSet left = query(findUpper, b.left(), true); + TupleSet right = query(findUpper, b.right(), false); + return left.product(right); + } + } + throw new ErrorFatal("Unknown expression encountered during bounds computation: "+expr); + } + + /** Shrink the bounds for the given relation; throws an exception if the new bounds is not sameAs/subsetOf the old bounds. */ + void shrink(Relation relation, TupleSet lowerBound, TupleSet upperBound) throws Err { + if (solved) throw new ErrorFatal("Cannot shrink a Kodkod relation since solve() has completed."); + TupleSet oldL = bounds.lowerBound(relation); + TupleSet oldU = bounds.upperBound(relation); + if (oldU.containsAll(upperBound) && upperBound.containsAll(lowerBound) && lowerBound.containsAll(oldL)) { + bounds.bound(relation, lowerBound, upperBound); + } else { + throw new ErrorAPI("Inconsistent bounds shrinking on relation: "+relation); + } + } + + //===================================================================================================// + + /** Returns true iff the problem has been solved and the result is satisfiable. */ + public boolean satisfiable() { return eval!=null; } + + /** Returns an unmodifiable copy of the list of all sigs in this solution's model; always contains UNIV+SIGINT+SEQIDX+STRING+NONE and has no duplicates. */ + public SafeList getAllReachableSigs() { return sigs.dup(); } + + /** Returns an unmodifiable copy of the list of all skolems if the problem is solved and is satisfiable; else returns an empty list. */ + public Iterable getAllSkolems() { return skolems.dup(); } + + /** Returns an unmodifiable copy of the list of all atoms if the problem is solved and is satisfiable; else returns an empty list. */ + public Iterable getAllAtoms() { return atoms.dup(); } + + /** Returns the short unique name corresponding to the given atom if the problem is solved and is satisfiable; else returns atom.toString(). */ + String atom2name(Object atom) { String ans=atom2name.get(atom); return ans==null ? atom.toString() : ans; } + + /** Returns the most specific sig corresponding to the given atom if the problem is solved and is satisfiable; else returns UNIV. */ + PrimSig atom2sig(Object atom) { PrimSig sig=atom2sig.get(atom); return sig==null ? UNIV : sig; } + + /** Caches eval(Sig) and eval(Field) results. */ + private Map evalCache = new LinkedHashMap(); + + /** Return the A4TupleSet for the given sig (if solution not yet solved, or unsatisfiable, or sig not found, then return an empty tupleset) */ + public A4TupleSet eval(Sig sig) { + try { + if (!solved || eval==null) return new A4TupleSet(factory.noneOf(1), this); + A4TupleSet ans = evalCache.get(sig); + if (ans!=null) return ans; + TupleSet ts = eval.evaluate((Expression) TranslateAlloyToKodkod.alloy2kodkod(this, sig)); + ans = new A4TupleSet(ts, this); + evalCache.put(sig, ans); + return ans; + } catch(Err er) { + return new A4TupleSet(factory.noneOf(1), this); + } + } + + /** Return the A4TupleSet for the given field (if solution not yet solved, or unsatisfiable, or field not found, then return an empty tupleset) */ + public A4TupleSet eval(Field field) { + try { + if (!solved || eval==null) return new A4TupleSet(factory.noneOf(field.type().arity()), this); + A4TupleSet ans = evalCache.get(field); + if (ans!=null) return ans; + TupleSet ts = eval.evaluate((Expression) TranslateAlloyToKodkod.alloy2kodkod(this, field)); + ans = new A4TupleSet(ts, this); + evalCache.put(field, ans); + return ans; + } catch(Err er) { + return new A4TupleSet(factory.noneOf(field.type().arity()), this); + } + } + + /** If this solution is solved and satisfiable, evaluates the given expression and returns an A4TupleSet, a java Integer, or a java Boolean. */ + public Object eval(Expr expr) throws Err { + try { + if (expr instanceof Sig) return eval((Sig)expr); + if (expr instanceof Field) return eval((Field)expr); + if (!solved) throw new ErrorAPI("This solution is not yet solved, so eval() is not allowed."); + if (eval==null) throw new ErrorAPI("This solution is unsatisfiable, so eval() is not allowed."); + if (expr.ambiguous && !expr.errors.isEmpty()) expr = expr.resolve(expr.type(), null); + if (!expr.errors.isEmpty()) throw expr.errors.pick(); + Object result = TranslateAlloyToKodkod.alloy2kodkod(this, expr); + if (result instanceof IntExpression) return eval.evaluate((IntExpression)result) + (eval.wasOverflow() ? " (OF)" : ""); + if (result instanceof Formula) return eval.evaluate((Formula)result); + if (result instanceof Expression) return new A4TupleSet(eval.evaluate((Expression)result), this); + throw new ErrorFatal("Unknown internal error encountered in the evaluator."); + } catch(CapacityExceededException ex) { + throw TranslateAlloyToKodkod.rethrow(ex); + } + } + + /** Returns the Kodkod instance represented by this solution; throws an exception if the problem is not yet solved or if it is unsatisfiable. */ + public Instance debugExtractKInstance() throws Err { + if (!solved) throw new ErrorAPI("This solution is not yet solved, so instance() is not allowed."); + if (eval==null) throw new ErrorAPI("This solution is unsatisfiable, so instance() is not allowed."); + return eval.instance().unmodifiableView(); + } + + //===================================================================================================// + + /** Maps a Kodkod formula to an Alloy Expr or Alloy Pos (or null if no such mapping) */ + Object k2pos(Node formula) { return k2pos.get(formula); } + + /** Associates the Kodkod formula to a particular Alloy Expr (if the Kodkod formula is not already associated with an Alloy Expr or Alloy Pos) */ + Formula k2pos(Formula formula, Expr expr) throws Err { + if (solved) throw new ErrorFatal("Cannot alter the k->pos mapping since solve() has completed."); + if (formula==null || expr==null || k2pos.containsKey(formula)) return formula; + k2pos.put(formula, expr); + if (formula instanceof BinaryFormula) { + BinaryFormula b = (BinaryFormula)formula; + if (b.op() == FormulaOperator.AND) { k2pos(b.left(), expr); k2pos(b.right(), expr); } + } + return formula; + } + + /** Associates the Kodkod formula to a particular Alloy Pos (if the Kodkod formula is not already associated with an Alloy Expr or Alloy Pos) */ + Formula k2pos(Formula formula, Pos pos) throws Err { + if (solved) throw new ErrorFatal("Cannot alter the k->pos mapping since solve() has completed."); + if (formula==null || pos==null || pos==Pos.UNKNOWN || k2pos.containsKey(formula)) return formula; + k2pos.put(formula, pos); + if (formula instanceof BinaryFormula) { + BinaryFormula b = (BinaryFormula)formula; + if (b.op() == FormulaOperator.AND) { k2pos(b.left(), pos); k2pos(b.right(), pos); } + } + return formula; + } + + //===================================================================================================// + + /** Associates the Kodkod relation to a particular Alloy Type (if it is not already associated with something) */ + void kr2type(Relation relation, Type newType) throws Err { + if (solved) throw new ErrorFatal("Cannot alter the k->type mapping since solve() has completed."); + if (!rel2type.containsKey(relation)) rel2type.put(relation, newType); + } + + /** Remove all mapping from Kodkod relation to Alloy Type. */ + void kr2typeCLEAR() throws Err { + if (solved) throw new ErrorFatal("Cannot clear the k->type mapping since solve() has completed."); + rel2type.clear(); + } + + //===================================================================================================// + + /** Caches a constant pair of Type.EMPTY and Pos.UNKNOWN */ + private Pair cachedPAIR = null; + + /** Maps a Kodkod variable to an Alloy Type and Alloy Pos (if no association exists, it will return (Type.EMPTY , Pos.UNKNOWN) */ + Pair kv2typepos(Variable var) { + Pair ans=decl2type.get(var); + if (ans!=null) return ans; + if (cachedPAIR==null) cachedPAIR=new Pair(Type.EMPTY, Pos.UNKNOWN); + return cachedPAIR; + } + + /** Associates the Kodkod variable to a particular Alloy Type and Alloy Pos (if it is not already associated with something) */ + void kv2typepos(Variable var, Type type, Pos pos) throws Err { + if (solved) throw new ErrorFatal("Cannot alter the k->type mapping since solve() has completed."); + if (type==null) type=Type.EMPTY; + if (pos==null) pos=Pos.UNKNOWN; + if (!decl2type.containsKey(var)) decl2type.put(var, new Pair(type, pos)); + } + + //===================================================================================================// + + /** Add the given formula to the list of Kodkod formulas, and associate it with the given Pos object (pos can be null if unknown). */ + void addFormula(Formula newFormula, Pos pos) throws Err { + if (solved) throw new ErrorFatal("Cannot add an additional formula since solve() has completed."); + if (formulas.size()>0 && formulas.get(0)==Formula.FALSE) return; // If one formula is false, we don't need the others + if (newFormula==Formula.FALSE) formulas.clear(); // If one formula is false, we don't need the others + formulas.add(newFormula); + if (pos!=null && pos!=Pos.UNKNOWN) k2pos(newFormula, pos); + } + + /** Add the given formula to the list of Kodkod formulas, and associate it with the given Expr object (expr can be null if unknown) */ + void addFormula(Formula newFormula, Expr expr) throws Err { + if (solved) throw new ErrorFatal("Cannot add an additional formula since solve() has completed."); + if (formulas.size()>0 && formulas.get(0)==Formula.FALSE) return; // If one formula is false, we don't need the others + if (newFormula==Formula.FALSE) formulas.clear(); // If one formula is false, we don't need the others + formulas.add(newFormula); + if (expr!=null) k2pos(newFormula, expr); + } + + //===================================================================================================// + + /** Helper class that wraps an iterator up where it will pre-fetch the first element (note: it will not prefetch subsequent elements). */ + private static final class Peeker implements Iterator { + /** The encapsulated iterator. */ + private Iterator iterator; + /** True iff we have captured the first element. */ + private boolean hasFirst; + /** If hasFirst is true, then this is the captured first element. */ + private T first; + /** Constructrs a Peeker object. */ + private Peeker(Iterator it) { + iterator = it; + if (it.hasNext()) { hasFirst=true; first=it.next(); } + } + /** {@inheritDoc} */ + public boolean hasNext() { + return hasFirst || iterator.hasNext(); + } + /** {@inheritDoc} */ + public T next() { + if (hasFirst) { hasFirst=false; T ans=first; first=null; return ans; } else return iterator.next(); + } + /** {@inheritDoc} */ + public void remove() { throw new UnsupportedOperationException(); } + } + + //===================================================================================================// + + /** Helper method to determine if a given binary relation is a total order over a given unary relation. */ + private static List isOrder(TupleSet b, TupleSet u) { + // Size check + final int n = u.size(); + final List list = new ArrayList(n); + if (b.size() == 0 && n <= 1) return list; + if (b.size() != n-1) return null; + // Find the starting element + Tuple head = null; + TupleSet right = b.project(1); + for(Tuple x: u) if (!right.contains(x)) {head = x; break;} + if (head==null) return null; + final TupleFactory f = head.universe().factory(); + // Form the list + list.add(head); + while(true) { + // Find head.next + Tuple headnext = null; + for(Tuple x: b) if (x.atom(0)==head.atom(0)) { headnext = f.tuple(x.atom(1)); break; } + // If we've reached the end of the chain, and indeed we've formed exactly n elements (and all are in u), we're done + if (headnext==null) return list.size()==n ? list : null; + // If we've accumulated more than n elements, or if we reached an element not in u, then we declare failure + if (list.size()==n || !u.contains(headnext)) return null; + // Move on to the next step + head = headnext; + list.add(head); + } + } + + /** Helper method that chooses a name for each atom based on its most specific sig; (external caller should call this method with s==null and nexts==null) */ + private static void rename (A4Solution frame, PrimSig s, Map> nexts, UniqueNameGenerator un) throws Err { + if (s==null) { + for(ExprVar sk:frame.skolems) un.seen(sk.label); + // Store up the skolems + List skolems = new ArrayList(); + for(Map.Entry e: frame.rel2type.entrySet()) { + Relation r = e.getKey(); if (!frame.eval.instance().contains(r)) continue; + Type t = e.getValue(); if (t.arity() > r.arity()) continue; // Something is wrong; let's skip it + while (t.arity() < r.arity()) t = UNIV.type().product(t); + String n = Util.tail(r.name()); + while(n.length()>0 && n.charAt(0)=='$') n = n.substring(1); + skolems.add(n); + skolems.add(t); + skolems.add(r); + } + // Find all suitable "next" or "prev" relations + nexts = new LinkedHashMap>(); + for(Sig sig:frame.sigs) for(Field f: sig.getFields()) if (f.label.compareToIgnoreCase("next")==0) { + List> fold = f.type().fold(); + if (fold.size()==1) { + List t = fold.get(0); + if (t.size()==3 && t.get(0).isOne!=null && t.get(1)==t.get(2) && !nexts.containsKey(t.get(1))) { + TupleSet set = frame.eval.evaluate(frame.a2k(t.get(1))); + if (set.size()<=1) continue; + TupleSet next = frame.eval.evaluate(frame.a2k(t.get(0)).join(frame.a2k(f))); + List test = isOrder(next, set); + if (test!=null) nexts.put(t.get(1), test); + } else if (t.size()==2 && t.get(0)==t.get(1) && !nexts.containsKey(t.get(0))) { + TupleSet set = frame.eval.evaluate(frame.a2k(t.get(0))); + if (set.size()<=1) continue; + TupleSet next = frame.eval.evaluate(frame.a2k(f)); + List test = isOrder(next, set); + if (test!=null) nexts.put(t.get(1), test); + } + } + } + for(Sig sig:frame.sigs) for(Field f: sig.getFields()) if (f.label.compareToIgnoreCase("prev")==0) { + List> fold = f.type().fold(); + if (fold.size()==1) { + List t = fold.get(0); + if (t.size()==3 && t.get(0).isOne!=null && t.get(1)==t.get(2) && !nexts.containsKey(t.get(1))) { + TupleSet set = frame.eval.evaluate(frame.a2k(t.get(1))); + if (set.size()<=1) continue; + TupleSet next = frame.eval.evaluate(frame.a2k(t.get(0)).join(frame.a2k(f)).transpose()); + List test = isOrder(next, set); + if (test!=null) nexts.put(t.get(1), test); + } else if (t.size()==2 && t.get(0)==t.get(1) && !nexts.containsKey(t.get(0))) { + TupleSet set = frame.eval.evaluate(frame.a2k(t.get(0))); + if (set.size()<=1) continue; + TupleSet next = frame.eval.evaluate(frame.a2k(f).transpose()); + List test = isOrder(next, set); + if (test!=null) nexts.put(t.get(1), test); + } + } + } + // Assign atom->name and atom->MostSignificantSig + for(Tuple t:frame.eval.evaluate(Relation.INTS)) { frame.atom2sig.put(t.atom(0), SIGINT); } + for(Tuple t:frame.eval.evaluate(KK_SEQIDX)) { frame.atom2sig.put(t.atom(0), SEQIDX); } + for(Tuple t:frame.eval.evaluate(KK_STRING)) { frame.atom2sig.put(t.atom(0), STRING); } + for(Sig sig:frame.sigs) if (sig instanceof PrimSig && !sig.builtin && ((PrimSig)sig).isTopLevel()) rename(frame, (PrimSig)sig, nexts, un); + // These are redundant atoms that were not chosen to be in the final instance + int unused=0; + for(Tuple tuple:frame.eval.evaluate(Relation.UNIV)) { + Object atom = tuple.atom(0); + if (!frame.atom2sig.containsKey(atom)) { frame.atom2name.put(atom, "unused"+unused); unused++; } + } + // Add the skolems + for(int num=skolems.size(), i=0; i0 && n.charAt(0)=='$') n=n.substring(1); + Type t = (Type) skolems.get(i+1); + Relation r = (Relation) skolems.get(i+2); + frame.addSkolem(un.make("$"+n), t, r); + } + return; + } + for(PrimSig c: s.children()) rename(frame, c, nexts, un); + String signame = un.make(s.label.startsWith("this/") ? s.label.substring(5) : s.label); + List list = new ArrayList(); + for(Tuple t: frame.eval.evaluate(frame.a2k(s))) list.add(t); + List order = nexts.get(s); + if (order!=null && order.size()==list.size() && order.containsAll(list)) { list=order; } + int i = 0; + for(Tuple t: list) { + if (frame.atom2sig.containsKey(t.atom(0))) continue; // This means one of the subsig has already claimed this atom. + String x = signame + "$" + i; + i++; + frame.atom2sig.put(t.atom(0), s); + frame.atom2name.put(t.atom(0), x); + ExprVar v = ExprVar.make(null, x, s.type()); + TupleSet ts = t.universe().factory().range(t, t); + Relation r = Relation.unary(x); + frame.eval.instance().add(r, ts); + frame.a2k.put(v, r); + frame.atoms.add(v); + } + } + + //===================================================================================================// + + /** Solve for the solution if not solved already; if cmd==null, we will simply use the lowerbound of each relation as its value. */ + A4Solution solve(final A4Reporter rep, Command cmd, Simplifier simp, boolean tryBookExamples) throws Err, IOException { + // If already solved, then return this object as is + if (solved) return this; + // If cmd==null, then all four arguments are ignored, and we simply use the lower bound of each relation + if (cmd==null) { + Instance inst = new Instance(bounds.universe()); + for(int max=max(), i=min(); i<=max; i++) { + Tuple it = factory.tuple(""+i); + inst.add(i, factory.range(it, it)); + } + for(Relation r: bounds.relations()) inst.add(r, bounds.lowerBound(r)); + eval = new Evaluator(inst, solver.options()); + rename(this, null, null, new UniqueNameGenerator()); + solved(); + return this; + } + // Otherwise, prepare to do the solve... + final A4Options opt = originalOptions; + long time = System.currentTimeMillis(); + rep.debug("Simplifying the bounds...\n"); + if (simp!=null && formulas.size()>0 && !simp.simplify(rep, this, formulas)) addFormula(Formula.FALSE, Pos.UNKNOWN); + rep.translate(opt.solver.id(), bitwidth, maxseq, solver.options().skolemDepth(), solver.options().symmetryBreaking()); + Formula fgoal = Formula.and(formulas); + rep.debug("Generating the solution...\n"); + kEnumerator = null; + Solution sol = null; + final Reporter oldReporter = solver.options().reporter(); + final boolean solved[] = new boolean[]{true}; + solver.options().setReporter(new AbstractReporter() { // Set up a reporter to catch the type+pos of skolems + @Override public void skolemizing(Decl decl, Relation skolem, List predecl) { + try { + Type t=kv2typepos(decl.variable()).a; + if (t==Type.EMPTY) return; + for(int i=(predecl==null ? -1 : predecl.size()-1); i>=0; i--) { + Type pp=kv2typepos(predecl.get(i).variable()).a; + if (pp==Type.EMPTY) return; + t=pp.product(t); + } + kr2type(skolem, t); + } catch(Throwable ex) { } // Exception here is not fatal + } + @Override public void solvingCNF(int primaryVars, int vars, int clauses) { + if (solved[0]) return; else solved[0]=true; // initially solved[0] is true, so we won't report the # of vars/clauses + if (rep!=null) rep.solve(primaryVars, vars, clauses); + } + }); + if (!opt.solver.equals(SatSolver.CNF) && !opt.solver.equals(SatSolver.KK) && tryBookExamples) { // try book examples + A4Reporter r = "yes".equals(System.getProperty("debug")) ? rep : null; + try { sol = BookExamples.trial(r, this, fgoal, solver, cmd.check); } catch(Throwable ex) { sol = null; } + } + solved[0] = false; // this allows the reporter to report the # of vars/clauses + for(Relation r: bounds.relations()) { formulas.add(r.eq(r)); } // Without this, kodkod refuses to grow unmentioned relations + fgoal = Formula.and(formulas); + // Now pick the solver and solve it! + if (opt.solver.equals(SatSolver.KK)) { + File tmpCNF = File.createTempFile("tmp", ".java", new File(opt.tempDirectory)); + String out = tmpCNF.getAbsolutePath(); + Util.writeAll(out, debugExtractKInput()); + rep.resultCNF(out); + return null; + } + if (opt.solver.equals(SatSolver.CNF)) { + File tmpCNF = File.createTempFile("tmp", ".cnf", new File(opt.tempDirectory)); + String out = tmpCNF.getAbsolutePath(); + solver.options().setSolver(WriteCNF.factory(out)); + try { sol = solver.solve(fgoal, bounds); } catch(WriteCNF.WriteCNFCompleted ex) { rep.resultCNF(out); return null; } + // The formula is trivial (otherwise, it would have thrown an exception) + // Since the user wants it in CNF format, we manually generate a trivially satisfiable (or unsatisfiable) CNF file. + Util.writeAll(out, sol.instance()!=null ? "p cnf 1 1\n1 0\n" : "p cnf 1 2\n1 0\n-1 0\n"); + rep.resultCNF(out); + return null; + } + if (solver.options().solver()==SATFactory.ZChaffMincost || !solver.options().solver().incremental()) { + if (sol==null) sol = solver.solve(fgoal, bounds); + } else { + kEnumerator = new Peeker(solver.solveAll(fgoal, bounds)); + if (sol==null) sol = kEnumerator.next(); + } + if (!solved[0]) rep.solve(0, 0, 0); + final Instance inst = sol.instance(); + // To ensure no more output during SolutionEnumeration + solver.options().setReporter(oldReporter); + // If unsatisfiable, then retreive the unsat core if desired + if (inst==null && solver.options().solver()==SATFactory.MiniSatProver) { + try { + lCore = new LinkedHashSet(); + Proof p = sol.proof(); + if (sol.outcome()==UNSATISFIABLE) { + // only perform the minimization if it was UNSATISFIABLE, rather than TRIVIALLY_UNSATISFIABLE + int i = p.highLevelCore().size(); + rep.minimizing(cmd, i); + if (opt.coreMinimization==0) try { p.minimize(new RCEStrategy(p.log())); } catch(Throwable ex) {} + if (opt.coreMinimization==1) try { p.minimize(new HybridStrategy(p.log())); } catch(Throwable ex) {} + rep.minimized(cmd, i, p.highLevelCore().size()); + } + for(Iterator it=p.core(); it.hasNext();) { + Object n=it.next().node(); + if (n instanceof Formula) lCore.add((Formula)n); + } + Map map = p.highLevelCore(); + hCore = new LinkedHashSet(map.keySet()); + hCore.addAll(map.values()); + } catch(Throwable ex) { + lCore = hCore = null; + } + } + // If satisfiable, then add/rename the atoms and skolems + if (inst!=null) { + eval = new Evaluator(inst, solver.options()); + rename(this, null, null, new UniqueNameGenerator()); + } + // report the result + solved(); + time = System.currentTimeMillis() - time; + if (inst!=null) rep.resultSAT(cmd, time, this); else rep.resultUNSAT(cmd, time, this); + return this; + } + + //===================================================================================================// + + /** This caches the toString() output. */ + private String toStringCache = null; + + /** Dumps the Kodkod solution into String. */ + @Override public String toString() { + if (!solved) return "---OUTCOME---\nUnknown.\n"; + if (eval == null) return "---OUTCOME---\nUnsatisfiable.\n"; + String answer = toStringCache; + if (answer != null) return answer; + Instance sol = eval.instance(); + StringBuilder sb = new StringBuilder(); + sb.append("---INSTANCE---\n" + "integers={"); + boolean firstTuple = true; + for(IndexedEntry e:sol.intTuples()) { + if (firstTuple) firstTuple=false; else sb.append(", "); + // No need to print e.index() since we've ensured the Int atom's String representation is always equal to ""+e.index() + Object atom = e.value().iterator().next().atom(0); + sb.append(atom2name(atom)); + } + sb.append("}\n"); + try { + for(Sig s:sigs) { + sb.append(s.label).append("=").append(eval(s)).append("\n"); + for(Field f:s.getFields()) sb.append(s.label).append("<:").append(f.label).append("=").append(eval(f)).append("\n"); + } + for(ExprVar v:skolems) { + sb.append("skolem ").append(v.label).append("=").append(eval(v)).append("\n"); + } + return toStringCache = sb.toString(); + } catch(Err er) { + return toStringCache = (""); + } + } + + //===================================================================================================// + + /** If nonnull, it caches the result of calling "next()". */ + private A4Solution nextCache = null; + + /** If this solution is UNSAT, return itself; else return the next solution (which could be SAT or UNSAT). + * @throws ErrorAPI if the solver was not an incremental solver + */ + public A4Solution next() throws Err { + if (!solved) throw new ErrorAPI("This solution is not yet solved, so next() is not allowed."); + if (eval==null) return this; + if (nextCache==null) nextCache=new A4Solution(this); + return nextCache; + } + + /** Returns true if this solution was generated by an incremental SAT solver. */ + public boolean isIncremental() { return kEnumerator!=null; } + + //===================================================================================================// + + /** The low-level unsat core; null if it is not available. */ + private LinkedHashSet lCore = null; + + /** This caches the result of lowLevelCore(). */ + private Set lCoreCache = null; + + /** If this solution is unsatisfiable and its unsat core is available, then return the core; else return an empty set. */ + public Set lowLevelCore() { + if (lCoreCache!=null) return lCoreCache; + Set ans1 = new LinkedHashSet(); + if (lCore!=null) for(Node f: lCore) { + Object y = k2pos(f); + if (y instanceof Pos) ans1.add( (Pos)y ); else if (y instanceof Expr) ans1.add( ((Expr)y).span() ); + } + return lCoreCache = Collections.unmodifiableSet(ans1); + } + + //===================================================================================================// + + /** The high-level unsat core; null if it is not available. */ + private LinkedHashSet hCore = null; + + /** This caches the result of highLevelCore(). */ + private Pair,Set> hCoreCache = null; + + /** If this solution is unsatisfiable and its unsat core is available, then return the core; else return an empty set. */ + public Pair,Set> highLevelCore() { + if (hCoreCache!=null) return hCoreCache; + Set ans1 = new LinkedHashSet(), ans2 = new LinkedHashSet(); + if (hCore!=null) for(Node f: hCore) { + Object x = k2pos(f); + if (x instanceof Pos) { + // System.out.println("F: "+f+" at "+x+"\n"); System.out.flush(); + ans1.add((Pos)x); + } else if (x instanceof Expr) { + Expr expr = (Expr)x; + Pos p = ((Expr)x).span(); + ans1.add(p); + // System.out.println("F: "+f+" by "+p.x+","+p.y+"->"+p.x2+","+p.y2+" for "+x+"\n\n"); System.out.flush(); + for(Func func: expr.findAllFunctions()) ans2.add(func.getBody().span()); + } + } + return hCoreCache = new Pair,Set>(Collections.unmodifiableSet(ans1), Collections.unmodifiableSet(ans2)); + } + + //===================================================================================================// + + /** Helper method to write out a full XML file. */ + public void writeXML(String filename) throws Err { + writeXML(filename, null, null); + } + + /** Helper method to write out a full XML file. */ + public void writeXML(String filename, Iterable macros) throws Err { + writeXML(filename, macros, null); + } + + /** Helper method to write out a full XML file. */ + public void writeXML(String filename, Iterable macros, Map sourceFiles) throws Err { + PrintWriter out=null; + try { + out=new PrintWriter(filename,"UTF-8"); + writeXML(out, macros, sourceFiles); + if (!Util.close(out)) throw new ErrorFatal("Error writing the solution XML file."); + } catch(IOException ex) { + Util.close(out); + throw new ErrorFatal("Error writing the solution XML file.", ex); + } + } + + /** Helper method to write out a full XML file. */ + public void writeXML(A4Reporter rep, String filename, Iterable macros, Map sourceFiles) throws Err { + PrintWriter out=null; + try { + out=new PrintWriter(filename,"UTF-8"); + writeXML(rep, out, macros, sourceFiles); + if (!Util.close(out)) throw new ErrorFatal("Error writing the solution XML file."); + } catch(IOException ex) { + Util.close(out); + throw new ErrorFatal("Error writing the solution XML file.", ex); + } + } + + /** Helper method to write out a full XML file. */ + public void writeXML(PrintWriter writer, Iterable macros, Map sourceFiles) throws Err { + A4SolutionWriter.writeInstance(null, this, writer, macros, sourceFiles); + if (writer.checkError()) throw new ErrorFatal("Error writing the solution XML file."); + } + + /** Helper method to write out a full XML file. */ + public void writeXML(A4Reporter rep, PrintWriter writer, Iterable macros, Map sourceFiles) throws Err { + A4SolutionWriter.writeInstance(rep, this, writer, macros, sourceFiles); + if (writer.checkError()) throw new ErrorFatal("Error writing the solution XML file."); + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/translator/A4SolutionReader.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/translator/A4SolutionReader.java new file mode 100644 index 00000000..83a998a8 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/translator/A4SolutionReader.java @@ -0,0 +1,314 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4compiler.translator; + +import static edu.mit.csail.sdg.alloy4compiler.ast.Sig.UNIV; +import static edu.mit.csail.sdg.alloy4compiler.ast.Sig.SIGINT; +import static edu.mit.csail.sdg.alloy4compiler.ast.Sig.SEQIDX; +import static edu.mit.csail.sdg.alloy4compiler.ast.Sig.STRING; +import static edu.mit.csail.sdg.alloy4compiler.ast.Sig.NONE; +import java.io.IOException; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; +import kodkod.ast.Relation; +import kodkod.instance.Tuple; +import kodkod.instance.TupleFactory; +import kodkod.instance.TupleSet; +import edu.mit.csail.sdg.alloy4.Err; +import edu.mit.csail.sdg.alloy4.ErrorFatal; +import edu.mit.csail.sdg.alloy4.ErrorSyntax; +import edu.mit.csail.sdg.alloy4.OurUtil; +import edu.mit.csail.sdg.alloy4.Pos; +import edu.mit.csail.sdg.alloy4.XMLNode; +import edu.mit.csail.sdg.alloy4.Util; +import edu.mit.csail.sdg.alloy4compiler.ast.Attr; +import edu.mit.csail.sdg.alloy4compiler.ast.Expr; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprVar; +import edu.mit.csail.sdg.alloy4compiler.ast.Sig; +import edu.mit.csail.sdg.alloy4compiler.ast.Sig.Field; +import edu.mit.csail.sdg.alloy4compiler.ast.Sig.PrimSig; +import edu.mit.csail.sdg.alloy4compiler.ast.Sig.SubsetSig; + +/** This helper class contains helper routines for reading an A4Solution object from an XML file. */ + +public final class A4SolutionReader { + + /** The resulting A4Solution object. */ + private final A4Solution sol; + + /** The provided choices of existing Sig and Field. */ + private final LinkedHashSet choices = new LinkedHashSet(); + + /** Stores the set of atoms. */ + private final TreeSet atoms = new TreeSet(); + + /** Stores the set of STRING atoms. */ + private final TreeSet strings = new TreeSet(); + + /** Maps each Sig/Field/Skolem id to an XMLNode. */ + private final Map nmap = new LinkedHashMap(); + + /** Maps each Sig id to a Sig. */ + private final Map id2sig = new LinkedHashMap(); + + /** Stores the set of all sigs. */ + private final Set allsigs = Util.asSet((Sig)UNIV, SIGINT, SEQIDX, STRING, NONE); + + /** Mapes each expression we've seen to its TupleSet. */ + private final Map expr2ts = new LinkedHashMap(); + + /** The Kodkod tupleset factory. */ + private final TupleFactory factory; + + /** Helper method that returns true if the given attribute value in the given XML node is equal to "yes" */ + private static boolean yes(XMLNode node, String attr) { return node.getAttribute(attr).equals("yes"); } + + /** Helper method that returns an XML node's "label" attribute. */ + private static String label(XMLNode node) { return node.getAttribute("label"); } + + /** Helper method that returns true if the two iterables contain the same elements (though possibly in different order) */ + private static boolean sameset(Iterable a, Iterable b) { + ArrayList tmp=new ArrayList(); + for(Sig x:b) tmp.add(x); + for(Sig x:a) if (tmp.contains(x)) tmp.remove(x); else return false; + return tmp.isEmpty(); + } + + /** Parse tuple. */ + private Tuple parseTuple(XMLNode tuple, int arity) throws Err { + Tuple ans = null; + try { + for(XMLNode sub:tuple) if (sub.is("atom")) { + Tuple x = factory.tuple(sub.getAttribute("label")); + if (ans==null) ans=x; else ans=ans.product(x); + } + if (ans==null) throw new ErrorFatal("Expecting: .. "); + if (ans.arity()!=arity) throw new ErrorFatal("Expecting: tuple of arity "+arity+" but got tuple of arity "+ans.arity()); + return ans; + } catch(Throwable ex) { + throw new ErrorFatal("Expecting: .. ", ex); + } + } + + /** Parse tuples. */ + private TupleSet parseTuples(XMLNode tuples, int arity) throws Err { + TupleSet ans = factory.noneOf(arity); + for(XMLNode sub:tuples) if (sub.is("tuple")) ans.add(parseTuple(sub, arity)); + return ans; + } + + /** Parse sig/set. */ + private Sig parseSig(String id, int depth) throws IOException, Err { + Sig ans = id2sig.get(id); + if (ans!=null) return ans; + XMLNode node = nmap.get(id); + if (node==null) throw new IOException("Unknown SigID "+id+" encountered."); + if (!node.is("sig")) throw new IOException("ID "+id+" is not a sig."); + String label = label(node); + Attr isAbstract = yes(node,"abstract") ? Attr.ABSTRACT : null; + Attr isOne = yes(node,"one") ? Attr.ONE : null; + Attr isLone = yes(node,"lone") ? Attr.LONE : null; + Attr isSome = yes(node,"some") ? Attr.SOME : null; + Attr isPrivate = yes(node,"private") ? Attr.PRIVATE : null; + Attr isMeta = yes(node,"meta") ? Attr.META : null; + Attr isEnum = yes(node,"enum") ? Attr.ENUM : null; + Attr isExact = yes(node,"exact") ? Attr.EXACT : null; + if (yes(node,"builtin")) { + if (label.equals(UNIV.label)) { id2sig.put(id, UNIV); return UNIV; } + if (label.equals(SIGINT.label)) { id2sig.put(id, SIGINT); return SIGINT; } + if (label.equals(SEQIDX.label)) { id2sig.put(id, SEQIDX); return SEQIDX; } + if (label.equals(STRING.label)) { id2sig.put(id, STRING); return STRING; } + throw new IOException("Unknown builtin sig: "+label+" (id="+id+")"); + } + if (depth > nmap.size()) throw new IOException("Sig "+label+" (id="+id+") is in a cyclic inheritance relationship."); + List parents = null; + TupleSet ts = factory.noneOf(1); + for(XMLNode sub:node) { + if (sub.is("atom")) { ts.add(factory.tuple(sub.getAttribute("label"))); continue; } + if (!sub.is("type")) continue; + Sig parent = parseSig(sub.getAttribute("ID"), depth+1); + if (parents==null) parents = new ArrayList(); + parents.add(parent); + } + if (parents==null) { + String parentID = node.getAttribute("parentID"); + Sig parent = parseSig(parentID, depth+1); + if (!(parent instanceof PrimSig)) throw new IOException("Parent of sig "+label+" (id="+id+") must not be a subset sig."); + for(Expr choice: choices) + if (choice instanceof PrimSig && parent==((PrimSig)choice).parent && ((Sig)choice).label.equals(label)) + { ans=(Sig)choice; choices.remove(choice); break; } + if (ans==null) { + ans = new PrimSig(label, (PrimSig)parent, isAbstract, isLone, isOne, isSome, isPrivate, isMeta, isEnum); + allsigs.add(ans); + } + } else { + for(Expr choice:choices) + if (choice instanceof SubsetSig && ((Sig)choice).label.equals(label) && sameset(parents, ((SubsetSig)choice).parents)) + { ans=(Sig)choice; choices.remove(choice); break; } + if (ans==null) { + ans = new SubsetSig(label, parents, isExact, isLone, isOne, isSome, isPrivate, isMeta); + allsigs.add(ans); + } + } + id2sig.put(id, ans); + expr2ts.put(ans, ts); + if (ans instanceof PrimSig) { + // Add the atoms in this SIG into all parent sigs + for(PrimSig ans2 = ((PrimSig)ans).parent; ans2!=null && !ans2.builtin; ans2 = ans2.parent) { + TupleSet ts2 = expr2ts.get(ans2); + if (ts2==null) ts2 = ts.clone(); else { ts2 = ts2.clone(); ts2.addAll(ts); } + expr2ts.put(ans2, ts2); + } + } + return ans; + } + + /** Parse type. */ + private Expr parseType(XMLNode node) throws IOException, Err { + Expr expr = null; + if (!node.is("types")) throw new IOException("... expected"); + for(XMLNode n:node) if (n.is("type")) { + Sig sig=parseSig(n.getAttribute("ID"), 0); + if (expr==null) expr=sig; else expr=expr.product(sig); + } + if (expr==null) throw new IOException(" expected"); + return expr; + } + + /** Parse field. */ + private Field parseField(String id) throws IOException, Err { + final XMLNode node = nmap.get(id); + if (node==null) throw new IOException("Unknown FieldID "+id+" encountered."); + if (!node.is("field")) throw new IOException("ID "+id+" is not a field."); + String label = label(node); + Pos isPrivate = yes(node,"private") ? Pos.UNKNOWN : null; + Pos isMeta = yes(node,"meta") ? Pos.UNKNOWN : null; + Expr type = null; + for(XMLNode sub:node) if (sub.is("types")) { Expr t=parseType(sub); if (type==null) type=t; else type=type.plus(t); } + int arity; + if (type==null || (arity=type.type().arity())<2) throw new IOException("Field "+label+" is maltyped."); + String parentID = node.getAttribute("parentID"); + Sig parent = id2sig.get(parentID); + if (parent==null) throw new IOException("ID "+parentID+" is not a sig."); + Field field = null; + for(Field f: parent.getFields()) + if (f.label.equals(label) && f.type().arity()==arity && choices.contains(f)) + { field=f; choices.remove(f); break; } + if (field==null) field = parent.addTrickyField(Pos.UNKNOWN, isPrivate, null, null, isMeta, new String[] {label}, UNIV.join(type)) [0]; + TupleSet ts = parseTuples(node, arity); + expr2ts.put(field, ts); + return field; + } + + /** Parse skolem. */ + private ExprVar parseSkolem(String id) throws IOException, Err { + final XMLNode node = nmap.get(id); + if (node==null) throw new IOException("Unknown ID "+id+" encountered."); + if (!node.is("skolem")) throw new IOException("ID "+id+" is not a skolem."); + String label = label(node); + Expr type = null; + for(XMLNode sub:node) if (sub.is("types")) { Expr t=parseType(sub); if (type==null) type=t; else type=type.plus(t); } + int arity; + if (type==null || (arity=type.type().arity())<1) throw new IOException("Skolem "+label+" is maltyped."); + ExprVar var = ExprVar.make(Pos.UNKNOWN, label, type.type()); + TupleSet ts = parseTuples(node, arity); + expr2ts.put(var, ts); + return var; + } + + /** Parse everything. */ + private A4SolutionReader(Iterable sigs, XMLNode xml) throws IOException, Err { + for(Sig s:sigs) if (!s.builtin) { + allsigs.add(s); + choices.add(s); + for(Field f:s.getFields()) choices.add(f); + } + // find .. + if (!xml.is("alloy")) throw new ErrorSyntax("The XML file's root node must be or ."); + XMLNode inst = null; + for(XMLNode sub: xml) if (sub.is("instance")) { inst=sub; break; } + if (inst==null) throw new ErrorSyntax("The XML file must contain an element."); + // set up the basic values of the A4Solution object + final int bitwidth = Integer.parseInt(inst.getAttribute("bitwidth")); + final int maxseq = Integer.parseInt(inst.getAttribute("maxseq")); + final int max = Util.max(bitwidth), min = Util.min(bitwidth); + if (bitwidth>=1 && bitwidth<=30) for(int i=min; i<=max; i++) { atoms.add(Integer.toString(i)); } + for(XMLNode x:inst) { + String id=x.getAttribute("ID"); + if (id.length()>0 && (x.is("field") || x.is("skolem") || x.is("sig"))) { + if (nmap.put(id, x)!=null) throw new IOException("ID "+id+" is repeated."); + if (x.is("sig")) { + boolean isString = STRING.label.equals(label(x)) && yes(x, "builtin"); + for(XMLNode y:x) if (y.is("atom")) { + String attr = y.getAttribute("label"); + atoms.add(attr); + if (isString) strings.add(attr); + } + } + } + } + // create the A4Solution object + A4Options opt = new A4Options(); + opt.originalFilename = inst.getAttribute("filename"); + sol = new A4Solution(inst.getAttribute("command"), bitwidth, maxseq, strings, atoms, null, opt, 1); + factory = sol.getFactory(); + // parse all the sigs, fields, and skolems + for(Map.Entry e:nmap.entrySet()) if (e.getValue().is("sig")) parseSig(e.getKey(), 0); + for(Map.Entry e:nmap.entrySet()) if (e.getValue().is("field")) parseField(e.getKey()); + for(Map.Entry e:nmap.entrySet()) if (e.getValue().is("skolem")) parseSkolem(e.getKey()); + for(Sig s:allsigs) if (!s.builtin) { + TupleSet ts = expr2ts.remove(s); + if (ts==null) ts = factory.noneOf(1); // If the sig was NOT mentioned in the XML file... + Relation r = sol.addRel(s.label, ts, ts); + sol.addSig(s, r); + for(Field f: s.getFields()) { + ts = expr2ts.remove(f); + if (ts==null) ts=factory.noneOf(f.type().arity()); // If the field was NOT mentioned in the XML file... + r = sol.addRel(s.label+"."+f.label, ts, ts); + sol.addField(f, r); + } + } + for(Map.Entry e:expr2ts.entrySet()) { + ExprVar v = (ExprVar)(e.getKey()); + TupleSet ts = e.getValue(); + Relation r = sol.addRel(v.label, ts, ts); + sol.kr2type(r, v.type()); + } + // Done! + sol.solve(null, null, null, false); + } + + /** Parse the XML element into an AlloyInstance. + * + *

The list of sigs, if not null, will be used as the sigs (and their fields) that we expect to exist; + *
if there's a sig or field X in the list but not in the XML, then X's tupleset will be regarded as empty; + *
if there's a sig or field X in the XML but not in the list, then X (and its value in XML file) is added to the solution. + */ + public static A4Solution read(Iterable sigs, XMLNode xml) throws Err { + try { + if (sigs == null) sigs = new ArrayList(); + A4SolutionReader x = new A4SolutionReader(sigs, xml); + return x.sol; + } catch(Throwable ex) { + if (ex instanceof Err) throw ((Err)ex); else throw new ErrorFatal("Fatal error occured: "+ex, ex); + } + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/translator/A4SolutionWriter.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/translator/A4SolutionWriter.java new file mode 100644 index 00000000..28ab378d --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/translator/A4SolutionWriter.java @@ -0,0 +1,233 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4compiler.translator; + +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; +import edu.mit.csail.sdg.alloy4.A4Reporter; +import edu.mit.csail.sdg.alloy4.ConstList; +import edu.mit.csail.sdg.alloy4.Err; +import edu.mit.csail.sdg.alloy4.ErrorAPI; +import edu.mit.csail.sdg.alloy4.ErrorFatal; +import edu.mit.csail.sdg.alloy4.Util; +import edu.mit.csail.sdg.alloy4.Version; +import edu.mit.csail.sdg.alloy4compiler.ast.Expr; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprVar; +import edu.mit.csail.sdg.alloy4compiler.ast.Func; +import edu.mit.csail.sdg.alloy4compiler.ast.Sig; +import edu.mit.csail.sdg.alloy4compiler.ast.Type; +import edu.mit.csail.sdg.alloy4compiler.ast.Sig.Field; +import edu.mit.csail.sdg.alloy4compiler.ast.Sig.PrimSig; +import edu.mit.csail.sdg.alloy4compiler.ast.Sig.SubsetSig; + +/** This helper class contains helper routines for writing an A4Solution object out as an XML file. */ + +public final class A4SolutionWriter { + + /** Maps each Sig, Field, and Skolem to a unique id. */ + private final IdentityHashMap map = new IdentityHashMap(); + + /** This is the solution we're writing out. */ + private final A4Solution sol; + + /** This is the A4Reporter that we're sending diagnostic messages to; can be null if none. */ + private final A4Reporter rep; + + /** This is the list of toplevel sigs. */ + private final List toplevels = new ArrayList(); + + /** This is the output file. */ + private final PrintWriter out; + + /** Helper method that returns a unique id for the given Sig, Field, or Skolem. */ + private String map(Expr obj) { + String id = map.get(obj); + if (id==null) { id=Integer.toString(map.size()); map.put(obj, id); } + return id; + } + + /** Helper method that returns the list of direct subsignatures. */ + private Iterable children(PrimSig x) throws Err { + if (x==Sig.NONE) return new ArrayList(); + if (x!=Sig.UNIV) return x.children(); else return toplevels; + } + + /** Write the given Expr and its Type. */ + private boolean writeExpr(String prefix, Expr expr) throws Err { + Type type = expr.type(); + if (!type.hasTuple()) return false; + if (sol!=null) { + // Check to see if the tupleset is *really* fully contained inside "type". + // If not, then grow "type" until the tupleset is fully contained inside "type" + Expr sum = type.toExpr(); + int lastSize = (-1); + while(true) { + A4TupleSet ts = (A4TupleSet)(sol.eval(expr.minus(sum))); + int n = ts.size(); + if (n<=0) break; + if (lastSize>0 && lastSize<=n) throw new ErrorFatal("An internal error occurred in the evaluator."); + lastSize=n; + Type extra = ts.iterator().next().type(); + type = type.merge(extra); + sum = sum.plus(extra.toExpr()); + } + // Now, write out the tupleset + A4TupleSet ts = (A4TupleSet)(sol.eval(expr)); + for(A4Tuple t: ts) { + if (prefix.length()>0) { out.print(prefix); prefix=""; } + out.print(" "); + for(int i=0; i"); + out.print(" \n"); + } + } + // Now, write out the type + if (prefix.length()>0) return false; + for(List ps: type.fold()) { + out.print(" "); + for(PrimSig sig: ps) Util.encodeXMLs(out, " "); + out.print(" \n"); + } + return true; + } + + /** Write the given Sig. */ + private A4TupleSet writesig(final Sig x) throws Err { + A4TupleSet ts = null, ts2 = null; + if (x==Sig.NONE) return null; // should not happen, but we test for it anyway + if (sol==null && x.isMeta!=null) return null; // When writing the metamodel, skip the metamodel sigs! + if (x instanceof PrimSig) for(final PrimSig sub:children((PrimSig)x)) { + A4TupleSet ts3 = writesig(sub); + if (ts2==null) ts2 = ts3; else ts2 = ts2.plus(ts3); + } + if (rep!=null) rep.write(x); + Util.encodeXMLs(out, "\n\n"); + try { + if (sol!=null && x!=Sig.UNIV && x!=Sig.SIGINT && x!=Sig.SEQIDX) { + ts = (A4TupleSet)(sol.eval(x)); + for(A4Tuple t: ts.minus(ts2)) Util.encodeXMLs(out, " \n"); + } + } catch(Throwable ex) { + throw new ErrorFatal("Error evaluating sig " + x.label, ex); + } + if (x instanceof SubsetSig) for(Sig p:((SubsetSig)x).parents) Util.encodeXMLs(out, " \n"); + out.print("\n"); + for(Field field: x.getFields()) writeField(field); + return ts; + } + + /** Write the given Field. */ + private void writeField(Field x) throws Err { + try { + if (sol==null && x.isMeta!=null) return; // when writing the metamodel, skip the metamodel fields! + if (x.type().hasNoTuple()) return; // we do not allow "none" in the XML file's type declarations + if (rep!=null) rep.write(x); + Util.encodeXMLs(out, "\n\n"); + writeExpr("", x); + out.print("\n"); + } catch(Throwable ex) { + throw new ErrorFatal("Error evaluating field "+x.sig.label+"."+x.label, ex); + } + } + + /** Write the given Skolem. */ + private void writeSkolem(ExprVar x) throws Err { + try { + if (sol==null) return; // when writing a metamodel, skip the skolems + if (x.type().hasNoTuple()) return; // we do not allow "none" in the XML file's type declarations + StringBuilder sb = new StringBuilder(); + Util.encodeXMLs(sb, "\n\n"); + if (writeExpr(sb.toString(), x)) { out.print("\n"); } + } catch(Throwable ex) { + throw new ErrorFatal("Error evaluating skolem "+x.label, ex); + } + } + + /** If sol==null, write the list of Sigs as a Metamodel, else write the solution as an XML file. */ + private A4SolutionWriter(A4Reporter rep, A4Solution sol, Iterable sigs, int bitwidth, int maxseq, String originalCommand, String originalFileName, PrintWriter out, Iterable extraSkolems) throws Err { + this.rep = rep; + this.out = out; + this.sol = sol; + for (Sig s:sigs) if (s instanceof PrimSig && ((PrimSig)s).parent==Sig.UNIV) toplevels.add((PrimSig)s); + out.print("\n"); + writesig(Sig.UNIV); + for (Sig s:sigs) if (s instanceof SubsetSig) writesig(s); + if (sol!=null) for (ExprVar s:sol.getAllSkolems()) { if (rep!=null) rep.write(s); writeSkolem(s); } + int m=0; + if (sol!=null && extraSkolems!=null) for(Func f:extraSkolems) if (f.count()==0 && f.call().type().hasTuple()) { + String label = f.label; + while(label.length()>0 && label.charAt(0)=='$') label=label.substring(1); + label="$"+label; + try { + if (rep!=null) rep.write(f.call()); + StringBuilder sb = new StringBuilder(); + Util.encodeXMLs(sb, "\n\n"); + if (writeExpr(sb.toString(), f.call())) { out.print("\n"); } + m++; + } catch(Throwable ex) { + throw new ErrorFatal("Error evaluating skolem "+label, ex); + } + } + out.print("\n\n"); + } + + /** If this solution is a satisfiable solution, this method will write it out in XML format. */ + static void writeInstance(A4Reporter rep, A4Solution sol, PrintWriter out, Iterable extraSkolems, Map sources) throws Err { + if (!sol.satisfiable()) throw new ErrorAPI("This solution is unsatisfiable."); + try { + Util.encodeXMLs(out, "\n\n"); + new A4SolutionWriter(rep, sol, sol.getAllReachableSigs(), sol.getBitwidth(), sol.getMaxSeq(), sol.getOriginalCommand(), sol.getOriginalFilename(), out, extraSkolems); + if (sources!=null) for(Map.Entry e: sources.entrySet()) { + Util.encodeXMLs(out, "\n\n"); + } + out.print("\n\n"); + } catch(Throwable ex) { + if (ex instanceof Err) throw (Err)ex; else throw new ErrorFatal("Error writing the solution XML file.", ex); + } + if (out.checkError()) throw new ErrorFatal("Error writing the solution XML file."); + } + + /** Write the metamodel as <instance>..</instance> in XML format. */ + public static void writeMetamodel(ConstList sigs, String originalFilename, PrintWriter out) throws Err { + try { + new A4SolutionWriter(null, null, sigs, 4, 4, "show metamodel", originalFilename, out, null); + } catch(Throwable ex) { + if (ex instanceof Err) throw (Err)ex; else throw new ErrorFatal("Error writing the solution XML file.", ex); + } + if (out.checkError()) throw new ErrorFatal("Error writing the solution XML file."); + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/translator/A4Tuple.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/translator/A4Tuple.java new file mode 100644 index 00000000..b4b6f91b --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/translator/A4Tuple.java @@ -0,0 +1,65 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4compiler.translator; + +import edu.mit.csail.sdg.alloy4compiler.ast.Type; +import edu.mit.csail.sdg.alloy4compiler.ast.Sig.PrimSig; +import kodkod.instance.Tuple; + +/** Immutable; represents a single Alloy tuple; comparison is by identity rather than by value. */ + +public final class A4Tuple { + + /** The Kodkod tuple. */ + private final Tuple tuple; + + /** The A4Solution that this came from. */ + private final A4Solution sol; + + /** Construct a Tuple from the kodkod Tuple, while renaming each atom using the atom2name map in sol. + *
NOTE: caller must ensure the Kodkod tuple is not modified, since we expect the resulting A4Tuple to be constant. + */ + A4Tuple(Tuple tuple, A4Solution sol) { + this.tuple = tuple; + this.sol = sol; + } + + /** Returns the arity. */ + public int arity() { return tuple.arity(); } + + /** Returns the type constructed by taking the product for each sig in this tuple. */ + public Type type() { + Type ans = null; + for(int i=0; i0) sb.append("->"); + sb.append(atom(i)); + } + return sb.toString(); + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/translator/A4TupleSet.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/translator/A4TupleSet.java new file mode 100644 index 00000000..5ced6fed --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/translator/A4TupleSet.java @@ -0,0 +1,116 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4compiler.translator; + +import java.util.Iterator; +import java.util.NoSuchElementException; +import edu.mit.csail.sdg.alloy4.ErrorAPI; +import kodkod.instance.Tuple; +import kodkod.instance.TupleSet; + +/** Immutable; represents a collection of Alloy tuples; comparison is by identity rather than by value. */ + +public final class A4TupleSet implements Iterable { + + /** The Kodkod tupleset. */ + private final TupleSet tuples; + + /** The A4Solution that this came from. */ + private final A4Solution sol; + + /** Construct a TupleSet from the kodkod TupleSet, while renaming each atom using the atom2name map in sol. + *
NOTE: caller must ensure the Kodkod tupleset is not modified, since we expect the resulting A4Tupleset to be constant. + */ + A4TupleSet(TupleSet tuples, A4Solution sol) { + this.tuples = tuples; + this.sol = sol; + } + + /** Return the underlying Kodkod tupleset. */ + public TupleSet debugGetKodkodTupleset() { return tuples.clone(); } + + /** Returns a read-only iterator that iterates over each tuple in this TupleSet. */ + public Iterator iterator() { + return new Iterator() { + private final Iterator it = tuples.iterator(); + public final boolean hasNext() { return it.hasNext(); } + public final A4Tuple next() { + if (!it.hasNext()) throw new NoSuchElementException(); + return new A4Tuple(it.next(), sol); + } + public final void remove() { throw new UnsupportedOperationException(); } + }; + } + + /** Returns the arity. */ + public int arity() { return tuples.arity(); } + + /** Returns the number of tuples in this tuple set. */ + public int size() { return tuples.size(); } + + /** Construct a new tupleset as the product of this and that; this and that must be come from the same solution. */ + public A4TupleSet product(A4TupleSet that) throws ErrorAPI { + if (sol != that.sol) throw new ErrorAPI("A4TupleSet.product() requires 2 tuplesets from the same A4Solution."); + return new A4TupleSet(tuples.product(that.tuples), sol); + } + + /** Construct a new tupleset as the union of this and that; this and that must be come from the same solution. + * Note: if that==null, then the method returns this A4TupleSet as-is. */ + public A4TupleSet plus(A4TupleSet that) throws ErrorAPI { + if (that==null) return this; + if (sol != that.sol) throw new ErrorAPI("A4TupleSet.plus() requires 2 tuplesets from the same A4Solution."); + if (arity() != that.arity()) throw new ErrorAPI("A4TupleSet.plus() requires 2 tuplesets with the same arity."); + if (this==that || tuples.size()==0) return that; else if (that.tuples.size()==0) return this; // special short cut + TupleSet ts = tuples.clone(); + ts.addAll(that.tuples); + if (tuples.size()==ts.size()) return this; + if (that.tuples.size()==ts.size()) return that; + return new A4TupleSet(ts, sol); + } + + /** Construct a new tupleset as the subtraction of this and that; this and that must be come from the same solution. + * Note: if that==null, then the method returns this A4TupleSet as-is. */ + public A4TupleSet minus(A4TupleSet that) throws ErrorAPI { + if (that==null) return this; + if (sol != that.sol) throw new ErrorAPI("A4TupleSet.minus() requires 2 tuplesets from the same A4Solution."); + if (arity() != that.arity()) throw new ErrorAPI("A4TupleSet.minus() requires 2 tuplesets with the same arity."); + if (tuples.size()==0 || that.tuples.size()==0) return this; // special short cut + TupleSet ts = tuples.clone(); + ts.removeAll(that.tuples); + if (tuples.size()!=ts.size()) return new A4TupleSet(ts, sol); else return this; + } + + /** Construct a new tupleset as the intersection of this and that; this and that must be come from the same solution. */ + public A4TupleSet intersect(A4TupleSet that) throws ErrorAPI { + if (sol != that.sol) throw new ErrorAPI("A4TupleSet.intersect() requires 2 tuplesets from the same A4Solution."); + if (arity() != that.arity()) throw new ErrorAPI("A4TupleSet.intersect() requires 2 tuplesets with the same arity."); + if (this.tuples.size()==0) return this; // special short cut + if (that.tuples.size()==0) return that; // special short cut + TupleSet ts = tuples.clone(); + ts.retainAll(that.tuples); + if (tuples.size()!=ts.size()) return new A4TupleSet(ts, sol); else return this; + } + + /** Prints a human-readable description of this TupleSet. */ + @Override public String toString() { + StringBuilder sb=new StringBuilder("{"); + for(A4Tuple t:this) { + if (sb.length()>1) sb.append(", "); + sb.append(t); + } + return sb.append('}').toString(); + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/translator/BookExamples.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/translator/BookExamples.java new file mode 100644 index 00000000..17da3a0b --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/translator/BookExamples.java @@ -0,0 +1,374 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4compiler.translator; + +import static kodkod.engine.Solution.Outcome.SATISFIABLE; +import static kodkod.engine.Solution.Outcome.TRIVIALLY_SATISFIABLE; +import edu.mit.csail.sdg.alloy4.A4Reporter; +import edu.mit.csail.sdg.alloy4compiler.ast.Sig; +import edu.mit.csail.sdg.alloy4compiler.ast.Sig.Field; +import kodkod.ast.BinaryExpression; +import kodkod.ast.Formula; +import kodkod.ast.Relation; +import kodkod.ast.Expression; +import kodkod.engine.Solver; +import kodkod.engine.Solution; +import kodkod.engine.satlab.SATFactory; +import kodkod.instance.Bounds; +import kodkod.instance.Tuple; +import kodkod.instance.TupleFactory; +import kodkod.instance.TupleSet; + +/** Immutable; this class stores the set of solutions from the book, for teaching purpose, + * so that users of the tool will see the same illustration as the book and not get confused by SAT solver nondeterminism. + */ + +final class BookExamples { + + // It calls the following methods on a bounds-computed A4Solution object: + // getAllReachableSigs(), getFactory(), getBounds(), a2k(), kr2typeCLEAR() + + /** Returns the Sig if the list of sig contains a sig with the given label, else returns null. */ + private static Sig hasSig (Iterable sigs, String label) { + for(Sig s:sigs) if (s.label.equals(label)) return s; + return null; + } + + /** If one of the solution is a solution to the given problem, return it, else return null. */ + static Solution trial (A4Reporter rep, A4Solution frame, Formula formula, Solver solver, boolean check) { + TupleFactory fac = frame.getFactory(); + Solution sol = null; + Iterable sigs = frame.getAllReachableSigs(); + if (hasSig(sigs, "this/Book")!=null) { + Tuple B0N0A0 = t_tuple(fac, "Book$0", "Name$0", "Addr$0"); + Tuple B0N1A0 = t_tuple(fac, "Book$0", "Name$1", "Addr$0"); + Tuple B0N2A0 = t_tuple(fac, "Book$0", "Name$2", "Addr$0"); + Tuple B0N2A1 = t_tuple(fac, "Book$0", "Name$2", "Addr$1"); + Tuple B0N1A1 = t_tuple(fac, "Book$0", "Name$1", "Addr$1"); + Tuple B1N0A0 = t_tuple(fac, "Book$1", "Name$0", "Addr$0"); + Tuple B1N2A1 = t_tuple(fac, "Book$1", "Name$2", "Addr$1"); + Tuple B1N1A1 = t_tuple(fac, "Book$1", "Name$1", "Addr$1"); + Tuple B000 = t_tuple(fac, "Book$0", "Target$0", "Target$0"); + Tuple B001 = t_tuple(fac, "Book$0", "Target$0", "Target$1"); + Tuple B002 = t_tuple(fac, "Book$0", "Target$0", "Target$2"); + Tuple B010 = t_tuple(fac, "Book$0", "Target$1", "Target$0"); + Tuple B101 = t_tuple(fac, "Book$1", "Target$0", "Target$1"); + Tuple B110 = t_tuple(fac, "Book$1", "Target$1", "Target$0"); + Tuple B102 = t_tuple(fac, "Book$1", "Target$0", "Target$2"); + Tuple B210 = t_tuple(fac, "Book$2", "Target$1", "Target$0"); + Tuple B202 = t_tuple(fac, "Book$2", "Target$0", "Target$2"); + Tuple B212 = t_tuple(fac, "Book$2", "Target$1", "Target$2"); + Tuple B302 = t_tuple(fac, "Book$3", "Target$0", "Target$2"); + Tuple B310 = t_tuple(fac, "Book$3", "Target$1", "Target$0"); + Tuple B312 = t_tuple(fac, "Book$3", "Target$1", "Target$2"); + if (sol==null && B000!=null) + sol=trial(rep, fac, solver, sigs, formula, frame, new Object[]{"Fig 2.9", + "Book$0", "", "this/Book", "", + "Target$0", "", "this/Alias", "", + "", "this/Group", "", + "", "this/Addr", "", + B000, "", "this/Book", "addr", + }); + if (sol==null && B001!=null) + sol=trial(rep, fac, solver, sigs, formula, frame, new Object[]{"Fig 2.10", + "Book$0", "", "this/Book", "", + "", "this/Alias", "", + "Target$0", "", "this/Group", "", + "Target$1", "Target$2", "", "this/Addr", "", + B001, B002, "", "this/Book", "addr", + }); + if (sol==null && B001!=null) + sol=trial(rep, fac, solver, sigs, formula, frame, new Object[]{"Fig 2.11", + "Book$0", "", "this/Book", "", + "Target$0", "", "this/Alias", "", + "", "this/Group", "", + "Target$1", "Target$2", "", "this/Addr", "", + B001, B002, "", "this/Book", "addr", + }); + if (sol==null && B001!=null) + sol=trial(rep, fac, solver, sigs, formula, frame, new Object[]{"Fig 2.12", + "Book$0", "", "this/Book", "", + "Target$0", "", "this/Alias", "", + "Target$1", "", "this/Group", "", + "", "this/Addr", "", + B001, "", "this/Book", "addr", + }); + if (sol==null && B010!=null) + sol=trial(rep, fac, solver, sigs, formula, frame, new Object[]{"Fig 2.13", + "Book$0", "Book$1", "", "this/Book", "", + "", "this/Alias", "", + "Target$0", "Target$1", "", "this/Group", "", + "Target$2", "", "this/Addr", "", + B010, B110, B102, "", "this/Book", "addr", + }); + if (sol==null && B312!=null) + sol=trial(rep, fac, solver, sigs, formula, frame, new Object[]{"Fig 2.15", + "Book$0", "Book$1", "Book$2", "Book$3", "", "this/Book", "", + "", "this/Alias", "", + "Target$0", "Target$1", "", "this/Group", "", + "Target$2", "", "this/Addr", "", + B102, B210, B202, B212, B302, B312, "", "this/Book", "addr", + }); + if (sol==null && B101!=null) + sol=trial(rep, fac, solver, sigs, formula, frame, new Object[]{"Fig 2.16", + "Book$0", "Book$1", "Book$2", "Book$3", "", "this/Book", "", + "Target$1", "", "this/Alias", "", + "Target$0", "", "this/Group", "", + "", "this/Addr", "", + B101, "", "this/Book", "addr", + }); + if (sol==null && B102!=null) + sol=trial(rep, fac, solver, sigs, formula, frame, new Object[]{"Fig 2.17", + "Book$0", "Book$1", "Book$2", "Book$3", "", "this/Book", "", + "Target$0", "", "this/Alias", "", + "Target$1", "", "this/Group", "", + "Target$2", "", "this/Addr", "", + B102, B210, B310, B302, "", "this/Book", "addr", + }); + if (sol==null && B0N0A0!=null && check) + sol=trial(rep, fac, solver, sigs, formula, frame, new Object[]{"Fig 2.6", + "Book$0", "Book$1", "", "this/Book", "", + "Addr$0", "", "this/Addr", "", + "Name$0", "", "this/Name", "", + B0N0A0, "", "this/Book", "addr", + }); + if (sol==null && B1N0A0!=null && !check) + sol=trial(rep, fac, solver, sigs, formula, frame, new Object[]{"Fig 2.4", + "Book$0", "Book$1", "", "this/Book", "", + "Addr$0", "", "this/Addr", "", + "Name$0", "", "this/Name", "", + B1N0A0, "", "this/Book", "addr", + }); + if (sol==null && B0N2A1!=null) + sol=trial(rep, fac, solver, sigs, formula, frame, new Object[]{"Fig 2.5", + "Book$0", "Book$1", "", "this/Book", "", + "Addr$0", "Addr$1", "", "this/Addr", "", + "Name$0", "Name$1", "Name$2", "", "this/Name", "", + B0N2A1, B0N1A1, B1N2A1, B1N1A1, B1N0A0, "", "this/Book", "addr", + }); + if (sol==null && B0N0A0!=null) + sol=trial(rep, fac, solver, sigs, formula, frame, new Object[]{"Fig 2.1", + "Book$0", "", "this/Book", "", + "Addr$0", "", "this/Addr", "", + "Name$0", "", "this/Name", "", + B0N0A0, "", "this/Book", "addr", + }); + if (sol==null && B0N0A0!=null) + sol=trial(rep, fac, solver, sigs, formula, frame, new Object[]{"Fig 2.2", + "Book$0", "", "this/Book", "", + "Addr$0", "", "this/Addr", "", + "Name$0", "Name$1", "Name$2", "", "this/Name", "", + B0N0A0, B0N1A0, B0N2A0, "", "this/Book", "addr", + }); + if (sol==null && B0N0A0!=null) + sol=trial(rep, fac, solver, sigs, formula, frame, new Object[]{"Fig 2.3", + "Book$0", "", "this/Book", "", + "Addr$0", "Addr$1", "", "this/Addr", "", + "Name$0", "Name$1", "Name$2", "", "this/Name", "", + B0N0A0, B0N1A0, B0N2A1, "", "this/Book", "addr", + }); + if (sol==null && B001!=null) + sol=trial(rep, fac, solver, sigs, formula, frame, new Object[]{"Fig 5.2", + "Book$0", "Book$1", "", "this/Book", "", + "Target$0", "", "this/Name", "", + "Target$1", "", "this/Addr", "", + B001, B101, "", "this/Book", "addr", + }); + if (sol==null && B102!=null) + sol=trial(rep, fac, solver, sigs, formula, frame, new Object[]{"Fig 5.3", + "Book$0", "Book$1", "", "this/Book", "", + "Target$0", "Target$1", "", "this/Name", "", + "Target$2", "", "this/Addr", "", + B010, B110, B102, "", "this/Book", "addr", + }); + } + else if (hasSig(sigs, "this/Woman")!=null) { + Tuple man0_woman0 = t_tuple(fac, "Person$1", "Person$0"); + Tuple man1_woman0 = t_tuple(fac, "Person$2", "Person$0"); + Tuple man0_woman1 = t_tuple(fac, "Person$1", "Person$3"); + Tuple man1_woman1 = t_tuple(fac, "Person$2", "Person$3"); + if (sol==null) + sol=trial(rep, fac, solver, sigs, formula, frame, new Object[]{"Fig 4.2", + "Person$1", "", "this/Man", "", + "Person$0", "", "this/Woman", "", + man0_woman0, "", "this/Man", "wife", + man0_woman0, "", "this/Person", "mother", + "", "this/Person", "father", + }); + if (sol==null) + sol=trial(rep, fac, solver, sigs, formula, frame, new Object[]{"Fig 4.3", + "Person$1", "Person$2", "", "this/Man", "", + "Person$0", "Person$3", "", "this/Woman", "", + man1_woman0, man0_woman1, "", "this/Man", "wife", + man1_woman1, man0_woman0, "", "this/Person", "mother", + "", "this/Person", "father", + }); + } + else if (hasSig(sigs, "this/Process")!=null) { + String p0="Process$0", p1="Process$1", p2="Process$2"; + String t0="Time$0", t1="Time$1", t2="Time$2", t3="Time$3"; + Tuple s20=t_tuple(fac,p2,p0), s01=t_tuple(fac,p0,p1), s12=t_tuple(fac,p1,p2); + Tuple d000=t_tuple(fac,p0,p0,t0), d110=t_tuple(fac,p1,p1,t0), d220=t_tuple(fac,p2,p2,t0); + Tuple d001=t_tuple(fac,p0,p0,t1), d021=t_tuple(fac,p0,p2,t1), d111=t_tuple(fac,p1,p1,t1); + Tuple d002=t_tuple(fac,p0,p0,t2), d112=t_tuple(fac,p1,p1,t2), d122=t_tuple(fac,p1,p2,t2); + Tuple d003=t_tuple(fac,p0,p0,t3), d113=t_tuple(fac,p1,p1,t3), d223=t_tuple(fac,p2,p2,t3); + if (sol==null && d000!=null) + sol=trial(rep, fac, solver, sigs, formula, frame, new Object[]{"Fig 6.4", + s20, s01, s12, "", "this/Process", "succ", + d000,d110,d220,d001,d021,d111,d002,d112,d122,d003,d113,d223,"","this/Process","toSend", + t_tuple(fac,p2,t3),"","this/Process","elected", + }); + } + else if (hasSig(sigs, "this/Desk")!=null) { + String g0="Guest$0", g1="Guest$1", r="Room$0", k0="Key$0", k1="Key$1"; + String t0="Time$0", t1="Time$1", t2="Time$2", t3="Time$3", t4="Time$4", t5="Time$5"; + String c0="Card$0", c1="Card$1"; + if (sol==null) + sol=trial(rep, fac, solver, sigs, formula, frame, new Object[]{"Fig E.3", + t_tuple(fac,c0,k0), t_tuple(fac,c1,k1), "", "this/Card", "fst", + t_tuple(fac,c0,k1), t_tuple(fac,c1,k0), "", "this/Card", "snd", + t_tuple(fac,g0,c0,t1), + t_tuple(fac,g0,c0,t2), t_tuple(fac,g1,c1,t2), + t_tuple(fac,g0,c0,t3), t_tuple(fac,g1,c1,t3), + t_tuple(fac,g0,c0,t4), t_tuple(fac,g1,c1,t4), + t_tuple(fac,g0,c0,t5), t_tuple(fac,g1,c1,t5), "", "this/Guest", "cards", + t_tuple(fac,r,k0,t0), t_tuple(fac,r,k0,t1), t_tuple(fac,r,k0,t2), + t_tuple(fac,r,k1,t3), t_tuple(fac,r,k0,t4), t_tuple(fac,r,k1,t5), "", "this/Room", "key", + t_tuple(fac,k1,t1), + t_tuple(fac,k0,t2), t_tuple(fac,k1,t2), + t_tuple(fac,k0,t3), t_tuple(fac,k1,t3), + t_tuple(fac,k0,t4), t_tuple(fac,k1,t4), + t_tuple(fac,k0,t5), t_tuple(fac,k1,t5), "", "this/Desk", "issued", + t_tuple(fac,r,k0,t0), t_tuple(fac,r,k1,t1), t_tuple(fac,r,k0,t2), + t_tuple(fac,r,k0,t3), t_tuple(fac,r,k0,t4), t_tuple(fac,r,k0,t5), "", "this/Desk", "prev" + }); + } + else if (hasSig(sigs, "this/FrontDesk")!=null) { + String g0="Guest$0", g1="Guest$1", r="Room$0", k0="Key$0", k1="Key$1", k2="Key$2"; + String t0="Time$0", t1="Time$1", t2="Time$2", t3="Time$3", t4="Time$4"; + Tuple G0=t_tuple(fac,g0), G1=t_tuple(fac,g1); + Tuple K0=t_tuple(fac,r,k0), K1=t_tuple(fac,r,k1), K2=t_tuple(fac,r,k2); + Tuple K0T0=t_tuple(fac,r,k0,t0), K0T1=t_tuple(fac,r,k0,t1), K0T2=t_tuple(fac,r,k0,t2); + Tuple K0T3=t_tuple(fac,r,k0,t3), K1T4=t_tuple(fac,r,k1,t4); + Tuple F1=t_tuple(fac,r,k0,t0), F2=t_tuple(fac,r,k1,t1), F3=t_tuple(fac,r,k1,t2); + Tuple F4=t_tuple(fac,r,k2,t3), F5=t_tuple(fac,r,k2,t4); + Tuple GK1=t_tuple(fac,g0,k1,t1), GK2=t_tuple(fac,g0,k1,t2), GK3=t_tuple(fac,g0,k1,t3); + Tuple GK4=t_tuple(fac,g1,k2,t3), GK5=t_tuple(fac,g0,k1,t4), GK6=t_tuple(fac,g1,k2,t4); + Tuple O1=t_tuple(fac,r,g0,t1), O2=t_tuple(fac,r,g1,t3), O3=t_tuple(fac,r,g1,t4); + if (sol==null && K0T0!=null) + sol=trial(rep, fac, solver, sigs, formula, frame, new Object[]{"Fig 6.13", + G0, G1, "", "this/Guest", "", + K0, K1, K2, "", "this/Room", "keys", + K0T0, K0T1, K0T2, K0T3, K1T4, "", "this/Room", "currentKey", + F1, F2, F3, F4, F5, "", "this/FrontDesk", "lastKey", + GK1, GK2, GK3, GK4, GK5, GK6, "", "this/Guest", "keys", + O1, O2, O3, "", "this/FrontDesk", "occupant", + "Event$0", "Event$1", "", "this/Checkin", "", + "Event$2", "", "this/Checkout", "", + "Event$3", "", "this/Entry", "", + t_tuple(fac,"Event$0",t0), + t_tuple(fac,"Event$2",t1), + t_tuple(fac,"Event$1",t2), + t_tuple(fac,"Event$3",t3), "", "this/Event", "pre", + }); + if (sol==null && K0T0!=null) + sol=trial(rep, fac, solver, sigs, formula, frame, new Object[]{"Fig 6.6", + G0, G1, "", "this/Guest", "", + K0, K1, K2, "", "this/Room", "keys", + K0T0, K0T1, K0T2, K0T3, K1T4, "", "this/Room", "currentKey", + F1, F2, F3, F4, F5, "", "this/FrontDesk", "lastKey", + GK1, GK2, GK3, GK4, GK5, GK6, "", "this/Guest", "keys", + O1, O2, O3, "", "this/FrontDesk", "occupant", + }); + } + return sol; + } + + /** This tries a particular solution against the formula. */ + private static Solution trial (A4Reporter rep, TupleFactory fac, Solver solver, Iterable sigs, Formula f, A4Solution frame, Object[] t) { + try { + frame.kr2typeCLEAR(); + Bounds b = null; + TupleSet ts = null; + for(int i=1; i0) { // This means it's a unary Tuple containing the given atom + Tuple xx = fac.tuple((String)x); + if (ts==null) ts=fac.noneOf(1); + ts.add(xx); + continue; + } + if (x instanceof Tuple) { // This means it's a Tuple + Tuple xx=(Tuple)x; + if (ts==null) ts=fac.noneOf(xx.arity()); + ts.add(xx); + continue; + } + if (x instanceof String) { // The empty string means the sig name follows here + i++; + if (i>=t.length-1 || !(t[i] instanceof String) || !(t[i+1] instanceof String)) return null; + String sigName = (String)(t[i]); + i++; + String fieldName = (String)(t[i]); + Sig first = hasSig(sigs,sigName); + if (first==null) return null; + Expression expr = null; + if (fieldName.length()==0) { + expr=frame.a2k(first); + } else { + for(Field field:first.getFields()) if (field.label.equals(fieldName)) { + expr=frame.a2k(field); + while(expr instanceof BinaryExpression) expr=((BinaryExpression)expr).right(); + break; + } + } + if (!(expr instanceof Relation)) return null; + if (b==null) b = frame.getBounds(); // We delay the expansive Bounds.clone() until we really find a possible match + if (ts==null) ts = fac.noneOf(expr.arity()); + if (!ts.containsAll(b.lowerBound((Relation)expr))) return null; // Sanity check + if (!b.upperBound((Relation)expr).containsAll(ts)) return null; // Sanity check + b.boundExactly((Relation)expr, ts); + ts=null; + continue; + } + } + SATFactory sat = solver.options().solver(); + Solution sol; + try { + solver.options().setSolver(SATFactory.DefaultSAT4J); + sol = solver.solve(f,b); + } finally { + solver.options().setSolver(sat); + } + if (sol==null || (sol.outcome()!=SATISFIABLE && sol.outcome()!=TRIVIALLY_SATISFIABLE)) return null; + if (rep!=null) rep.debug("Comment: "+t[0]+"\n"); + return sol; + } catch(Throwable ex) { + return null; + } + } + + /** This constructs a Kodkod Tuple from the list of atoms, and returns null if no such Tuple can be constructed. */ + private static Tuple t_tuple (TupleFactory factory, Object... atoms) { + try { + if (atoms.length <= 0) return null; + return factory.tuple(atoms); + } catch(Throwable ex) { + return null; + } + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/translator/BoundsComputer.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/translator/BoundsComputer.java new file mode 100644 index 00000000..77726b68 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/translator/BoundsComputer.java @@ -0,0 +1,329 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4compiler.translator; + +import java.util.ArrayList; +import java.util.List; +import java.util.LinkedHashMap; +import java.util.Map; +import kodkod.ast.Decls; +import kodkod.ast.Expression; +import kodkod.ast.Formula; +import kodkod.ast.Relation; +import kodkod.ast.Variable; +import kodkod.instance.Tuple; +import kodkod.instance.TupleFactory; +import kodkod.instance.TupleSet; +import kodkod.instance.Universe; +import edu.mit.csail.sdg.alloy4.A4Reporter; +import edu.mit.csail.sdg.alloy4.Err; +import edu.mit.csail.sdg.alloy4.Pos; +import edu.mit.csail.sdg.alloy4compiler.ast.Sig.Field; +import edu.mit.csail.sdg.alloy4compiler.ast.Sig.PrimSig; +import edu.mit.csail.sdg.alloy4compiler.ast.Expr; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprBinary; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprConstant; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprList; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprUnary; +import edu.mit.csail.sdg.alloy4compiler.ast.Sig; +import edu.mit.csail.sdg.alloy4compiler.ast.Type; +import edu.mit.csail.sdg.alloy4compiler.ast.Sig.SubsetSig; + +/** Immutable; this class assigns each sig and field to some Kodkod relation or expression, then set the bounds. */ + +final class BoundsComputer { + + // It calls these A4Solution methods... + // getFactory(), query(), a2k(), addRel(), addSig(), addField(), addFormula() + + /** Stores the reporter that will receive diagnostic messages. */ + private final A4Reporter rep; + + /** Stores the scope, bounds, and other settings necessary for performing a solve. */ + private final A4Solution sol; + + /** Stores the factory. */ + private final TupleFactory factory; + + /** Stores the computed scope for each sig. */ + private final ScopeComputer sc; + + /** Stores the upperbound for each sig. */ + private final Map ub = new LinkedHashMap(); + + /** Stores the lowerbound for each sig. */ + private final Map lb = new LinkedHashMap(); + + //==============================================================================================================// + + /** Computes the lowerbound from bottom-up; it will also set a suitable initial value for each sig's upperbound. + * Precondition: sig is not a builtin sig + */ + private TupleSet computeLowerBound(List atoms, final PrimSig sig) throws Err { + int n = sc.sig2scope(sig); + TupleSet lower = factory.noneOf(1); + for(PrimSig c:sig.children()) lower.addAll(computeLowerBound(atoms, c)); + TupleSet upper = lower.clone(); + boolean isExact = sc.isExact(sig); + if (isExact || sig.isTopLevel()) for(n=n-upper.size(); n>0; n--) { + Tuple atom = atoms.remove(atoms.size()-1); + // If MUST lb.get(c).size()) ub.get(c).addAll(x); + computeUpperBound(c); + } + } + + //==============================================================================================================// + + /** Allocate relations for nonbuiltin PrimSigs bottom-up. */ + private Expression allocatePrimSig(PrimSig sig) throws Err { + // Recursively allocate all children expressions, and form the union of them + Expression sum = null; + for(PrimSig child:sig.children()) { + Expression childexpr=allocatePrimSig(child); + if (sum==null) { sum=childexpr; continue; } + // subsigs are disjoint + sol.addFormula(sum.intersection(childexpr).no(), child.isSubsig); + sum = sum.union(childexpr); + } + TupleSet lower = lb.get(sig).clone(), upper = ub.get(sig).clone(); + if (sum == null) { + // If sig doesn't have children, then sig should make a fresh relation for itself + sum = sol.addRel(sig.label, lower, upper); + } else if (sig.isAbstract == null) { + // If sig has children, and sig is not abstract, then create a new relation to act as the remainder. + for(PrimSig child:sig.children()) { + // Remove atoms that are KNOWN to be in a subsig; + // it's okay to mistakenly leave some atoms in, since we will never solve for the "remainder" relation directly; + // instead, we union the remainder with the children, then solve for the combined solution. + // (Thus, the more we can remove, the more efficient it gets, but it is not crucial for correctness) + TupleSet childTS = sol.query(false, sol.a2k(child), false); + lower.removeAll(childTS); + upper.removeAll(childTS); + } + sum = sum.union(sol.addRel(sig.label+" remainder", lower, upper)); + } + sol.addSig(sig, sum); + return sum; + } + + //==============================================================================================================// + + /** Allocate relations for SubsetSig top-down. */ + private Expression allocateSubsetSig(SubsetSig sig) throws Err { + // We must not visit the same SubsetSig more than once, so if we've been here already, then return the old value right away + Expression sum = sol.a2k(sig); + if (sum!=null && sum!=Expression.NONE) return sum; + // Recursively form the union of all parent expressions + TupleSet ts = factory.noneOf(1); + for(Sig parent:sig.parents) { + Expression p = (parent instanceof PrimSig) ? sol.a2k(parent) : allocateSubsetSig((SubsetSig)parent); + ts.addAll(sol.query(true, p, false)); + if (sum==null) sum=p; else sum=sum.union(p); + } + // If subset is exact, then just use the "sum" as is + if (sig.exact) { sol.addSig(sig, sum); return sum; } + // Allocate a relation for this subset sig, then bound it + rep.bound("Sig "+sig+" in "+ts+"\n"); + Relation r = sol.addRel(sig.label, null, ts); + sol.addSig(sig, r); + // Add a constraint that it is INDEED a subset of the union of its parents + sol.addFormula(r.in(sum), sig.isSubset); + return r; + } + + //==============================================================================================================// + + /** Helper method that returns the constraint that the sig has exactly "n" elements, or at most "n" elements */ + private Formula size(Sig sig, int n, boolean exact) { + Expression a = sol.a2k(sig); + if (n<=0) return a.no(); + if (n==1) return exact ? a.one() : a.lone(); + Formula f = exact ? Formula.TRUE : null; + Decls d = null; + Expression sum = null; + while(n>0) { + n--; + Variable v = Variable.unary("v" + Integer.toString(TranslateAlloyToKodkod.cnt++)); + kodkod.ast.Decl dd = v.oneOf(a); + if (d==null) d=dd; else d=dd.and(d); + if (sum==null) sum=v; else { if (f!=null) f=v.intersection(sum).no().and(f); sum=v.union(sum); } + } + if (f!=null) return sum.eq(a).and(f).forSome(d); else return a.no().or(sum.eq(a).forSome(d)); + } + + //==============================================================================================================// + + /** If ex is a simple combination of Relations, then return that combination, else return null. */ + private Expression sim(Expr ex) { + while(ex instanceof ExprUnary) { + ExprUnary u = (ExprUnary)ex; + if (u.op!=ExprUnary.Op.NOOP && u.op!=ExprUnary.Op.EXACTLYOF) break; + ex = u.sub; + } + if (ex instanceof ExprBinary) { + ExprBinary b = (ExprBinary)ex; + if (b.op==ExprBinary.Op.ARROW || b.op==ExprBinary.Op.PLUS || b.op==ExprBinary.Op.JOIN) { + Expression left = sim(b.left); if (left==null) return null; + Expression right = sim(b.right); if (right==null) return null; + if (b.op==ExprBinary.Op.ARROW) return left.product(right); + if (b.op==ExprBinary.Op.PLUS) return left.union(right); else return left.join(right); + } + } + if (ex instanceof ExprConstant) { + switch(((ExprConstant)ex).op) { + case EMPTYNESS: return Expression.NONE; + } + } + if (ex==Sig.NONE) return Expression.NONE; + if (ex==Sig.SIGINT) return Expression.INTS; + if (ex instanceof Sig) return sol.a2k((Sig)ex); + if (ex instanceof Field) return sol.a2k((Field)ex); + return null; + } + + /** Computes the bounds for sigs/fields, then construct a BoundsComputer object that you can query. */ + private BoundsComputer(A4Reporter rep, A4Solution sol, ScopeComputer sc, Iterable sigs) throws Err { + this.sc = sc; + this.factory = sol.getFactory(); + this.rep = rep; + this.sol = sol; + // Figure out the sig bounds + final Universe universe = factory.universe(); + final int atomN = universe.size(); + final List atoms = new ArrayList(atomN); + for(int i=atomN-1; i>=0; i--) atoms.add(factory.tuple(universe.atom(i))); + for(Sig s:sigs) if (!s.builtin && s.isTopLevel()) computeLowerBound(atoms, (PrimSig)s); + for(Sig s:sigs) if (!s.builtin && s.isTopLevel()) computeUpperBound((PrimSig)s); + // Bound the sigs + for(Sig s:sigs) if (!s.builtin && s.isTopLevel()) allocatePrimSig((PrimSig)s); + for(Sig s:sigs) if (s instanceof SubsetSig) allocateSubsetSig((SubsetSig)s); + // Bound the fields + again: + for(Sig s:sigs) { + while (s.isOne!=null && s.getFieldDecls().size()==2 && s.getFields().size()==2 && s.getFacts().size()==1) { + // Let's check whether this is a total ordering on an enum... + Expr fact = s.getFacts().get(0).deNOP(), b1 = s.getFieldDecls().get(0).expr.deNOP(), b2 = s.getFieldDecls().get(1).expr.deNOP(), b3; + if (!(fact instanceof ExprList) || !(b1 instanceof ExprUnary) || !(b2 instanceof ExprBinary)) break; + ExprList list = (ExprList)fact; + if (list.op!=ExprList.Op.TOTALORDER || list.args.size()!=3) break; + if (((ExprUnary)b1).op!=ExprUnary.Op.SETOF) break; else b1 = ((ExprUnary)b1).sub.deNOP(); + if (((ExprBinary)b2).op!=ExprBinary.Op.ARROW) break; else { b3 = ((ExprBinary)b2).right.deNOP(); b2 = ((ExprBinary)b2).left.deNOP(); } + if (!(b1 instanceof PrimSig) || b1!=b2 || b1!=b3) break; + PrimSig sub = (PrimSig)b1; + Field f1 = s.getFields().get(0), f2 = s.getFields().get(1); + if (sub.isEnum==null || !list.args.get(0).isSame(sub) || !list.args.get(1).isSame(s.join(f1)) || !list.args.get(2).isSame(s.join(f2))) break; + // Now, we've confirmed it is a total ordering on an enum. Let's pre-bind the relations + TupleSet me = sol.query(true, sol.a2k(s), false), firstTS = factory.noneOf(2), lastTS = null, nextTS = factory.noneOf(3); + if (me.size()!=1 || me.arity()!=1) break; + int n = sub.children().size(); + for(PrimSig c: sub.children()) { + TupleSet TS = sol.query(true, sol.a2k(c), false); + if (TS.size()!=1 || TS.arity()!=1) { firstTS=factory.noneOf(2); nextTS=factory.noneOf(3); break; } + if (lastTS==null) { firstTS=me.product(TS); lastTS=TS; continue; } + nextTS.addAll(me.product(lastTS).product(TS)); + lastTS=TS; + } + if (firstTS.size()!=(n>0 ? 1 : 0) || nextTS.size() != n-1) break; + sol.addField(f1, sol.addRel(s.label+"."+f1.label, firstTS, firstTS)); + sol.addField(f2, sol.addRel(s.label+"."+f2.label, nextTS, nextTS)); + rep.bound("Field "+s.label+"."+f1.label+" == "+firstTS+"\n"); + rep.bound("Field "+s.label+"."+f2.label+" == "+nextTS+"\n"); + continue again; + } + for(Field f:s.getFields()) { + boolean isOne = s.isOne!=null; + if (isOne && f.decl().expr.mult()==ExprUnary.Op.EXACTLYOF) { + Expression sim = sim(f.decl().expr); + if (sim!=null) { + rep.bound("Field "+s.label+"."+f.label+" defined to be "+sim+"\n"); + sol.addField(f, sol.a2k(s).product(sim)); + continue; + } + } + Type t = isOne ? Sig.UNIV.type().join(f.type()) : f.type(); + TupleSet ub = factory.noneOf(t.arity()); + for(List p:t.fold()) { + TupleSet upper=null; + for(PrimSig b:p) { + TupleSet tmp = sol.query(true, sol.a2k(b), false); + if (upper==null) upper=tmp; else upper=upper.product(tmp); + } + ub.addAll(upper); + } + Relation r = sol.addRel(s.label+"."+f.label, null, ub); + sol.addField(f, isOne ? sol.a2k(s).product(r) : r); + } + } + // Add any additional SIZE constraints + for(Sig s:sigs) if (!s.builtin) { + Expression exp = sol.a2k(s); + TupleSet upper = sol.query(true,exp,false), lower=sol.query(false,exp,false); + final int n = sc.sig2scope(s); + if (s.isOne!=null && (lower.size()!=1 || upper.size()!=1)) { + rep.bound("Sig "+s+" in "+upper+" with size==1\n"); + sol.addFormula(exp.one(), s.isOne); + continue; + } + if (s.isSome!=null && lower.size()<1) sol.addFormula(exp.some(), s.isSome); + if (s.isLone!=null && upper.size()>1) sol.addFormula(exp.lone(), s.isLone); + if (n<0) continue; // This means no scope was specified + if (lower.size()==n && upper.size()==n && sc.isExact(s)) { + rep.bound("Sig "+s+" == "+upper+"\n"); + } + else if (sc.isExact(s)) { + rep.bound("Sig "+s+" in "+upper+" with size=="+n+"\n"); + sol.addFormula(size(s,n,true), Pos.UNKNOWN); + } + else if (upper.size()<=n){ + rep.bound("Sig "+s+" in "+upper+"\n"); + } + else { + rep.bound("Sig "+s+" in "+upper+" with size<="+n+"\n"); + sol.addFormula(size(s,n,false), Pos.UNKNOWN); + } + } + } + + //==============================================================================================================// + + /** Assign each sig and field to some Kodkod relation or expression, then set the bounds. */ + static void compute (A4Reporter rep, A4Solution sol, ScopeComputer sc, Iterable sigs) throws Err { + new BoundsComputer(rep, sol, sc, sigs); + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/translator/ConvToConjunction.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/translator/ConvToConjunction.java new file mode 100644 index 00000000..cb362c2f --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/translator/ConvToConjunction.java @@ -0,0 +1,102 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4compiler.translator; + +import edu.mit.csail.sdg.alloy4.Err; +import edu.mit.csail.sdg.alloy4.Pos; +import edu.mit.csail.sdg.alloy4compiler.ast.Expr; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprBinary; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprList; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprCall; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprConstant; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprITE; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprLet; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprQt; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprUnary; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprVar; +import edu.mit.csail.sdg.alloy4compiler.ast.Sig; +import edu.mit.csail.sdg.alloy4compiler.ast.VisitReturn; +import edu.mit.csail.sdg.alloy4compiler.ast.Sig.Field; + +/** Immutable; this class rearranges the AST to promote as many clauses up to the top level as possible + * (in order to get better precision unsat core results) + */ + +final class ConvToConjunction extends VisitReturn { + + /** {@inheritDoc} */ + @Override public Expr visit(ExprBinary x) throws Err { + if (x.op == ExprBinary.Op.AND) { + Expr a = visitThis(x.left); + Expr b = visitThis(x.right); + return a.and(b); + } + return x; + } + + /** {@inheritDoc} */ + @Override public Expr visit(ExprQt x) throws Err { + if (x.op == ExprQt.Op.ALL) { + Expr s = x.sub.deNOP(); + if (s instanceof ExprBinary && ((ExprBinary)s).op==ExprBinary.Op.AND) { + Expr a = visitThis(x.op.make(Pos.UNKNOWN, Pos.UNKNOWN, x.decls, ((ExprBinary)s).left)); + Expr b = visitThis(x.op.make(Pos.UNKNOWN, Pos.UNKNOWN, x.decls, ((ExprBinary)s).right)); + return a.and(b); + } + } + return x; + } + + /** {@inheritDoc} */ + @Override public Expr visit(ExprUnary x) throws Err { + if (x.op == ExprUnary.Op.NOOP) { + return visitThis(x.sub); + } + if (x.op == ExprUnary.Op.NOT) { + Expr s = x.sub.deNOP(); + if (s instanceof ExprBinary && ((ExprBinary)s).op==ExprBinary.Op.OR) { + Expr a = visitThis(((ExprBinary)s).left.not()); + Expr b = visitThis(((ExprBinary)s).right.not()); + return a.and(b); + } + } + return x; + } + + /** {@inheritDoc} */ + @Override public Expr visit(ExprList x) { return x; } + + /** {@inheritDoc} */ + @Override public Expr visit(ExprCall x) { return x; } + + /** {@inheritDoc} */ + @Override public Expr visit(ExprConstant x) { return x; } + + /** {@inheritDoc} */ + @Override public Expr visit(ExprITE x) { return x; } + + /** {@inheritDoc} */ + @Override public Expr visit(ExprLet x) { return x; } + + /** {@inheritDoc} */ + @Override public Expr visit(ExprVar x) { return x; } + + /** {@inheritDoc} */ + @Override public Expr visit(Sig x) { return x; } + + /** {@inheritDoc} */ + @Override public Expr visit(Field x) { return x; } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/translator/ScopeComputer.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/translator/ScopeComputer.java new file mode 100644 index 00000000..fbeda696 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/translator/ScopeComputer.java @@ -0,0 +1,404 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4compiler.translator; + +import static edu.mit.csail.sdg.alloy4compiler.ast.Sig.NONE; +import static edu.mit.csail.sdg.alloy4compiler.ast.Sig.SEQIDX; +import static edu.mit.csail.sdg.alloy4compiler.ast.Sig.SIGINT; +import static edu.mit.csail.sdg.alloy4compiler.ast.Sig.STRING; +import static edu.mit.csail.sdg.alloy4compiler.ast.Sig.UNIV; + +import java.util.ArrayList; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Set; + +import edu.mit.csail.sdg.alloy4.A4Reporter; +import edu.mit.csail.sdg.alloy4.Err; +import edu.mit.csail.sdg.alloy4.ErrorAPI; +import edu.mit.csail.sdg.alloy4.ErrorSyntax; +import edu.mit.csail.sdg.alloy4.Pair; +import edu.mit.csail.sdg.alloy4.Pos; +import edu.mit.csail.sdg.alloy4.SafeList; +import edu.mit.csail.sdg.alloy4.UniqueNameGenerator; +import edu.mit.csail.sdg.alloy4.Util; +import edu.mit.csail.sdg.alloy4compiler.ast.Command; +import edu.mit.csail.sdg.alloy4compiler.ast.CommandScope; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprCall; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprUnary; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprUnary.Op; +import edu.mit.csail.sdg.alloy4compiler.ast.Sig; +import edu.mit.csail.sdg.alloy4compiler.ast.VisitQuery; +import edu.mit.csail.sdg.alloy4compiler.ast.Sig.Field; +import edu.mit.csail.sdg.alloy4compiler.ast.Sig.PrimSig; +import edu.mit.csail.sdg.alloy4compiler.ast.Type.ProductType; + +/** Immutable; this class computes the scopes for each sig and computes the bitwidth and maximum sequence length. + * + *

The scopes are determined as follows: + * + *

"run x": every topsig is scoped to <= 3 elements. + * + *

"run x for N": every topsig is scoped to <= N elements. + * + *

"run x for N but N1 SIG1, N2 SIG2...": + *
Every sig following "but" is constrained explicitly. + *
Any topsig that is + *
a) not listed, and + *
b) its scope is not derived otherwise + *
will be scoped to have <= N elements. + * + *

"run x for N1 SIG1, N2 SIG2..." + *
Every sig following "but" is constrained explicitly. + *
Any topsig that is + *
a) not listed, and + *
b) its scope is not derived otherwise + *
we will give an error message. + * + *

Please see ScopeComputer.java for the exact rules for deriving the missing scopes. + */ + +final class ScopeComputer { + + // It calls A4Solution's constructor + + /** Stores the reporter that will receive diagnostic messages. */ + private final A4Reporter rep; + + /** Stores the command that we're computing the scope for. */ + private final Command cmd; + + /** The integer bitwidth of this solution's model; always between 1 and 30. */ + private int bitwidth = 4; + + /** The maximum sequence length; always between 0 and (2^(bitwidth-1))-1. */ + private int maxseq = 4; + + /** The number of STRING atoms to allocate; -1 if it was not specified. */ + private int maxstring = (-1); + + /** The scope for each sig. */ + private final IdentityHashMap sig2scope = new IdentityHashMap(); + + /** The sig's scope is exact iff it is in exact.keySet() (the value is irrelevant). */ + private final IdentityHashMap exact = new IdentityHashMap(); + + /** The list of atoms. */ + private final List atoms = new ArrayList(); + + /** This UniqueNameGenerator allows each sig's atoms to be distinct strings. */ + private final UniqueNameGenerator un = new UniqueNameGenerator(); + + /** Returns the scope for a sig (or -1 if we don't know). */ + public int sig2scope(Sig sig) { + if (sig==SIGINT) return 1<=0) throw new ErrorSyntax(cmd.pos, "Sig \""+sig+"\" already has a scope of "+old+", so we cannot set it to be "+newValue); + sig2scope.put((PrimSig)sig, newValue); + rep.scope("Sig "+sig+" scope <= "+newValue+"\n"); + } + + /** Returns whether the scope of a sig is exact or not. */ + public boolean isExact(Sig sig) { + return sig==SIGINT || sig==SEQIDX || sig==STRING || ((sig instanceof PrimSig) && exact.containsKey(sig)); + } + + /** Make the given sig "exact". */ + private void makeExact(Pos pos, Sig sig) throws Err { + if (!(sig instanceof PrimSig)) throw new ErrorSyntax(pos, "Cannot specify a scope for a subset signature \""+sig+"\""); + exact.put(sig, sig); + } + + /** Modifies the integer bitwidth of this solution's model (and sets the max sequence length to 0) */ + private void setBitwidth(Pos pos, int newBitwidth) throws ErrorAPI, ErrorSyntax { + if (newBitwidth<0) throw new ErrorSyntax(pos, "Cannot specify a bitwidth less than 0"); + if (newBitwidth>30) throw new ErrorSyntax(pos, "Cannot specify a bitwidth greater than 30"); + bitwidth = newBitwidth; + maxseq = 0; + sig2scope.put(SIGINT, bitwidth < 1 ? 0 : 1< max()) throw new ErrorSyntax(pos, "With integer bitwidth of "+bitwidth+", you cannot have sequence length longer than "+max()); + if (newMaxSeq < 0) newMaxSeq = 0; //throw new ErrorSyntax(pos, "The maximum sequence length cannot be negative."); + maxseq = newMaxSeq; + sig2scope.put(SEQIDX, maxseq); + } + + /** Returns the largest allowed integer. */ + private int max() { return Util.max(bitwidth); } + + /** Returns the smallest allowed integer. */ + private int min() { return Util.min(bitwidth); } + + //===========================================================================================================================// + + /** If A is abstract, unscoped, and all children are scoped, then set A's scope to be the sum; + * if A is abstract, scoped, and every child except one is scoped, then set that child's scope to be the difference. + */ + private boolean derive_abstract_scope (Iterable sigs) throws Err { + boolean changed=false; + again: + for(Sig s:sigs) if (!s.builtin && (s instanceof PrimSig) && s.isAbstract!=null) { + SafeList subs = ((PrimSig)s).children(); + if (subs.size()==0) continue; + Sig missing=null; + int sum=0; + for(Sig c:subs) { + int cn = sig2scope(c); + if (cn<0) { if (missing==null) { missing=c; continue; } else { continue again; } } + sum=sum+cn; + if (sum<0) throw new ErrorSyntax(cmd.pos, "The number of atoms exceeds the internal limit of "+Integer.MAX_VALUE); + } + int sn = sig2scope(s); + if (sn<0) { + if (missing!=null) continue; + sig2scope(s, sum); + changed=true; + } else if (missing!=null) { + sig2scope(missing, (sn sigs) throws Err { + boolean changed=false; + final int overall = (cmd.overall<0 && cmd.scope.size()==0) ? 3 : cmd.overall; + for(Sig s:sigs) if (!s.builtin && s.isTopLevel() && sig2scope(s)<0) { + if (s.isEnum!=null) { sig2scope(s, 0); continue; } // enum without children should get the empty set + if (overall<0) throw new ErrorSyntax(cmd.pos, "You must specify a scope for sig \""+s+"\""); + sig2scope(s, overall); + changed=true; + } + return changed; + } + + //===========================================================================================================================// + + /** If A is not toplevel, and we haven't been able to derive its scope yet, then give it its parent's scope. */ + private boolean derive_scope_from_parent (Iterable sigs) throws Err { + boolean changed=false; + Sig trouble=null; + for(Sig s:sigs) if (!s.builtin && !s.isTopLevel() && sig2scope(s)<0 && (s instanceof PrimSig)) { + PrimSig p = ((PrimSig)s).parent; + int pb = sig2scope(p); + if (pb>=0) {sig2scope(s,pb); changed=true;} else {trouble=s;} + } + if (changed) return true; + if (trouble==null) return false; + throw new ErrorSyntax(cmd.pos,"You must specify a scope for sig \""+trouble+"\""); + } + + //===========================================================================================================================// + + /** Computes the number of atoms needed for each sig (and add these atoms to this.atoms) */ + private int computeLowerBound(final PrimSig sig) throws Err { + if (sig.builtin) return 0; + int n=sig2scope(sig), lower=0; + boolean isExact = isExact(sig); + // First, figure out what atoms *MUST* be in this sig + for(PrimSig c:sig.children()) lower = lower + computeLowerBound(c); + // Bump up the scope if the sum of children exceed the scope for this sig + if (nlower && (isExact || sig.isTopLevel())) { + // Figure out how many new atoms to make + n = n-lower; + // Pick a name for them + String name=sig.label; + if (name.startsWith("this/")) name=name.substring(5); + name=un.make(name); + // Now, generate each atom using the format "SIGNAME$INDEX" + // By prepending the index with 0 so that they're the same width, we ensure they sort lexicographically. + StringBuilder sb=new StringBuilder(); + for(int i=0; i sigs, Command cmd) throws Err { + this.rep = rep; + this.cmd = cmd; + boolean shouldUseInts = areIntsUsed(sigs, cmd); + // Process each sig listed in the command + for(CommandScope entry:cmd.scope) { + Sig s = entry.sig; + int scope = entry.startingScope; + boolean exact = entry.isExact; + if (s==UNIV) throw new ErrorSyntax(cmd.pos, "You cannot set a scope on \"univ\"."); + if (s==SIGINT) throw new ErrorSyntax(cmd.pos, + "You can no longer set a scope on \"Int\". " + +"The number of atoms in Int is always exactly equal to 2^(i" + + "nteger bitwidth).\n"); + if (s==SEQIDX) throw new ErrorSyntax(cmd.pos, + "You cannot set a scope on \"seq/Int\". " + +"To set the maximum allowed sequence length, use the seq keyword.\n"); + if (s==STRING) { + if (maxstring>=0) throw new ErrorSyntax(cmd.pos, "Sig \"String\" already has a scope of "+maxstring+", so we cannot set it to be "+scope); + if (!exact) throw new ErrorSyntax(cmd.pos, "Sig \"String\" must have an exact scope."); + maxstring = scope; + continue; + } + if (s==NONE) throw new ErrorSyntax(cmd.pos, "You cannot set a scope on \"none\"."); + if (s.isEnum!=null) throw new ErrorSyntax(cmd.pos, "You cannot set a scope on the enum \""+s.label+"\""); + if (s.isOne!=null && scope!=1) throw new ErrorSyntax(cmd.pos, + "Sig \""+s+"\" has the multiplicity of \"one\", so its scope must be 1, and cannot be "+scope); + if (s.isLone!=null && scope>1) throw new ErrorSyntax(cmd.pos, + "Sig \""+s+"\" has the multiplicity of \"lone\", so its scope must 0 or 1, and cannot be "+scope); + if (s.isSome!=null && scope<1) throw new ErrorSyntax(cmd.pos, + "Sig \""+s+"\" has the multiplicity of \"some\", so its scope must 1 or above, and cannot be "+scope); + sig2scope(s, scope); + if (exact) makeExact(cmd.pos, s); + } + // Force "one" sigs to be exactly one, and "lone" to be at most one + for(Sig s:sigs) if (s instanceof PrimSig) { + if (s.isOne!=null) { makeExact(cmd.pos, s); sig2scope(s,1); } else if (s.isLone!=null && sig2scope(s)!=0) sig2scope(s,1); + } + // Derive the implicit scopes + while(true) { + if (derive_abstract_scope(sigs)) { do {} while(derive_abstract_scope(sigs)); continue; } + if (derive_overall_scope(sigs)) { do {} while(derive_overall_scope(sigs)); continue; } + if (derive_scope_from_parent(sigs)) { do {} while(derive_scope_from_parent(sigs)); continue; } + break; + } + // Set the initial scope on "int" and "Int" and "seq" + int maxseq=cmd.maxseq, bitwidth=cmd.bitwidth; + if (bitwidth<0) { bitwidth = (shouldUseInts ? 4 : 0); } + setBitwidth(cmd.pos, bitwidth); + if (maxseq<0) { + if (cmd.overall>=0) maxseq=cmd.overall; else maxseq=4; + int max = Util.max(bitwidth); + if (maxseq > max) maxseq = max; + } + setMaxSeq(cmd.pos, maxseq); + // Generate the atoms and the universe + for(Sig s:sigs) if (s.isTopLevel()) computeLowerBound((PrimSig)s); + int max = max(), min = min(); + if (max >= min) for(int i=min; i<=max; i++) atoms.add(""+i); + } + + /** Whether or not Int appears in the relation types found in these sigs */ + private boolean areIntsUsed(Iterable sigs, Command cmd) { + /* check for Int-typed relations */ + for (Sig s : sigs) { + for (Field f : s.getFields()) { + for (ProductType pt : f.type()) { + for (int k = 0; k < pt.arity(); k++) { + if (pt.get(k) == SIGINT || pt.get(k) == SEQIDX) + return true; + } + } + } + } + /* check expressions; look for CAST2SIGING (Int[]) */ + try { + Object intTriggerNode; + intTriggerNode = cmd.formula.accept(new VisitQuery() { + @Override + public Object visit(ExprCall x) throws Err { + // skip integer arithmetic functions, because their + // arguments are always explicitly cast to SIGINT using Int[] + if (x.fun.label.startsWith("integer/")) + return null; + return super.visit(x); + } + + @Override + public Object visit(ExprUnary x) throws Err { + if (x.op == Op.CAST2SIGINT) + return x; + return super.visit(x); + } + }); + if (intTriggerNode != null) return true; + } catch (Err e) {} + + return false; + } + + //===========================================================================================================================// + + /** Computes the scopes for each sig and computes the bitwidth and maximum sequence length. + * + *

The scopes are determined as follows: + * + *

"run x": every topsig is scoped to <= 3 elements. + * + *

"run x for N": every topsig is scoped to <= N elements. + * + *

"run x for N but N1 SIG1, N2 SIG2...": + *
Every sig following "but" is constrained explicitly. + *
Any topsig that is + *
a) not listed, and + *
b) its scope is not derived otherwise + *
will be scoped to have <= N elements. + * + *

"run x for N1 SIG1, N2 SIG2..." + *
Every sig following "but" is constrained explicitly. + *
Any topsig that is + *
a) not listed, and + *
b) its scope is not derived otherwise + *
we will give an error message. + * + *

Please see ScopeComputer.java for the exact rules for deriving the missing scopes. + */ + static Pair compute (A4Reporter rep, A4Options opt, Iterable sigs, Command cmd) throws Err { + ScopeComputer sc = new ScopeComputer(rep, sigs, cmd); + Set set = cmd.getAllStringConstants(sigs); + if (sc.maxstring>=0 && set.size()>sc.maxstring) rep.scope("Sig String expanded to contain all "+set.size()+" String constant(s) referenced by this command.\n"); + for(int i=0; set.size()(sol, sc); + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/translator/Simplifier.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/translator/Simplifier.java new file mode 100644 index 00000000..0f9ad5ef --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/translator/Simplifier.java @@ -0,0 +1,280 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4compiler.translator; + +import java.util.Iterator; +import java.util.List; +import edu.mit.csail.sdg.alloy4.A4Reporter; +import edu.mit.csail.sdg.alloy4.Err; +import edu.mit.csail.sdg.alloy4.MailBug; +import kodkod.ast.BinaryExpression; +import kodkod.ast.BinaryFormula; +import kodkod.ast.ComparisonFormula; +import kodkod.ast.Expression; +import kodkod.ast.Formula; +import kodkod.ast.NaryFormula; +import kodkod.ast.Relation; +import kodkod.ast.operator.ExprCompOperator; +import kodkod.ast.operator.ExprOperator; +import kodkod.ast.operator.FormulaOperator; +import kodkod.instance.TupleSet; + +/** Immutable; this class shrinks the unknowns as much as possible in order to reduce the number of variables in final CNF. + * + *

Currently it recognizes the following patterns: + * + *

(1) When it sees "A in B", it will try to derive a safe upperbound for B, and then remove + * any excess unknowns from A's upperbound. + * + *

(2) When it sees "A = B", it will try to simplify A assuming "A in B", and then simplify B assuming "B in A". + */ + +public class Simplifier { + + /** Reporter for receiving debug messages. */ + private A4Reporter rep = null; + + /** The A4Solution object we are attempting to simplify. */ + private A4Solution sol = null; + + /** Construct a Simplifier object. */ + public Simplifier() { } + + /* Stores the equivalence relation discovered so far. */ + //private final IdentityHashMap> equiv = new IdentityHashMap>(); + + /** Simplify sol.bounds() based on the set of formulas, or to modify the formulas list itself. + * Subclasses should override this method to implement different simplification algorithms. + * (Note: this method is allowed to modify the "formulas" array if it sees an opportunity for optimization) + */ + public boolean simplify(A4Reporter rep, A4Solution sol, List formulas) throws Err { + this.rep = rep; + this.sol = sol; + while(true) { + //equiv.clear(); + for(Formula f: formulas) if (!simplify_eq(f)) return false; + for(Formula f: formulas) if (!simplify_in(f)) return false; + /* + if (equiv.size()==0) return true; + // We have to construct this replacer from scratch, since each time it will retain some info we don't want + final AbstractReplacer ar = new AbstractReplacer(new kodkod.util.collections.IdentityHashSet()) { + @Override public Expression visit(Relation relation) { + List list = equiv.get(relation); + if (list!=null) return list.get(0); else return relation; + } + }; + for(Map.Entry> e: equiv.entrySet()) System.out.println("Equiv: "+e); System.out.flush(); + for(int i=formulas.size()-1; i>=0; i--) { + Formula OLD = formulas.get(i); + Formula NEW = OLD.accept(ar); + if (OLD!=NEW) { System.out.println("OLD->NEW: "+OLD+" ===> "+NEW); System.out.flush(); } + formulas.set(i, NEW); + } + */ + return true; + } + } + + /** Simplify (a.(a->b)) into b when semantically equivalent */ + private final Expression condense(Expression x) { + while (x instanceof BinaryExpression) { + BinaryExpression b = (BinaryExpression)x; + if (b.op() == ExprOperator.JOIN && b.left() instanceof Relation && b.right() instanceof BinaryExpression) { + Relation r = (Relation) (b.left()); + try { + if (sol.query(true, r, false).size()!=1) return x; + if (sol.query(false, r, false).size()!=1) return x; + } catch(Err er) { + return x; + } + b = (BinaryExpression)(b.right()); + if (b.op() == ExprOperator.PRODUCT && b.left()==r) { + x = b.right(); + continue; + } + } + break; + } + return x; + } + + /** Simplify the bounds based on the fact that "a == b"; return false if we discover the formula is unsat. */ + private final boolean simplify_equal(Expression a, Expression b) { + a = condense(a); + b = condense(b); + /* + System.out.println("A: "+a+" B: "+b); System.out.flush(); + if (a instanceof Relation && b instanceof Relation && a!=b) { + List al = equiv.get(a); + List bl = equiv.get(b); + if (al==null) { Expression oldA=a; al=bl; bl=null; a=b; b=oldA; } + if (al==null) { + al = new ArrayList(); + al.add(a); + al.add(b); + equiv.put(a, al); + equiv.put(b, al); + System.out.println("BothNewEquivalent: "+a+" "+b); System.out.flush(); + } else if (bl==null) { + al.add(b); + equiv.put(b, al); + System.out.println("NewEquivalent: "+a+" "+b); System.out.flush(); + } else if (al!=bl) { + al.addAll(bl); + for(Node x: bl) equiv.put(x, al); + System.out.println("MergeEquivalent: "+a+" "+b); System.out.flush(); + } + } else*/ + if (a instanceof Relation || b instanceof Relation) { + try { + TupleSet a0 = sol.query(false, a, false), a1 = sol.query(true, a, false); + TupleSet b0 = sol.query(false, b, false), b1 = sol.query(true, b, false); + if (a instanceof Relation && a0.size()"+(a1.size()-b0.size())+"\n"); + sol.shrink((Relation)a, a0=b0, a1); + } + if (a instanceof Relation && a1.size()>b1.size() && b1.containsAll(a0) && a1.containsAll(b1)) { + rep.debug("Comment: Simplify "+a+" "+(a1.size()-a0.size())+"->"+(b1.size()-a0.size())+"\n"); + sol.shrink((Relation)a, a0, a1=b1); + } + if (b instanceof Relation && b0.size()"+(b1.size()-a0.size())+"\n"); + sol.shrink((Relation)b, b0=a0, b1); + } + if (b instanceof Relation && b1.size()>a1.size() && a1.containsAll(b0) && b1.containsAll(a1)) { + rep.debug("Comment: Simplify "+b+" "+(b1.size()-b0.size())+"->"+(a1.size()-b0.size())+"\n"); + sol.shrink((Relation)b, b0, b1=a1); + } + } catch(Exception ex) {} + } + return true; + } + + /** Simplify the bounds based on the fact that "a is subset of b"; return false if we discover the formula is unsat. */ + private final boolean simplify_in(Expression a, Expression b) { + a = condense(a); + b = condense(b); + if (a instanceof Relation) { + try { + Relation r = (Relation)a; + TupleSet ub = sol.query(true, r, false), lb = sol.query(false, r, false), t = sol.approximate(b); + t.retainAll(ub); + if (!t.containsAll(lb)) { rep.debug("Comment: Simplify "+a+" "+ub.size()+"->false\n"); return false; } // This means the upperbound is shrunk BELOW the lowerbound. + if (t.size() < ub.size()) { rep.debug("Comment: Simplify "+a+" "+ub.size()+"->"+t.size()+"\n"); sol.shrink(r,lb,t); } + } catch(Throwable ex) { + rep.debug("Comment: Simplify "+a+" exception: "+ex+"\n"+MailBug.dump(ex).trim()+"\n"); // Not fatal; let's report it to the debug() reporter + } + } + return true; + } + + // ALTERNATIVE VERSION THAT COMPUTES LOWER BOUNDS AS WELL +// /** Simplify the bounds based on the fact that "a is subset of b"; return false if we discover the formula is unsat. */ +// private final boolean simplify_in(Expression a, Expression b) { +// a = condense(a); +// b = condense(b); +// if (a instanceof Relation) { +// return simpIn((Relation)a, b, true); +// } +// if (b instanceof Relation) { +// return simpIn((Relation)b, a, false); +// } +// return true; +// } +// +// private final boolean simpIn(Relation r, Expression b, boolean bIsUpper) { +// try { +// TupleSet ub = sol.query(true, r, false); +// TupleSet lb = sol.query(false, r, false); +// TupleSet t = sol.approximate(b); +// t.retainAll(ub); +// if (bIsUpper) { +// if (!t.containsAll(lb)) { +// // This means the upperbound is shrunk BELOW the lowerbound. +// rep.debug("Comment: Simplify upper "+r+" "+ub.size()+"->false\n"); +// return false; +// } +// if (t.size() < ub.size()) { +// rep.debug("Comment: Simplify upper "+r+" "+ub.size()+"->"+t.size()+"\n"); +// sol.shrink(r,lb,t); +// } +// } else { +// if (!ub.containsAll(t)) { +// // This means the upperbound is shrunk BELOW the lowerbound. +// rep.debug("Comment: Simplify lower "+r+" "+lb.size()+"->false\n"); +// return false; +// } +// if (lb.size() < t.size()) { +// rep.debug("Comment: Simplify lower "+r+" "+lb.size()+"->"+t.size()+"\n"); +// sol.shrink(r,t,ub); +// } +// } +// } catch(Throwable ex) { +// rep.debug("Comment: Simplify "+r+" exception: "+ex+"\n"+MailBug.dump(ex).trim()+"\n"); // Not fatal; let's report it to the debug() reporter +// } +// return true; +// } + + /** Simplify the bounds based on the fact that "form is true"; return false if we discover the formula is unsat. */ + private final boolean simplify_in (Formula form) { + if (form instanceof NaryFormula) { + NaryFormula f = (NaryFormula)form; + if (f.op() == FormulaOperator.AND) { + for(Iterator i = f.iterator(); i.hasNext();) if (!simplify_in(i.next())) return false; + } + } + if (form instanceof BinaryFormula) { + BinaryFormula f = (BinaryFormula)form; + if (f.op() == FormulaOperator.AND) { + return simplify_in(f.left()) && simplify_in(f.right()); + } + } + if (form instanceof ComparisonFormula) { + ComparisonFormula f = (ComparisonFormula)form; + if (f.op() == ExprCompOperator.SUBSET) { + if (!simplify_in(f.left(), f.right())) return false; + } + if (f.op() == ExprCompOperator.EQUALS) { + if (!simplify_in(f.left(), f.right())) return false; + if (!simplify_in(f.right(), f.left())) return false; + } + } + return true; + } + + /** Simplify the bounds based on the fact that "form is true"; return false if we discover the formula is unsat. */ + private final boolean simplify_eq (Formula form) { + if (form instanceof NaryFormula) { + NaryFormula f = (NaryFormula)form; + if (f.op() == FormulaOperator.AND) { + for(Iterator i = f.iterator(); i.hasNext();) if (!simplify_eq(i.next())) return false; + } + } + if (form instanceof BinaryFormula) { + BinaryFormula f = (BinaryFormula)form; + if (f.op() == FormulaOperator.AND) { + return simplify_eq(f.left()) && simplify_eq(f.right()); + } + } + if (form instanceof ComparisonFormula) { + ComparisonFormula f = (ComparisonFormula)form; + if (f.op() == ExprCompOperator.EQUALS) { + if (!simplify_equal(f.left(), f.right())) return false; + } + } + return true; + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/translator/TranslateAlloyToKodkod.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/translator/TranslateAlloyToKodkod.java new file mode 100644 index 00000000..ab49ef22 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/translator/TranslateAlloyToKodkod.java @@ -0,0 +1,1048 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4compiler.translator; + +import static edu.mit.csail.sdg.alloy4.Util.tail; +import static edu.mit.csail.sdg.alloy4compiler.ast.Sig.UNIV; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import kodkod.ast.BinaryExpression; +import kodkod.ast.Decls; +import kodkod.ast.ExprToIntCast; +import kodkod.ast.Expression; +import kodkod.ast.Formula; +import kodkod.ast.IntConstant; +import kodkod.ast.IntExpression; +import kodkod.ast.IntToExprCast; +import kodkod.ast.QuantifiedFormula; +import kodkod.ast.Relation; +import kodkod.ast.Variable; +import kodkod.ast.operator.ExprOperator; +import kodkod.engine.CapacityExceededException; +import kodkod.engine.fol2sat.HigherOrderDeclException; +import kodkod.instance.Tuple; +import kodkod.instance.TupleFactory; +import kodkod.instance.TupleSet; +import kodkod.util.ints.IntVector; +import edu.mit.csail.sdg.alloy4.A4Reporter; +import edu.mit.csail.sdg.alloy4.ConstList; +import edu.mit.csail.sdg.alloy4.ConstMap; +import edu.mit.csail.sdg.alloy4.Env; +import edu.mit.csail.sdg.alloy4.Err; +import edu.mit.csail.sdg.alloy4.ErrorFatal; +import edu.mit.csail.sdg.alloy4.ErrorSyntax; +import edu.mit.csail.sdg.alloy4.ErrorType; +import edu.mit.csail.sdg.alloy4.Pair; +import edu.mit.csail.sdg.alloy4.Pos; +import edu.mit.csail.sdg.alloy4.Util; +import edu.mit.csail.sdg.alloy4compiler.ast.Command; +import edu.mit.csail.sdg.alloy4compiler.ast.CommandScope; +import edu.mit.csail.sdg.alloy4compiler.ast.Decl; +import edu.mit.csail.sdg.alloy4compiler.ast.Expr; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprBinary; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprCall; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprConstant; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprHasName; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprITE; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprLet; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprList; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprQt; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprUnary; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprVar; +import edu.mit.csail.sdg.alloy4compiler.ast.Func; +import edu.mit.csail.sdg.alloy4compiler.ast.Sig; +import edu.mit.csail.sdg.alloy4compiler.ast.Type; +import edu.mit.csail.sdg.alloy4compiler.ast.VisitReturn; +import edu.mit.csail.sdg.alloy4compiler.ast.Sig.Field; + +/** Translate an Alloy AST into Kodkod AST then attempt to solve it using Kodkod. */ + +public final class TranslateAlloyToKodkod extends VisitReturn { + + static int cnt = 0; + + /** This is used to detect "function recursion" (which we currently do not allow); + * also, by knowing the current function name, we can provide a more meaningful name for skolem variables + */ + private final List current_function = new ArrayList(); + + /** This maps the current local variables (LET, QUANT, Function Param) to the actual Kodkod Expression/IntExpression/Formula. */ + private Env env = new Env(); + + /** If frame!=null, it stores the scope, bounds, and other settings necessary for performing a solve. */ + private final A4Solution frame; + + /** If frame==null, it stores the mapping from each Sig/Field/Skolem/Atom to its corresponding Kodkod expression. */ + private final ConstMap a2k; + + /** If frame==null, it stores the mapping from each String literal to its corresponding Kodkod expression. */ + private final ConstMap s2k; + + /** The current reporter. */ + private A4Reporter rep; + + /** If nonnull, it's the current command. */ + private final Command cmd; + + /** The bitwidth. */ + private final int bitwidth; + + /** The minimum allowed integer. */ + private final int min; + + /** The maximum allowed integer. */ + private final int max; + + /** The maximum allowed loop unrolling and recursion. */ + private final int unrolls; + + /** Construct a translator based on the given list of sigs and the given command. + * @param rep - if nonnull, it's the reporter that will receive diagnostics and progress reports + * @param opt - the solving options (must not be null) + * @param sigs - the list of sigs (must not be null, and must be a complete list) + * @param cmd - the command to solve (must not be null) + */ + private TranslateAlloyToKodkod (A4Reporter rep, A4Options opt, Iterable sigs, Command cmd) throws Err { + this.unrolls = opt.unrolls; + this.rep = (rep != null) ? rep : A4Reporter.NOP; + this.cmd = cmd; + Pair pair = ScopeComputer.compute(this.rep, opt, sigs, cmd); + this.frame = pair.a; + this.bitwidth = pair.a.getBitwidth(); + this.min = pair.a.min(); + this.max = pair.a.max(); + this.a2k = null; + this.s2k = null; + BoundsComputer.compute(rep, frame, pair.b, sigs); + } + + /** Construct a translator based on a already-fully-constructed association map. + * @param bitwidth - the integer bitwidth to use + * @param unrolls - the maximum number of loop unrolling and recursion allowed + * @param a2k - the mapping from Alloy sig/field/skolem/atom to the corresponding Kodkod expression + */ + private TranslateAlloyToKodkod (int bitwidth, int unrolls, Map a2k, Map s2k) throws Err { + this.unrolls = unrolls; + if (bitwidth<0) throw new ErrorSyntax("Cannot specify a bitwidth less than 0"); + if (bitwidth>30) throw new ErrorSyntax("Cannot specify a bitwidth greater than 30"); + this.rep = A4Reporter.NOP; + this.cmd = null; + this.frame = null; + this.bitwidth = bitwidth; + this.max = Util.max(bitwidth); + this.min = Util.min(bitwidth); + this.a2k = ConstMap.make(a2k); + this.s2k = ConstMap.make(s2k); + } + + /** Associate the given formula with the given expression, then return the formula as-is. */ + private Formula k2pos(Formula f, Expr e) throws Err { + if (k2pos_enabled) if (frame!=null) frame.k2pos(f, e); + return f; + } + private boolean k2pos_enabled = true; + + /** Returns the expression corresponding to the given sig. */ + private Expression a2k(Sig x) throws Err { if (a2k!=null) return a2k.get(x); else return frame.a2k(x); } + + /** Returns the expression corresponding to the given field. */ + private Expression a2k(Field x) throws Err { if (a2k!=null) return a2k.get(x); else return frame.a2k(x); } + + /** Returns the expression corresponding to the given skolem/atom. */ + private Expression a2k(ExprVar x) throws Err { if (a2k!=null) return a2k.get(x); else return frame.a2k(x); } + + /** Returns the expression corresponding to the given string literal. */ + private Expression s2k(String x) throws Err { if (s2k!=null) return s2k.get(x); else return frame.a2k(x); } + + //==============================================================================================================// + + /** Stores the list of "totalOrder predicates" that we constructed. */ + private final List totalOrderPredicates = new ArrayList(); + + /** Conjoin the constraints for "field declarations" and "fact" paragraphs */ + private void makeFacts(Expr facts) throws Err { + rep.debug("Generating facts...\n"); + // convert into a form that hopefully gives better unsat core + facts = (Expr) (new ConvToConjunction()).visitThis(facts); + // add the field facts and appended facts + for(Sig s: frame.getAllReachableSigs()) { + for(Decl d: s.getFieldDecls()) { + k2pos_enabled = false; + for(ExprHasName n: d.names) { + Field f = (Field)n; + Expr form = s.decl.get().join(f).in(d.expr); + form = s.isOne==null ? form.forAll(s.decl) : ExprLet.make(null, (ExprVar)(s.decl.get()), s, form); + frame.addFormula(cform(form), f); + // Given the above, we can be sure that every column is well-bounded (except possibly the first column). + // Thus, we need to add a bound that the first column is a subset of s. + if (s.isOne==null) { + Expression sr = a2k(s), fr = a2k(f); + for(int i=f.type().arity(); i>1; i--) fr=fr.join(Relation.UNIV); + frame.addFormula(fr.in(sr), f); + } + } + if (s.isOne==null && d.disjoint2!=null) for(ExprHasName f: d.names) { + Decl that = s.oneOf("that"); + Expr formula = s.decl.get().equal(that.get()).not().implies(s.decl.get().join(f).intersect(that.get().join(f)).no()); + frame.addFormula(cform(formula.forAll(that).forAll(s.decl)), d.disjoint2); + } + if (d.names.size()>1 && d.disjoint!=null) { frame.addFormula(cform(ExprList.makeDISJOINT(d.disjoint, null, d.names)), d.disjoint); } + } + k2pos_enabled = true; + for(Expr f: s.getFacts()) { + Expr form = s.isOne==null ? f.forAll(s.decl) : ExprLet.make(null, (ExprVar)(s.decl.get()), s, f); + frame.addFormula(cform(form), f); + } + } + k2pos_enabled = true; + recursiveAddFormula(facts); + } + + /** Break up x into conjuncts then add them each as a fact. */ + private void recursiveAddFormula(Expr x) throws Err { + if (x instanceof ExprList && ((ExprList)x).op==ExprList.Op.AND) { + for(Expr e: ((ExprList)x).args) recursiveAddFormula(e); + } else { + frame.addFormula(cform(x), x); + } + } + + //==============================================================================================================// + + private static final class GreedySimulator extends Simplifier { + private List totalOrderPredicates = null; + private Iterable allSigs = null; + private ConstList growableSigs = null; + private A4Solution partial = null; + public GreedySimulator() { } + private TupleSet convert(TupleFactory factory, Expr f) throws Err { + TupleSet old = ((A4TupleSet) (partial.eval(f))).debugGetKodkodTupleset(); + TupleSet ans = factory.noneOf(old.arity()); + for(Tuple oldT: old) { + Tuple newT = null; + for(int i=0; i unused) throws Err { + TupleFactory factory = sol.getFactory(); + TupleSet oldUniv = convert(factory, Sig.UNIV); + Set oldAtoms = new HashSet(); for(Tuple t: oldUniv) oldAtoms.add(t.atom(0)); + for(Sig s: allSigs) { + // The case below is STRICTLY an optimization; the entire statement can be removed without affecting correctness + if (s.isOne!=null && s.getFields().size()==2) + for(int i=0; i+3 sigs, Command usercommand, A4Options opt) throws Exception { + // FIXTHIS: if the next command has a "smaller scope" than the last command, we would get a Kodkod exception... + // FIXTHIS: if the solver is "toCNF" or "toKodkod" then this method will throw an Exception... + // FIXTHIS: does solution enumeration still work when we're doing a greedy solve? + TranslateAlloyToKodkod tr = null; + try { + long start = System.currentTimeMillis(); + GreedySimulator sim = new GreedySimulator(); + sim.allSigs = sigs; + sim.partial = null; + A4Reporter rep2 = new A4Reporter(rep) { + private boolean first = true; + public void translate(String solver, int bitwidth, int maxseq, int skolemDepth, int symmetry) { if (first) super.translate(solver, bitwidth, maxseq, skolemDepth, symmetry); first=false; } + public void resultSAT(Object command, long solvingTime, Object solution) { } + public void resultUNSAT(Object command, long solvingTime, Object solution) { } + }; + // Form the list of commands + List commands = new ArrayList(); + while(usercommand!=null) { commands.add(usercommand); usercommand = usercommand.parent; } + // For each command... + A4Solution sol = null; + for(int i=commands.size()-1; i>=0; i--) { + Command cmd = commands.get(i); + sim.growableSigs = cmd.getGrowableSigs(); + while(cmd != null) { + rep.debug(cmd.scope.toString()); + usercommand = cmd; + tr = new TranslateAlloyToKodkod(rep2, opt, sigs, cmd); + tr.makeFacts(cmd.formula); + sim.totalOrderPredicates = tr.totalOrderPredicates; + sol = tr.frame.solve(rep2, cmd, sim.partial==null || cmd.check ? new Simplifier() : sim, false); + if (!sol.satisfiable() && !cmd.check) { + start = System.currentTimeMillis() - start; + if (sim.partial==null) { rep.resultUNSAT(cmd, start, sol); return sol; } else { rep.resultSAT(cmd, start, sim.partial); return sim.partial; } + } + if (sol.satisfiable() && cmd.check) { + start = System.currentTimeMillis() - start; + rep.resultSAT(cmd, start, sol); return sol; + } + sim.partial = sol; + if (sim.growableSigs.isEmpty()) break; + for(Sig s: sim.growableSigs) { + CommandScope sc = cmd.getScope(s); + if (sc.increment > sc.endingScope - sc.startingScope) {cmd=null; break;} + cmd = cmd.change(s, sc.isExact, sc.startingScope+sc.increment, sc.endingScope, sc.increment); + } + } + } + if (sol.satisfiable()) rep.resultSAT(usercommand, System.currentTimeMillis()-start, sol); else rep.resultUNSAT(usercommand, System.currentTimeMillis()-start, sol); + return sol; + } catch(CapacityExceededException ex) { + throw rethrow(ex); + } catch(HigherOrderDeclException ex) { + Pos p = tr!=null ? tr.frame.kv2typepos(ex.decl().variable()).b : Pos.UNKNOWN; + throw new ErrorType(p, "Analysis cannot be performed since it requires higher-order quantification that could not be skolemized."); + } + } + + /** Based on the specified "options", execute one command and return the resulting A4Solution object. + * + * @param rep - if nonnull, we'll send compilation diagnostic messages to it + * @param sigs - the list of sigs; this list must be complete + * @param cmd - the Command to execute + * @param opt - the set of options guiding the execution of the command + * + * @return null if the user chose "save to FILE" as the SAT solver, + * and nonnull if the solver finishes the entire solving and is either satisfiable or unsatisfiable. + *

If the return value X is satisfiable, you can call X.next() to get the next satisfying solution X2; + * and you can call X2.next() to get the next satisfying solution X3... until you get an unsatisfying solution. + */ + public static A4Solution execute_command (A4Reporter rep, Iterable sigs, Command cmd, A4Options opt) throws Err { + if (rep==null) rep = A4Reporter.NOP; + TranslateAlloyToKodkod tr = null; + try { + if (cmd.parent!=null || !cmd.getGrowableSigs().isEmpty()) return execute_greedyCommand(rep, sigs, cmd, opt); + tr = new TranslateAlloyToKodkod(rep, opt, sigs, cmd); + tr.makeFacts(cmd.formula); + return tr.frame.solve(rep, cmd, new Simplifier(), false); + } catch(UnsatisfiedLinkError ex) { + throw new ErrorFatal("The required JNI library cannot be found: "+ex.toString().trim(), ex); + } catch(CapacityExceededException ex) { + throw rethrow(ex); + } catch(HigherOrderDeclException ex) { + Pos p = tr!=null ? tr.frame.kv2typepos(ex.decl().variable()).b : Pos.UNKNOWN; + throw new ErrorType(p, "Analysis cannot be performed since it requires higher-order quantification that could not be skolemized."); + } catch(Throwable ex) { + if (ex instanceof Err) throw (Err)ex; else throw new ErrorFatal("Unknown exception occurred: "+ex, ex); + } + } + + /** Based on the specified "options", execute one command and return the resulting A4Solution object. + * + *

Note: it will first test whether the model fits one of the model from the "Software Abstractions" book; + * if so, it will use the exact instance that was in the book. + * + * @param rep - if nonnull, we'll send compilation diagnostic messages to it + * @param sigs - the list of sigs; this list must be complete + * @param cmd - the Command to execute + * @param opt - the set of options guiding the execution of the command + * + * @return null if the user chose "save to FILE" as the SAT solver, + * and nonnull if the solver finishes the entire solving and is either satisfiable or unsatisfiable. + *

If the return value X is satisfiable, you can call X.next() to get the next satisfying solution X2; + * and you can call X2.next() to get the next satisfying solution X3... until you get an unsatisfying solution. + */ + public static A4Solution execute_commandFromBook (A4Reporter rep, Iterable sigs, Command cmd, A4Options opt) throws Err { + if (rep==null) rep = A4Reporter.NOP; + TranslateAlloyToKodkod tr = null; + try { + if (cmd.parent!=null || !cmd.getGrowableSigs().isEmpty()) return execute_greedyCommand(rep, sigs, cmd, opt); + tr = new TranslateAlloyToKodkod(rep, opt, sigs, cmd); + tr.makeFacts(cmd.formula); + return tr.frame.solve(rep, cmd, new Simplifier(), true); + } catch(UnsatisfiedLinkError ex) { + throw new ErrorFatal("The required JNI library cannot be found: "+ex.toString().trim(), ex); + } catch(CapacityExceededException ex) { + throw rethrow(ex); + } catch(HigherOrderDeclException ex) { + Pos p = tr!=null ? tr.frame.kv2typepos(ex.decl().variable()).b : Pos.UNKNOWN; + throw new ErrorType(p, "Analysis cannot be performed since it requires higher-order quantification that could not be skolemized."); + } catch(Throwable ex) { + if (ex instanceof Err) throw (Err)ex; else throw new ErrorFatal("Unknown exception occurred: "+ex, ex); + } + } + + /** Translate the Alloy expression into an equivalent Kodkod Expression or IntExpression or Formula object. + * @param sol - an existing satisfiable A4Solution object + * @param expr - this is the Alloy expression we want to translate + */ + public static Object alloy2kodkod(A4Solution sol, Expr expr) throws Err { + if (expr.ambiguous && !expr.errors.isEmpty()) expr = expr.resolve(expr.type(), null); + if (!expr.errors.isEmpty()) throw expr.errors.pick(); + TranslateAlloyToKodkod tr = new TranslateAlloyToKodkod(sol.getBitwidth(), sol.unrolls(), sol.a2k(), sol.s2k()); + Object ans; + try { + ans = tr.visitThis(expr); + } catch(UnsatisfiedLinkError ex) { + throw new ErrorFatal("The required JNI library cannot be found: "+ex.toString().trim()); + } catch(CapacityExceededException ex) { + throw rethrow(ex); + } catch(HigherOrderDeclException ex) { + throw new ErrorType("Analysis cannot be performed since it requires higher-order quantification that could not be skolemized."); + } catch(Throwable ex) { + if (ex instanceof Err) throw (Err)ex; + throw new ErrorFatal("Unknown exception occurred: "+ex, ex); + } + if ((ans instanceof IntExpression) || (ans instanceof Formula) || (ans instanceof Expression)) return ans; + throw new ErrorFatal("Unknown internal error encountered in the evaluator."); + } + + //==============================================================================================================// + + /** Convenience method that evalutes x and casts the result to be a Kodkod Formula. + * @return the formula - if x evaluates to a Formula + * @throws ErrorFatal - if x does not evaluate to a Formula + */ + private Formula cform(Expr x) throws Err { + if (!x.errors.isEmpty()) throw x.errors.pick(); + Object y=visitThis(x); + if (y instanceof Formula) return (Formula)y; + throw new ErrorFatal(x.span(), "This should have been a formula.\nInstead it is "+y); + } + + /** Convenience method that evalutes x and cast the result to be a Kodkod IntExpression. + * @return the integer expression - if x evaluates to an IntExpression + * @throws ErrorFatal - if x does not evaluate to an IntExpression + */ + private IntExpression cint(Expr x) throws Err { + if (!x.errors.isEmpty()) throw x.errors.pick(); + return toInt(x, visitThis(x)); + } + + private IntExpression toInt(Expr x, Object y) throws Err, ErrorFatal { + // simplify: if y is int[Int[sth]] then return sth + if (y instanceof ExprToIntCast) { + ExprToIntCast y2 = (ExprToIntCast) y; + if (y2.expression() instanceof IntToExprCast) + return ((IntToExprCast)y2.expression()).intExpr(); + } + // simplify: if y is Int[sth], then return sth + if (y instanceof IntToExprCast) + return ((IntToExprCast) y).intExpr(); + if (y instanceof IntExpression) + return (IntExpression)y; + //[AM]: maybe this conversion should be removed + if (y instanceof Expression) return ((Expression) y).sum(); + throw new ErrorFatal(x.span(), "This should have been an integer expression.\nInstead it is "+y); + } + + /** Convenience method that evaluаtes x and cast the result to be a Kodkod Expression. + * @return the expression - if x evaluates to an Expression + * @throws ErrorFatal - if x does not evaluate to an Expression + */ + private Expression cset(Expr x) throws Err { + if (!x.errors.isEmpty()) throw x.errors.pick(); + return toSet(x, visitThis(x)); + } + + private Expression toSet(Expr x, Object y) throws Err, ErrorFatal { + if (y instanceof Expression) return (Expression)y; + if (y instanceof IntExpression) return ((IntExpression) y).toExpression(); + throw new ErrorFatal(x.span(), "This should have been a set or a relation.\nInstead it is "+y); + } + + //==============================================================================================================// + + /** Given a variable name "name", prepend the current function name to form a meaningful "skolem name". + * (Note: this function does NOT, and need NOT guarantee that the name it generates is unique) + */ + private String skolem(String name) { + if (current_function.size()==0) { + if (cmd!=null && cmd.label.length()>0 && cmd.label.indexOf('$')<0) return cmd.label+"_"+name; else return name; + } + Func last=current_function.get(current_function.size()-1); + String funcname=tail(last.label); + if (funcname.indexOf('$')<0) return funcname+"_"+name; else return name; + } + + //==============================================================================================================// + + /** If x = SOMETHING->RELATION where SOMETHING.arity==1, then return the RELATION, else return null. */ + private static Relation right(Expression x) { + if (!(x instanceof BinaryExpression)) return null; + BinaryExpression bin = (BinaryExpression)x; + if (bin.op() != ExprOperator.PRODUCT) return null; + if (bin.left().arity()==1 && bin.right() instanceof Relation) return (Relation)(bin.right()); else return null; + } + + //==============================================================================================================// + + /*============================*/ + /* Evaluates an ExprITE node. */ + /*============================*/ + + /** {@inheritDoc} */ + @Override public Object visit(ExprITE x) throws Err { + Formula c = cform(x.cond); + Object l = visitThis(x.left); + if (l instanceof Formula) { + Formula c1 = c.implies((Formula)l); + Formula c2 = c.not().implies(cform(x.right)); + return k2pos(c1.and(c2), x); + } + if (l instanceof Expression) { + return c.thenElse((Expression)l, cset(x.right)); + } + return c.thenElse((IntExpression)l, cint(x.right)); + } + + /*============================*/ + /* Evaluates an ExprLet node. */ + /*============================*/ + + /** {@inheritDoc} */ + @Override public Object visit(ExprLet x) throws Err { + env.put(x.var, visitThis(x.expr)); + Object ans = visitThis(x.sub); + env.remove(x.var); + return ans; + } + + /*=================================*/ + /* Evaluates an ExprConstant node. */ + /*=================================*/ + + /** {@inheritDoc} */ + @Override public Object visit(ExprConstant x) throws Err { + switch(x.op) { + case MIN: return IntConstant.constant(min); //TODO + case MAX: return IntConstant.constant(max); //TODO + case NEXT: return A4Solution.KK_NEXT; + case TRUE: return Formula.TRUE; + case FALSE: return Formula.FALSE; + case EMPTYNESS: return Expression.NONE; + case IDEN: return Expression.IDEN.intersection(a2k(UNIV).product(Expression.UNIV)); + case STRING: + Expression ans = s2k(x.string); + if (ans==null) throw new ErrorFatal(x.pos, "String literal "+x+" does not exist in this instance.\n"); + return ans; + case NUMBER: + int n=x.num(); + //[am] const +// if (nmax) throw new ErrorType(x.pos, "Current bitwidth is set to "+bitwidth+", thus this integer constant "+n+" is bigger than the maximum integer "+max); + return IntConstant.constant(n).toExpression(); + } + throw new ErrorFatal(x.pos, "Unsupported operator ("+x.op+") encountered during ExprConstant.accept()"); + } + + /*==============================*/ + /* Evaluates an ExprUnary node. */ + /*==============================*/ + + /** {@inheritDoc} */ + @Override public Object visit(ExprUnary x) throws Err { + switch(x.op) { + case EXACTLYOF: case SOMEOF: case LONEOF: case ONEOF: case SETOF: return cset(x.sub); + case NOOP: return visitThis(x.sub); + case NOT: return k2pos( cform(x.sub).not() , x ); + case SOME: return k2pos( cset(x.sub).some() , x); + case LONE: return k2pos( cset(x.sub).lone() , x); + case ONE: return k2pos( cset(x.sub).one() , x); + case NO: return k2pos( cset(x.sub).no() , x); + case TRANSPOSE: return cset(x.sub).transpose(); + case CARDINALITY: return cset(x.sub).count(); + case CAST2SIGINT: return cint(x.sub).toExpression(); + case CAST2INT: return sum(cset(x.sub)); + case RCLOSURE: + Expression iden=Expression.IDEN.intersection(a2k(UNIV).product(Relation.UNIV)); + return cset(x.sub).closure().union(iden); + case CLOSURE: return cset(x.sub).closure(); + } + throw new ErrorFatal(x.pos, "Unsupported operator ("+x.op+") encountered during ExprUnary.visit()"); + } + + /** Performs int[x]; contains an efficiency shortcut that simplifies int[Int[x]] to x. */ + private IntExpression sum(Expression x) { + if (x instanceof IntToExprCast) return ((IntToExprCast)x).intExpr(); else return x.sum(); + } + + /*============================*/ + /* Evaluates an ExprVar node. */ + /*============================*/ + + /** {@inheritDoc} */ + @Override public Object visit(ExprVar x) throws Err { + Object ans=env.get(x); + if (ans==null) ans=a2k(x); + if (ans==null) throw new ErrorFatal(x.pos, "Variable \""+x+"\" is not bound to a legal value during translation.\n"); + return ans; + } + + /*=========================*/ + /* Evaluates a Field node. */ + /*=========================*/ + + /** {@inheritDoc} */ + @Override public Object visit(Field x) throws Err { + Expression ans = a2k(x); + if (ans==null) throw new ErrorFatal(x.pos, "Field \""+x+"\" is not bound to a legal value during translation.\n"); + return ans; + } + + /*=======================*/ + /* Evaluates a Sig node. */ + /*=======================*/ + + /** {@inheritDoc} */ + @Override public Object visit(Sig x) throws Err { + Expression ans = a2k(x); + if (ans==null) + throw new ErrorFatal(x.pos, "Sig \""+x+"\" is not bound to a legal value during translation.\n"); + return ans; + } + + /*=============================*/ + /* Evaluates an ExprCall node. */ + /*=============================*/ + + /** Caches parameter-less functions to a Kodkod Expression, Kodkod IntExpression, or Kodkod Formula. */ + private final Map cacheForConstants = new IdentityHashMap(); + + /** {@inheritDoc} */ + @Override public Object visit(ExprCall x) throws Err { + final Func f = x.fun; + final Object candidate = f.count()==0 ? cacheForConstants.get(f) : null; + if (candidate!=null) return candidate; + final Expr body = f.getBody(); + if (body.type().arity()<0 || body.type().arity()!=f.returnDecl.type().arity()) throw new ErrorType(body.span(), "Function return value not fully resolved."); + final int n = f.count(); + int maxRecursion = unrolls; + for(Func ff:current_function) if (ff==f) { + if (maxRecursion<0) { + throw new ErrorSyntax(x.span(), ""+f+" cannot call itself recursively!"); + } + if (maxRecursion==0) { + Type t = f.returnDecl.type(); + if (t.is_bool) return Formula.FALSE; + if (t.is_int()) return IntConstant.constant(0); + int i = t.arity(); + Expression ans = Expression.NONE; + while(i>1) { ans = ans.product(Expression.NONE); i--; } + return ans; + } + maxRecursion--; + } + Env newenv = new Env(); + for(int i=0; i oldenv = env; + env = newenv; + current_function.add(f); + Object ans = visitThis(body); + env = oldenv; + current_function.remove(current_function.size()-1); + if (ans instanceof Formula) k2pos((Formula)ans, x); + if (f.count()==0) cacheForConstants.put(f, ans); + return ans; + } + + /*================================*/ + /* Evaluates an ExprList node. */ + /*================================*/ + + /** Helper method that merge a list of conjuncts or disjoints while minimizing the AST depth (external caller should use i==1) */ + private Formula getSingleFormula(boolean isConjunct, int i, List formulas) throws Err { + // We actually build a "binary heap" where node X's two children are node 2X and node 2X+1 + int n = formulas.size(); + if (n==0) return isConjunct ? Formula.TRUE : Formula.FALSE; + Formula me = cform(formulas.get(i-1)), other; + int child1=i+i, child2=child1+1; + if (child1n) return me; + other = getSingleFormula(isConjunct, child1, formulas); if (isConjunct) me=me.and(other); else me=me.or(other); + if (child2<1 || child2>n) return me; + other = getSingleFormula(isConjunct, child2, formulas); if (isConjunct) me=me.and(other); else me=me.or(other); + return me; + } + + /** {@inheritDoc} */ + @Override public Object visit(ExprList x) throws Err { + if (x.op == ExprList.Op.AND || x.op == ExprList.Op.OR) { + if (x.args.size()==0) return (x.op==ExprList.Op.AND) ? Formula.TRUE : Formula.FALSE; + Formula answer = getSingleFormula(x.op==ExprList.Op.AND, 1, x.args); + return k2pos(answer, x); + } + if (x.op == ExprList.Op.TOTALORDER) { + Expression elem = cset(x.args.get(0)), first = cset(x.args.get(1)), next = cset(x.args.get(2)); + if (elem instanceof Relation && first instanceof Relation && next instanceof Relation) { + Relation lst = frame.addRel("", null, frame.query(true, (Relation)elem, false)); + totalOrderPredicates.add((Relation)elem); totalOrderPredicates.add((Relation)first); totalOrderPredicates.add(lst); totalOrderPredicates.add((Relation)next); + return k2pos(((Relation)next).totalOrder((Relation)elem, (Relation)first, lst), x); + } + Formula f1 = elem.in(first.join(next.reflexiveClosure())); // every element is in the total order + Formula f2 = next.join(first).no(); // first element has no predecessor + Variable e = Variable.unary("v" + Integer.toString(cnt++)); + Formula f3 = e.eq(first).or(next.join(e).one()); // each element (except the first) has one predecessor + Formula f4 = e.eq(elem.difference(next.join(elem))).or(e.join(next).one()); // each element (except the last) has one successor + Formula f5 = e.in(e.join(next.closure())).not(); // there are no cycles + return k2pos(f3.and(f4).and(f5).forAll(e.oneOf(elem)).and(f1).and(f2), x); + } + // This says no(a&b) and no((a+b)&c) and no((a+b+c)&d)... + // Emperically this seems to be more efficient than "no(a&b) and no(a&c) and no(b&c)" + Formula answer = null; + Expression a = null; + for(Expr arg:x.args) { + Expression b=cset(arg); + if (a==null) {a=b;continue;} + if (answer==null) answer=a.intersection(b).no(); else answer=a.intersection(b).no().and(answer); + a=a.union(b); + } + if (answer!=null) return k2pos(answer, x); else return Formula.TRUE; + } + + /*===============================*/ + /* Evaluates an ExprBinary node. */ + /*===============================*/ + + /** {@inheritDoc} */ + @Override public Object visit(ExprBinary x) throws Err { + Expr a=x.left, b=x.right; + Expression s, s2, eL, eR; IntExpression i; Formula f; Object objL, objR; + switch(x.op) { + case IMPLIES: f=cform(a).not().or(cform(b)); return k2pos(f,x); + case IN: return k2pos(isIn(cset(a),b), x); + case NOT_IN: return k2pos(isIn(cset(a),b).not(), x); + case LT: i=cint(a); f=i.lt(cint(b)); return k2pos(f,x); + case LTE: i=cint(a); f=i.lte(cint(b)); return k2pos(f,x); + case GT: i=cint(a); f=i.gt(cint(b)); return k2pos(f,x); + case GTE: i=cint(a); f=i.gte(cint(b)); return k2pos(f,x); + case NOT_LT: i=cint(a); f=i.lt(cint(b)).not(); return k2pos(f,x); + case NOT_LTE: i=cint(a); f=i.lte(cint(b)).not(); return k2pos(f,x); + case NOT_GT: i=cint(a); f=i.gt(cint(b)).not(); return k2pos(f,x); + case NOT_GTE: i=cint(a); f=i.gte(cint(b)).not(); return k2pos(f,x); + case AND: f=cform(a); f=f.and(cform(b)); return k2pos(f,x); + case OR: f=cform(a); f=f.or(cform(b)); return k2pos(f,x); + case IFF: f=cform(a); f=f.iff(cform(b)); return k2pos(f,x); + case PLUSPLUS: s=cset(a); return s.override(cset(b)); + case MUL: i=cint(a); return i.multiply(cint(b)); + case DIV: i=cint(a); return i.divide(cint(b)); + case REM: i=cint(a); return i.modulo(cint(b)); + case SHL: i=cint(a); return i.shl(cint(b)); + case SHR: i=cint(a); return i.shr(cint(b)); + case SHA: i=cint(a); return i.sha(cint(b)); + case PLUS: + return cset(a).union(cset(b)); + //[AM] +// obj = visitThis(a); +// if (obj instanceof IntExpression) { i=(IntExpression)obj; return i.plus(cint(b)); } +// s = (Expression)obj; return s.union(cset(b)); + case IPLUS: + return cint(a).plus(cint(b)); + case MINUS: + // Special exception to allow "0-8" to not throw an exception, where 7 is the maximum allowed integer (when bitwidth==4) + // (likewise, when bitwidth==5, then +15 is the maximum allowed integer, and we want to allow 0-16 without throwing an exception) + if (a instanceof ExprConstant && ((ExprConstant)a).op==ExprConstant.Op.NUMBER && ((ExprConstant)a).num()==0) + if (b instanceof ExprConstant && ((ExprConstant)b).op==ExprConstant.Op.NUMBER && ((ExprConstant)b).num()==max+1) + return IntConstant.constant(min); + return cset(a).difference(cset(b)); + //[AM] +// obj=visitThis(a); +// if (obj instanceof IntExpression) { i=(IntExpression)obj; return i.minus(cint(b));} +// s=(Expression)obj; return s.difference(cset(b)); + case IMINUS: + return cint(a).minus(cint(b)); + case INTERSECT: + s=cset(a); return s.intersection(cset(b)); + case ANY_ARROW_SOME: case ANY_ARROW_ONE: case ANY_ARROW_LONE: + case SOME_ARROW_ANY: case SOME_ARROW_SOME: case SOME_ARROW_ONE: case SOME_ARROW_LONE: + case ONE_ARROW_ANY: case ONE_ARROW_SOME: case ONE_ARROW_ONE: case ONE_ARROW_LONE: + case LONE_ARROW_ANY: case LONE_ARROW_SOME: case LONE_ARROW_ONE: case LONE_ARROW_LONE: + case ISSEQ_ARROW_LONE: + case ARROW: + s=cset(a); return s.product(cset(b)); + case JOIN: + a=a.deNOP(); s=cset(a); s2=cset(b); + if (a instanceof Sig && ((Sig)a).isOne!=null && s2 instanceof BinaryExpression) { + BinaryExpression bin = (BinaryExpression)s2; + if (bin.op()==ExprOperator.PRODUCT && bin.left()==s) return bin.right(); + } + return s.join(s2); + case EQUALS: + objL = visitThis(a); + objR = visitThis(b); + eL = toSet(a, objL); + eR = toSet(b, objR); + if (eL instanceof IntToExprCast && eR instanceof IntToExprCast) + f = ((IntToExprCast) eL).intExpr().eq(((IntToExprCast) eR).intExpr()); + else + f = eL.eq(eR); + return k2pos(f, x); + case NOT_EQUALS: + objL = visitThis(a); + objR = visitThis(b); + eL = toSet(a, objL); + eR = toSet(b, objR); + if (eL instanceof IntToExprCast && eR instanceof IntToExprCast) + f = ((IntToExprCast) eL).intExpr().eq(((IntToExprCast) eR).intExpr()).not(); + else + f = eL.eq(eR).not(); + return k2pos(f, x); + case DOMAIN: + s=cset(a); + s2=cset(b); + for(int j=s2.arity(); j>1; j--) s=s.product(Expression.UNIV); + return s.intersection(s2); + case RANGE: + s=cset(a); + s2=cset(b); + for(int j=s.arity(); j>1; j--) s2=Expression.UNIV.product(s2); + return s.intersection(s2); + } + throw new ErrorFatal(x.pos, "Unsupported operator ("+x.op+") encountered during ExprBinary.accept()"); + } + + /** Helper method that translates the formula "a in b" into a Kodkod formula. */ + private Formula isIn(Expression a, Expr right) throws Err { + Expression b; + if (right instanceof ExprBinary && right.mult!=0 && ((ExprBinary)right).op.isArrow) { + // Handles possible "binary" or higher-arity multiplicity + return isInBinary(a, (ExprBinary)right); + } + switch(right.mult()) { + case EXACTLYOF: b=cset(right); return a.eq(b); + case ONEOF: b=cset(right); return a.one().and(a.in(b)); + case LONEOF: b=cset(right); return a.lone().and(a.in(b)); + case SOMEOF: b=cset(right); return a.some().and(a.in(b)); + default: b=cset(right); return a.in(b); + } + } + + //[AM] + private static boolean am = true; + + /** Helper method that translates the formula "r in (a ?->? b)" into a Kodkod formula. */ + private Formula isInBinary(Expression r, ExprBinary ab) throws Err { + final Expression a=cset(ab.left), b=cset(ab.right); + Decls d=null, d2=null; + Formula ans1, ans2; + // "R in A ->op B" means for each tuple a in A, there are "op" tuples in r that begins with a. + Expression atuple=null, ar=r; + for(int i=a.arity(); i>0; i--) { + Variable v=Variable.unary("v" + Integer.toString(cnt++)); + if (!am) { + if (a.arity()==1) d=v.oneOf(a); else if (d==null) d=v.oneOf(Relation.UNIV); else d=v.oneOf(Relation.UNIV).and(d); + } else { + d = am(a, d, i, v); + } + ar=v.join(ar); + if (atuple==null) atuple=v; else atuple=atuple.product(v); + } + ans1=isIn(ar, ab.right); + switch(ab.op) { + case ISSEQ_ARROW_LONE: + case ANY_ARROW_LONE: case SOME_ARROW_LONE: case ONE_ARROW_LONE: case LONE_ARROW_LONE: ans1=ar.lone().and(ans1); break; + case ANY_ARROW_ONE: case SOME_ARROW_ONE: case ONE_ARROW_ONE: case LONE_ARROW_ONE: ans1=ar.one().and(ans1); break; + case ANY_ARROW_SOME: case SOME_ARROW_SOME: case ONE_ARROW_SOME: case LONE_ARROW_SOME: ans1=ar.some().and(ans1); break; + } + if (a.arity()>1) { Formula tmp=isIn(atuple, ab.left); if (tmp!=Formula.TRUE) ans1=tmp.implies(ans1); } + ans1=ans1.forAll(d); + // "R in A op-> B" means for each tuple b in B, there are "op" tuples in r that end with b. + Expression btuple=null, rb=r; + for(int i=b.arity(); i>0; i--) { + Variable v=Variable.unary("v" + Integer.toString(cnt++)); + if (!am) { + if (b.arity()==1) d2=v.oneOf(b); else if (d2==null) d2=v.oneOf(Relation.UNIV); else d2=v.oneOf(Relation.UNIV).and(d2); + } else { + d2 = am(b, d2, i, v); + } + rb=rb.join(v); + if (btuple==null) btuple=v; else btuple=v.product(btuple); + } + ans2=isIn(rb, ab.left); + switch(ab.op) { + case LONE_ARROW_ANY: case LONE_ARROW_SOME: case LONE_ARROW_ONE: case LONE_ARROW_LONE: ans2=rb.lone().and(ans2); break; + case ONE_ARROW_ANY: case ONE_ARROW_SOME: case ONE_ARROW_ONE: case ONE_ARROW_LONE: ans2=rb.one().and(ans2); break; + case SOME_ARROW_ANY: case SOME_ARROW_SOME: case SOME_ARROW_ONE: case SOME_ARROW_LONE: ans2=rb.some().and(ans2); break; + } + if (b.arity()>1) { Formula tmp=isIn(btuple, ab.right); if (tmp!=Formula.TRUE) ans2=tmp.implies(ans2); } + ans2=ans2.forAll(d2); + // Now, put everything together + Formula ans=r.in(a.product(b)).and(ans1).and(ans2); + if (ab.op==ExprBinary.Op.ISSEQ_ARROW_LONE) { + Expression rr=r; + while(rr.arity()>1) rr=rr.join(Relation.UNIV); + ans=rr.difference(rr.join(A4Solution.KK_NEXT)).in(A4Solution.KK_ZERO).and(ans); + } + return ans; + } + + private Decls am(final Expression a, Decls d, int i, Variable v) { + kodkod.ast.Decl ddd; + if (a.arity() == 1) { + assert i == 1; + ddd = v.oneOf(a); + } else { + ddd = v.oneOf(a.project(IntConstant.constant(i - 1))); + } + if (d == null) + d = ddd; + else + d = ddd.and(d); + return d; + } + + /*===========================*/ + /* Evaluates an ExprQt node. */ + /*===========================*/ + + /** Adds a "one of" in front of X if X is unary and does not have a declared multiplicity. */ + private static Expr addOne(Expr x) { + Expr save = x; + while(x instanceof ExprUnary) { + switch(((ExprUnary)x).op) { + case EXACTLYOF: case SETOF: case ONEOF: case LONEOF: case SOMEOF: return save; + case NOOP: x = ((ExprUnary)x).sub; continue; + default: break; + } + } + return (x.type().arity()!=1) ? x : ExprUnary.Op.ONEOF.make(x.span(), x); + } + + /** Helper method that translates the quantification expression "op vars | sub" */ + private Object visit_qt(final ExprQt.Op op, final ConstList xvars, final Expr sub) throws Err { + if (op == ExprQt.Op.NO) { + return visit_qt(ExprQt.Op.ALL, xvars, sub.not()); + } + if (op == ExprQt.Op.ONE || op == ExprQt.Op.LONE) { + boolean ok = true; + for(int i=0; i guards = new ArrayList(); + for(Decl dep: xvars) { + final Expr dexexpr = addOne(dep.expr); + final Expression dv = cset(dexexpr); + for(ExprHasName dex: dep.names) { + final Variable v = Variable.nary(skolem(dex.label), dex.type().arity()); + final kodkod.ast.Decl newd; + env.put((ExprVar)dex, v); + if (dex.type().arity()!=1) { + guards.add(isIn(v, dexexpr)); + newd = v.setOf(dv); + } else switch(dexexpr.mult()) { + case SETOF: newd = v.setOf(dv); break; + case SOMEOF: newd = v.someOf(dv); break; + case LONEOF: newd = v.loneOf(dv); break; + default: newd = v.oneOf(dv); + } + if (frame!=null) frame.kv2typepos(v, dex.type(), dex.pos); + if (dd==null) dd = newd; else dd = dd.and(newd); + } + } + final Formula ans = (op==ExprQt.Op.SUM) ? null : cform(sub) ; + final IntExpression ians = (op!=ExprQt.Op.SUM) ? null : cint(sub) ; + for(Decl d: xvars) for(ExprHasName v: d.names) env.remove((ExprVar)v); + if (op==ExprQt.Op.COMPREHENSION) return ans.comprehension(dd); // guards.size()==0, since each var has to be unary + if (op==ExprQt.Op.SUM) return ians.sum(dd); // guards.size()==0, since each var has to be unary + if (op==ExprQt.Op.SOME) { + if (guards.size()==0) return ans.forSome(dd); + guards.add(ans); + return Formula.and(guards).forSome(dd); + } else { + if (guards.size()==0) return ans.forAll(dd); + return Formula.and(guards).implies(ans).forAll(dd); + } + } + + /** {@inheritDoc} */ + @Override public Object visit(ExprQt x) throws Err { + Expr xx = x.desugar(); + if (xx instanceof ExprQt) x = (ExprQt)xx; else return visitThis(xx); + Object ans = visit_qt(x.op, x.decls, x.sub); + if (ans instanceof Formula) k2pos((Formula)ans, x); + return ans; + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/translator/TranslateKodkodToJava.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/translator/TranslateKodkodToJava.java new file mode 100644 index 00000000..a306aa95 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/translator/TranslateKodkodToJava.java @@ -0,0 +1,632 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4compiler.translator; + +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.ArrayList; +import java.util.IdentityHashMap; +import java.util.Map; +import java.io.PrintWriter; +import java.io.StringWriter; +import kodkod.ast.BinaryExpression; +import kodkod.ast.BinaryFormula; +import kodkod.ast.BinaryIntExpression; +import kodkod.ast.ComparisonFormula; +import kodkod.ast.Comprehension; +import kodkod.ast.ConstantExpression; +import kodkod.ast.ConstantFormula; +import kodkod.ast.Decl; +import kodkod.ast.Decls; +import kodkod.ast.ExprToIntCast; +import kodkod.ast.IfExpression; +import kodkod.ast.IfIntExpression; +import kodkod.ast.IntComparisonFormula; +import kodkod.ast.IntConstant; +import kodkod.ast.IntExpression; +import kodkod.ast.IntToExprCast; +import kodkod.ast.NaryExpression; +import kodkod.ast.NaryFormula; +import kodkod.ast.NaryIntExpression; +import kodkod.ast.Node; +import kodkod.ast.ProjectExpression; +import kodkod.ast.MultiplicityFormula; +import kodkod.ast.NotFormula; +import kodkod.ast.QuantifiedFormula; +import kodkod.ast.Relation; +import kodkod.ast.RelationPredicate; +import kodkod.ast.UnaryExpression; +import kodkod.ast.SumExpression; +import kodkod.ast.UnaryIntExpression; +import kodkod.ast.Variable; +import kodkod.ast.Expression; +import kodkod.ast.Formula; +import kodkod.ast.RelationPredicate.Function; +import kodkod.ast.visitor.ReturnVisitor; +import kodkod.ast.visitor.VoidVisitor; +import kodkod.instance.Bounds; +import kodkod.instance.TupleSet; +import kodkod.instance.Tuple; +import kodkod.util.ints.IndexedEntry; +import kodkod.util.nodes.PrettyPrinter; + +/** Translate a Kodkod formula node to an equivalent Java program that solves the formula. + * + *

Requirements: atoms must be String objects (since we cannot possibly + * output a Java source code that can re-generate arbitrary Java objects). + */ + +public final class TranslateKodkodToJava implements VoidVisitor { + + /** Count the height of the given Kodkod AST tree. */ + public static int countHeight(Node node) { + ReturnVisitor vis = new ReturnVisitor() { + private int max(int a, int b) { return (a>=b) ? a : b; } + private int max(int a, int b, int c) { return (a>=b) ? (a>=c ? a : c) : (b>=c ? b: c); } + public Integer visit(Relation x) { return 1; } + public Integer visit(IntConstant x) { return 1; } + public Integer visit(ConstantFormula x) { return 1; } + public Integer visit(Variable x) { return 1; } + public Integer visit(ConstantExpression x) { return 1; } + public Integer visit(NotFormula x) { return 1 + x.formula().accept(this); } + public Integer visit(IntToExprCast x) { return 1 + x.intExpr().accept(this); } + public Integer visit(Decl x) { return 1 + x.expression().accept(this); } + public Integer visit(ExprToIntCast x) { return 1 + x.expression().accept(this); } + public Integer visit(UnaryExpression x) { return 1 + x.expression().accept(this); } + public Integer visit(UnaryIntExpression x) { return 1 + x.intExpr().accept(this); } + public Integer visit(MultiplicityFormula x) { return 1 + x.expression().accept(this); } + public Integer visit(BinaryExpression x) { return 1 + max(x.left().accept(this), x.right().accept(this)); } + public Integer visit(ComparisonFormula x) { return 1 + max(x.left().accept(this), x.right().accept(this)); } + public Integer visit(BinaryFormula x) { return 1 + max(x.left().accept(this), x.right().accept(this)); } + public Integer visit(BinaryIntExpression x) { return 1 + max(x.left().accept(this), x.right().accept(this)); } + public Integer visit(IntComparisonFormula x) { return 1 + max(x.left().accept(this), x.right().accept(this)); } + public Integer visit(IfExpression x) { return 1 + max(x.condition().accept(this), x.thenExpr().accept(this), x.elseExpr().accept(this)); } + public Integer visit(IfIntExpression x) { return 1 + max(x.condition().accept(this), x.thenExpr().accept(this), x.elseExpr().accept(this)); } + public Integer visit(SumExpression x) { return 1 + max(x.decls().accept(this), x.intExpr().accept(this)); } + public Integer visit(QuantifiedFormula x) { return 1 + max(x.decls().accept(this), x.formula().accept(this)); } + public Integer visit(Comprehension x) { return 1 + max(x.decls().accept(this), x.formula().accept(this)); } + public Integer visit(Decls x) { + int max = 0, n = x.size(); + for(int i=0; i t = x.columns(); t.hasNext();) { max = max(max, t.next().accept(this)); } + return max; + } + public Integer visit(RelationPredicate x) { + if (x instanceof Function) { + Function f = ((Function)x); + return max(f.domain().accept(this), f.range().accept(this)); + } + return 1; + } + public Integer visit(NaryExpression x) { + int max = 0; + for(int m=0, n=x.size(), i=0; i Requirement: atoms must be String objects (since we cannot possibly + * output a Java source code that can re-generate arbitrary Java objects). + * + * @param formula - the formula to convert + * @param bitwidth - the integer bitwidth + * @param atoms - an iterator over the set of all atoms + * @param bounds - the Kodkod bounds object to use + * @param atomMap - if nonnull, it is used to map the atom name before printing + */ + public static String convert + (Formula formula, int bitwidth, Iterable atoms, Bounds bounds, Map atomMap) { + StringWriter string=new StringWriter(); + PrintWriter file=new PrintWriter(string); + new TranslateKodkodToJava(file, formula, bitwidth, atoms, bounds, atomMap); + if (file.checkError()) { + return ""; // shouldn't happen + } else { + return string.toString(); + } + } + + /** The PrintWriter that is receiving the text. */ + private final PrintWriter file; + + /** This caches nodes that we have already generated. */ + private final IdentityHashMap map=new IdentityHashMap(); + + /** Given a node, return its name (if no name has been chosen, then make a new name) */ + private String makename(Node obj) { + if (map.containsKey(obj)) return null; + String name="x"+(map.size()); + map.put(obj, name); + return name; + } + + /** Given a node, call the visitor to dump its text out, then return its name. */ + private String make(Node x) { x.accept(this); return map.get(x); } + + /** Constructor is private, so that the only way to access this class is via the static convert() method. */ + private TranslateKodkodToJava + (PrintWriter pw, Formula x, int bitwidth, Iterable atoms, Bounds bounds, Map atomMap) { + file=pw; + file.printf("import java.util.Arrays;%n"); + file.printf("import java.util.List;%n"); + file.printf("import kodkod.ast.*;%n"); + file.printf("import kodkod.ast.operator.*;%n"); + file.printf("import kodkod.instance.*;%n"); + file.printf("import kodkod.engine.*;%n"); + file.printf("import kodkod.engine.satlab.SATFactory;%n"); + file.printf("import kodkod.engine.config.Options;%n%n"); + + file.printf("/* %n"); + file.printf(" ==================================================%n"); + file.printf(" kodkod formula: %n"); + file.printf(" ==================================================%n"); + file.print(PrettyPrinter.print(x, 4) + "\n"); + file.printf(" ==================================================%n"); + file.printf("*/%n"); + file.printf("public final class Test {%n%n"); + file.printf("public static void main(String[] args) throws Exception {%n%n"); + ArrayList atomlist=new ArrayList(); + for(Object a:atoms) { + String b = atomMap==null ? null : atomMap.get(a); + atomlist.add(b==null ? a.toString() : b); + } + Collections.sort(atomlist); + for(Relation r:bounds.relations()) { + String name=makename(r); + int a=r.arity(); + if (a==1) + file.printf("Relation %s = Relation.unary(\"%s\");%n", name, r.name()); + else + file.printf("Relation %s = Relation.nary(\"%s\", %d);%n", name, r.name(), a); + } + file.printf("%nList atomlist = Arrays.asList(%n"); + int j=(-1); + for(String a:atomlist) { + if (j!=(-1)) file.printf(","); else j=0; + if (j==5) {file.printf("%n "); j=0;} else {file.printf(" "); j++;} + file.printf("\"%s\"", a); + } + file.printf("%n);%n%n"); + file.printf("Universe universe = new Universe(atomlist);%n"); + file.printf("TupleFactory factory = universe.factory();%n"); + file.printf("Bounds bounds = new Bounds(universe);%n%n"); + for(Relation r:bounds.relations()) { + String n=map.get(r); + TupleSet upper=bounds.upperBound(r); + TupleSet lower=bounds.lowerBound(r); + printTupleset(n+"_upper", upper, atomMap); + if (upper.equals(lower)) { + file.printf("bounds.boundExactly(%s, %s_upper);%n%n",n,n); + } + else if (lower.size()==0) { + file.printf("bounds.bound(%s, %s_upper);%n%n",n,n); + } + else { + printTupleset(n+"_lower", lower, atomMap); + file.printf("bounds.bound(%s, %s_lower, %s_upper);%n%n",n,n,n); + } + } + for(IndexedEntry i:bounds.intBounds()) { + for(Tuple t:i.value()) { + Object a = t.atom(0); + String b = (atomMap!=null ? atomMap.get(a) : null); + String c = (b!=null? b : a.toString()); + file.printf("bounds.boundExactly(%d,factory.range(" + +"factory.tuple(\"%s\"),factory.tuple(\"%s\")));%n", i.index(), c, c); + } + } + file.printf("%n"); + String result=make(x); + file.printf("%nSolver solver = new Solver();"); + file.printf("%nsolver.options().setSolver(SATFactory.DefaultSAT4J);"); + file.printf("%nsolver.options().setBitwidth(%d);",bitwidth != 0 ? bitwidth : 1); + file.printf("%nsolver.options().setFlatten(false);"); + file.printf("%nsolver.options().setIntEncoding(Options.IntEncoding.TWOSCOMPLEMENT);"); + file.printf("%nsolver.options().setSymmetryBreaking(20);"); + file.printf("%nsolver.options().setSkolemDepth(0);"); + file.printf("%nSystem.out.println(\"Solving...\");"); + file.printf("%nSystem.out.flush();"); + file.printf("%nSolution sol = solver.solve(%s,bounds);", result); + file.printf("%nSystem.out.println(sol.toString());"); + file.printf("%n}}%n"); + file.close(); + } + + /** Print the tupleset using the name n. */ + private void printTupleset(String n, TupleSet ts, Map atomMap) { + file.printf("TupleSet %s = factory.noneOf(%d);%n", n, ts.arity()); + for(Tuple t:ts) { + file.printf("%s.add(",n); + for(int i=0; i names=new ArrayList(); + for(Iterator i = x.columns(); i.hasNext(); ) { names.add(make(i.next())); } + for(int i=0; i 0; i--) { + // get enough space into the buffer for the cnf header, which will be written last + buffer.append(' '); + } + buffer.append('\n'); + } catch (Exception ex) { + throw new RuntimeException("WriteCNF failed.", ex); + } + } + + /** Helper method that flushes the buffer. */ + private void flush() { + try { + cnf.writeBytes(buffer.toString()); + buffer.setLength(0); + } catch (IOException ex) { + throw new RuntimeException("WriteCNF failed.", ex); + } + } + + /** {@inheritDoc} */ + @Override protected void finalize() throws Throwable { + super.finalize(); + free(); + } + + /** {@inheritDoc} */ + public void free() { Util.close(cnf); } + + /** {@inheritDoc} */ + public void addVariables(int numVars) { if (numVars >= 0) vars += numVars; } + + /** {@inheritDoc} */ + public boolean addClause(int[] lits) { + if (lits.length>0) { + clauses++; + if (buffer.length() > capacity) flush(); + for(int i=0; i exactly 1 INSTANCE + 0 or more SOURCE + +INSTANCE = 0 or more PRIMSIG + 0 or more SUBSETSIG + 0 or more FIELD + 0 or more SKOLEM + +PRIMSIG = 0 or more ATOM + +SUBSETSIG = 0 or more ATOM + 1 or more TYPE + +FIELD = 0 or more TUPLE + 1 or more TYPES + +SKOLEM = 0 or more TUPLE + 1 or more TYPES + +TUPLE = 1 or more ATOM +ATOM = + +TYPES = 1 or more TYPE +TYPE = + +SOURCE = diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/translator/package.html b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/translator/package.html new file mode 100644 index 00000000..d1ddf4a8 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4compiler/translator/package.html @@ -0,0 +1,5 @@ + + +This package contains the translator from Alloy4 to CNF (using kodkod). + + diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4graph/Artist.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4graph/Artist.java new file mode 100644 index 00000000..4566fd6a --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4graph/Artist.java @@ -0,0 +1,204 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4graph; + +import static java.awt.BasicStroke.CAP_ROUND; +import static java.awt.BasicStroke.JOIN_ROUND; +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Font; +import java.awt.FontMetrics; +import java.awt.Graphics2D; +import java.awt.Shape; +import java.awt.font.FontRenderContext; +import java.awt.font.GlyphVector; +import java.awt.geom.CubicCurve2D; +import java.awt.geom.Rectangle2D; +import java.awt.image.BufferedImage; +import edu.mit.csail.sdg.alloy4.OurPDFWriter; + +/** This class abstracts the drawing operations so that we can + * draw the graph using different frameworks such as Java2D or PDF. + * + *

Thread Safety: Can be called only by the AWT event thread. + */ + +public final strictfp class Artist { + + /** The font name. */ + private static final String fontName = "Lucida Grande"; + + /** The font size. */ + private static final int fontSize = 12; + + /** The corresponding Graphics2D object. */ + private Graphics2D gr; + + /** The corresponding OurPDFWriter. */ + private OurPDFWriter pdf; + + /** Construct an artist that acts as a wrapper around the given Graphics2D object. */ + public Artist(Graphics2D graphics2D) { this.gr=graphics2D; this.pdf=null; } + + /** Construct an artist that acts as a wrapper around the given OurPDFWriter object. */ + public Artist(OurPDFWriter pdfWriter) { this.gr=null; this.pdf=pdfWriter; } + + /** Shifts the coordinate space by the given amount. */ + public void translate(int x, int y) { if (gr!=null) gr.translate(x,y); else pdf.shiftCoordinateSpace(x, y); } + + /** Draws a circle of the given radius, centered at (0,0) */ + public void drawCircle(int radius) { if (gr!=null) gr.drawArc(-radius, -radius, radius*2, radius*2, 0, 360); else pdf.drawCircle(radius, false); } + + /** Fills a circle of the given radius, centered at (0,0) */ + public void fillCircle(int radius) { if (gr!=null) gr.fillArc(-radius, -radius, radius*2, radius*2, 0, 360); else pdf.drawCircle(radius, true); } + + /** Draws a line from (x1,y1) to (x2,y2) */ + public void drawLine(int x1, int y1, int x2, int y2) { if (gr!=null) gr.drawLine(x1,y1,x2,y2); else pdf.drawLine(x1, y1, x2, y2); } + + /** Changes the current color. */ + public void setColor(Color color) { if (gr!=null) gr.setColor(color); else pdf.setColor(color); } + + /** Returns true if left<=x<=right or right<=x<=left. */ + private static boolean in(double left, double x, double right) { return (left<=x && x<=right) || (right<=x && x<=left); } + + /** Draws the given curve smoothly (assuming the curve is monotonic vertically) */ + public void drawSmoothly(Curve curve) { + final int smooth=15; + double cx=0, cy=0, slope; + for(int n=curve.list.size(), i=0; i0) { c.ctrlx1=cx; c.ctrly1=cy; } + if (c2==null) { draw(c,false); return; } + if ((c.x1c.x2 && c2.x2>c2.x1)) slope=0; else slope=(c2.x2-c.x1)/(c2.y2-c.y1); + double tmp=c.y2-smooth, tmpx=c.x2-smooth*slope; + if (tmp>c.ctrly1 && tmpc2.y1 && tmp2 NOTE: as a special guarantee, if gr2d==null, then this method returns immediately without doing anything. + *

NOTE: just like the typical AWT and Swing methods, this method can be called only by the AWT event dispatching thread. + */ + public void set(DotStyle style, double scale) { + if (gr!=null) { + BasicStroke bs; + switch(style) { + case BOLD: bs=new BasicStroke(scale>1 ? (float)(2.6d/scale) : 2.6f); break; + case DOTTED: bs=new BasicStroke(scale>1 ? (float)(1.3d/scale) : 1.3f, CAP_ROUND, JOIN_ROUND, 15f, dot, 0f); break; + case DASHED: bs=new BasicStroke(scale>1 ? (float)(1.3d/scale) : 1.3f, CAP_ROUND, JOIN_ROUND, 15f, dashed, 5f); break; + default: bs=new BasicStroke(scale>1 ? (float)(1.3d/scale) : 1.3f); + } + gr.setStroke(bs); + return; + } + switch(style) { + case BOLD: pdf.setBoldLine(); return; + case DOTTED: pdf.setDottedLine(); return; + case DASHED: pdf.setDashedLine(); return; + default: pdf.setNormalLine(); return; + } + } + + /** Saves the current font boldness. */ + private boolean fontBoldness = false; + + /** Changes the current font. */ + public void setFont(boolean fontBoldness) { + calc(); + if (gr!=null) gr.setFont(fontBoldness ? cachedBoldFont : cachedPlainFont); else this.fontBoldness=fontBoldness; + } + + /** Draws the given string at (x,y) */ + public void drawString(String text, int x, int y) { + if (text.length()==0) return; + if (gr!=null) { gr.drawString(text,x,y); return; } + calc(); + Font font = (fontBoldness ? cachedBoldFont : cachedPlainFont); + GlyphVector gv = font.createGlyphVector(new FontRenderContext(null,false,false), text); + translate(x,y); + draw(gv.getOutline(), true); + translate(-x,-y); + } + + /** If nonnull, it caches a Graphics2D object for calculating string bounds. */ + private static Graphics2D cachedGraphics = null; + + /** If nonnull, it caches a FontMetrics object associated with the nonbold font. */ + private static FontMetrics cachedPlainMetrics = null; + + /** If nonnull, it caches a FontMetrics object associated with the bold font. */ + private static FontMetrics cachedBoldMetrics = null; + + /** If nonnull, it caches the nonbold Font. */ + private static Font cachedPlainFont = null; + + /** If nonnull, it caches the bold Font. */ + private static Font cachedBoldFont = null; + + /** If nonnegative, it caches the maximum ascent of the font. */ + private static int cachedMaxAscent = -1; + + /** If nonnegative, it caches the maximum descent of the font. */ + private static int cachedMaxDescent = -1; + + /** Allocates the nonbold and bold fonts, then calculates the max ascent and descent. */ + private static void calc() { + if (cachedMaxDescent >= 0) return; // already done + BufferedImage image = new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB); + cachedGraphics = (Graphics2D)(image.getGraphics()); + cachedPlainMetrics = cachedGraphics.getFontMetrics(cachedPlainFont = new Font(fontName, Font.PLAIN, fontSize)); + cachedBoldMetrics = cachedGraphics.getFontMetrics(cachedBoldFont = new Font(fontName, Font.BOLD, fontSize)); + cachedGraphics.setFont(cachedPlainFont); + cachedMaxAscent = cachedPlainMetrics.getMaxAscent(); + cachedMaxDescent = cachedPlainMetrics.getMaxDescent(); + } + + /** Returns the max ascent when drawing text using the given font size and font boldness settings. */ + public static int getMaxAscent() { + calc(); + return cachedMaxAscent; + } + + /** Returns the sum of the max ascent and max descent when drawing text using the given font size and font boldness settings. */ + public static int getMaxAscentAndDescent() { + calc(); + return cachedMaxAscent + cachedMaxDescent; + } + + /** Returns the bounding box when drawing the given string using the given font size and font boldness settings. */ + public static Rectangle2D getBounds(boolean fontBoldness, String string) { + calc(); + return (fontBoldness ? cachedBoldMetrics : cachedPlainMetrics).getStringBounds(string, cachedGraphics); + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4graph/AvailableSpace.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4graph/AvailableSpace.java new file mode 100644 index 00000000..51e0a8a5 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4graph/AvailableSpace.java @@ -0,0 +1,61 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4graph; + +import java.util.ArrayList; +import java.util.List; + +/** Mutable; this allows you to compute whether a rectangle overlaps with a set of rectangles or not. + * + *

Thread Safety: Can be called only by the AWT event thread. + */ + +public final class AvailableSpace { + + /** Mutable; represents a rectangle. */ + static final class Box { + /** (x,y) is the top-left corner. */ int x, y; + /** (w,h) is the width and height. */ final int w, h; + public Box(int x, int y, int w, int h) { this.x=x; this.y=y; this.w=w; this.h=h; } + } + + /** The list of existing rectangles; we ensure every rectangle in here has width>0 and height>0. */ + private List list = new ArrayList(); + + /** Construct an empty space. */ + public AvailableSpace() { } + + /** Returns true if the given rectangle does not overlap with any existing rectangle in this space. */ + public boolean ok(int x, int y, int w, int h) { + if (w<=0 || h<=0) return true; // always okay + for(Box box: list) { + if ((x >= box.x && x <= box.x+box.w-1) || (x+w >= box.x+1 && x+w <= box.x+box.w)) + if ((y >= box.y && y <= box.y+box.h-1) || (y+h >= box.y+1 && y+h <= box.y+box.h)) return false; + if ((box.x >= x && box.x <= x+w-1) || (box.x+box.w >= x+1 && box.x+box.w <= x+w)) + if ((box.y >= y && box.y <= y+h-1) || (box.y+box.h >= y+1 && box.y+box.h <= y+h)) return false; + } + return true; + } + + /** Add the given rectangle to the list of rectangles in this space. */ + public void add(int x, int y, int w, int h) { + if (w<=0 || h<=0) return; // no-op + list.add(new Box(x, y, w, h)); + } + + /** Erases the list of rectangles in this space. */ + public void clear() { list.clear(); } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4graph/Curve.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4graph/Curve.java new file mode 100644 index 00000000..abd555fd --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4graph/Curve.java @@ -0,0 +1,219 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4graph; + +import java.util.ArrayList; +import java.util.List; +import java.awt.geom.CubicCurve2D; + +/** Mutable; represents a connected path. + * + *

Thread Safety: Can be called only by the AWT event thread. + */ + +final class Curve { + + /** starting position and ending position. */ + public double startX, startY, endX, endY; + + /** The list of segments in this curve. */ + public final List list = new ArrayList(); + + /** Construct a curve starting at the given location. */ + public Curve(double startX, double startY) { + this.startX=startX; this.endX=startX; + this.startY=startY; this.endY=startY; + } + + /** Make a deep copy of this Curve object. */ + public Curve dup() { + Curve ans = new Curve(startX, startY); + ans.endX = endX; + ans.endY = endY; + for(CubicCurve2D.Double x:list) { + CubicCurve2D.Double c = new CubicCurve2D.Double(); + c.setCurve(x); + ans.list.add(c); + } + return ans; + } + /** Precondition: this.lastX==next.firstX and this.lastY==next.firstY. + * Note: the resulting Curve will still share the same CubicCurve2D objects as this and that. */ + Curve join(Curve that) { + Curve ans = new Curve(startX, startY); + ans.list.addAll(list); + ans.list.addAll(that.list); + ans.endX=that.endX; + ans.endY=that.endY; + return ans; + } + + /** Produce a cubic bezier curve representing a straightline segment from (x1,y1) to (x2,y2) */ + private static CubicCurve2D.Double makeline(double x1, double y1, double x2, double y2) { + return new CubicCurve2D.Double(x1, y1, (x2-x1)*0.3+x1, (y2-y1)*0.3+y1, (x2-x1)*0.6+x1, (y2-y1)*0.6+y1, x2, y2); + } + + /** Add a straightline segment to (ax,ay) */ + public Curve lineTo(double ax, double ay) { + list.add(makeline(endX, endY, ax, ay)); + this.endX = ax; + this.endY = ay; + return this; + } + + /** Add a cubic bezier segment to (cx,cy) using (ax,ay) and (bx,by) as the two control points. */ + public Curve cubicTo(double ax, double ay, double bx, double by, double cx, double cy) { + list.add(new CubicCurve2D.Double(endX, endY, ax, ay, bx, by, cx, cy)); + this.endX = cx; + this.endY = cy; + return this; + } + + /** Returns true if (x1,y1)..(x2,y2) intersects (x,y3)..(x,y4) */ + private static boolean intersects(double x1, double y1, double x2, double y2, double x, double y3, double y4) { + if (!(y3=n) list.clear(); + while(i>0 && !list.isEmpty()) { list.remove(0); i--; } + if (list.isEmpty()) { list.add(new CubicCurve2D.Double(startX=endX, startY=endY, endX,endY, endX,endY, endX,endY)); return; } + CubicCurve2D.Double tmp=new CubicCurve2D.Double(); + divide(t-di, list.get(0), new CubicCurve2D.Double(), tmp); + list.get(0).setCurve(tmp); + startX=tmp.x1; + startY=tmp.y1; + } + + /** Chop the end of this curve. */ + public void chopEnd(double t) { + int n=list.size(); + t=t*n; + double di=StrictMath.floor(t); + int i=(int)di; + // + if (i<0) list.clear(); + if (i>=n) return; + while(i+1 Precondition: the curve is monotonic in the vertical direction. + */ + public double getXatY(double y, double startT, double endT, double defaultValue) { + double a=startT, b=endT, ay=getY(a), by=getY(b); + if (ay>by) { double tmp=ay; ay=by; by=tmp; a=endT; b=startT; } + if (!(ay<=y && y<=by)) return defaultValue; + while(StrictMath.abs(a-b)>0.001D) { + double t=(a+b)/2, ty=getY(t); + if (ty==y) {a=t;break;} else if (ty=n) return endX; else return getX(list.get(i), t-di); + } + + /** Returns the y position at the given point 0 <= t <= 1 */ + public double getY(double t) { + int n=list.size(); + t=t*n; + double di=StrictMath.floor(t); + int i=(int)di; + if (i<0) return startY; else if (i>=n) return endY; else return getY(list.get(i), t-di); + } + + /** Returns the x position of the given segment at the given point 0 <= t <= 1 */ + private double getX(CubicCurve2D.Double curve, double t) { + double px=(curve.ctrlx1-curve.x1)*t+curve.x1, qx=(curve.ctrlx2-curve.ctrlx1)*t+curve.ctrlx1, rx=(curve.x2-curve.ctrlx2)*t+curve.ctrlx2; + double sx=(qx-px)*t+px, tx=(rx-qx)*t+qx; + return (tx-sx)*t+sx; + } + + /** Returns the y position of the given segment at the given point 0 <= t <= 1 */ + private double getY(CubicCurve2D.Double curve, double t) { + double py=(curve.ctrly1-curve.y1)*t+curve.y1, qy=(curve.ctrly2-curve.ctrly1)*t+curve.ctrly1, ry=(curve.y2-curve.ctrly2)*t+curve.ctrly2; + double sy=(qy-py)*t+py, ty=(ry-qy)*t+qy; + return (ty-sy)*t+sy; + } + + /** Given 0<=t<=1 and an existing curve, divide it into two chunks and store the two chunks into "first" and "second" */ + public static void divide(double t, CubicCurve2D.Double curve, CubicCurve2D.Double first, CubicCurve2D.Double second) { + // This algorithm uses de Casteljau's algorithm for chopping one bezier curve into two bezier curves + first.x1 = curve.x1; + second.x2 = curve.x2; + first.ctrlx1 = (1-t)*curve.x1 + t*curve.ctrlx1; + double x = (1-t)*curve.ctrlx1 + t*curve.ctrlx2; + second.ctrlx2 = (1-t)*curve.ctrlx2 + t*curve.x2; + first.ctrlx2 = (1-t)*first.ctrlx1 + t*x; + second.ctrlx1 = (1-t)*x + t*second.ctrlx2; + second.x1 = first.x2 = (1-t)*first.ctrlx2 + t*second.ctrlx1; + // now that we've computed the x coordinates, we now compute the y coordinates + first.y1 = curve.y1; + second.y2 = curve.y2; + first.ctrly1 = (1-t)*curve.y1 + t*curve.ctrly1; + double y = (1-t)*curve.ctrly1 + t*curve.ctrly2; + second.ctrly2 = (1-t)*curve.ctrly2 + t*curve.y2; + first.ctrly2 = (1-t)*first.ctrly1 + t*y; + second.ctrly1 = (1-t)*y + t*second.ctrly2; + second.y1 = first.y2 = (1-t)*first.ctrly2 + t*second.ctrly1; + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4graph/DotColor.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4graph/DotColor.java new file mode 100644 index 00000000..1425307e --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4graph/DotColor.java @@ -0,0 +1,133 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4graph; + +import java.awt.Color; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javax.swing.Icon; +import edu.mit.csail.sdg.alloy4.OurUtil; + +/** Immutable; this defines the set of possible colors. + * + *

Thread Safety: Can be called only by the AWT event thread. + */ + +public enum DotColor { + + MAGIC("Magic", "magic"), + YELLOW("Yellow", "gold", "yellow", "lightgoldenrod", "yellow"), + GREEN("Green", "limegreen", "green2","darkolivegreen2","chartreuse2"), + BLUE("Blue", "cornflowerblue", "blue", "cadetblue", "cyan"), + RED("Red", "palevioletred", "red", "salmon", "magenta"), + GRAY("Gray", "lightgray"), + WHITE("White", "white"), + BLACK("Black", "black"); + + /** The text to display. */ + private final String displayText; + + /** The list of colors to use, corresponding to the current palette; + * if there are more palette choices than colors.size(), then the extra palettes would all use the first color. */ + private final List colors = new ArrayList(); + + /** The list of icons to show, corresponding to the current palette; + * if there are more palette choices than icons.size(), then the extra palettes would all use the first icon. */ + private final List icons = new ArrayList(); + + /** Construct a new DotColor. */ + private DotColor(String text, String... colors) { + displayText = text; + for(int i=0; i name2color = new HashMap(); + + /** Returns the list of values that the user is allowed to select from. */ + public static Object[] valuesWithout(DotColor exclude) { + Object[] ans = new Object[values().length - 1]; + int i = 0; + for(DotColor d: values()) if (d != exclude) ans[i++] = d; + return ans; + } + + /** Returns the Icon that will be displayed in the GUI to represent this value, when used with the given palette. */ + public Icon getIcon(DotPalette pal) { + int i = 0; + for(Object choice: DotPalette.values()) { + if (i >= icons.size()) break; + if (pal == choice) return icons.get(i); + i++; + } + return icons.get(0); + } + + /** Convert this color into its corresponding Java Color object. */ + public Color getColor(DotPalette pal) { + String name = getDotText(pal); + Color ans = name2color.get(name); + if (ans!=null) return ans; + else if (name.equals("palevioletred")) ans = new Color(222,113,148); + else if (name.equals("red")) ans = new Color(255,0,0); + else if (name.equals("salmon")) ans = new Color(255,130,115); + else if (name.equals("magenta")) ans = new Color(255,0,255); + else if (name.equals("limegreen")) ans = new Color(49,207,49); + else if (name.equals("green2")) ans = new Color(0,239,0); + else if (name.equals("darkolivegreen2")) ans = new Color(189,239,107); + else if (name.equals("chartreuse2")) ans = new Color(115,239,0); + else if (name.equals("gold")) ans = new Color(255,215,0); + else if (name.equals("yellow")) ans = new Color(255,255,0); + else if (name.equals("lightgoldenrod")) ans = new Color(239,223,132); + else if (name.equals("cornflowerblue")) ans = new Color(99,150,239); + else if (name.equals("blue")) ans = new Color(0,0,255); + else if (name.equals("cadetblue")) ans = new Color(90,158,165); + else if (name.equals("cyan")) ans = new Color(0,255,255); + else if (name.equals("lightgray")) ans = new Color(214,214,214); + else if (name.equals("white")) ans = Color.WHITE; + else ans = Color.BLACK; + name2color.put(name, ans); + return ans; + } + + /** Returns the String that should be written into the dot file for this value, when used with the given palette. */ + public String getDotText(DotPalette pal) { + int i = 0; + for(Object choice: DotPalette.values()) { + if (i >= colors.size()) break; + if (pal == choice) return colors.get(i); + i++; + } + return colors.get(0); + } + + /** Returns the String that will be displayed in the GUI to represent this value. */ + public String getDisplayedText() { return displayText; } + + /** This method is used in parsing the XML value into a valid color; returns null if there is no match. */ + public static DotColor parse(String x) { + if (x != null) for(DotColor d: values()) if (d.toString().equals(x)) return d; + return null; + } + + /** This value is used in writing XML. */ + @Override public String toString() { return displayText; } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4graph/DotDirection.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4graph/DotDirection.java new file mode 100644 index 00000000..f11c5060 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4graph/DotDirection.java @@ -0,0 +1,37 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4graph; + +/** Immutable; this defines the set of possible edge directions. + * + *

Thread Safety: Can be called only by the AWT event thread. + */ + +public enum DotDirection { + + /** Going forward. */ FORWARD("forward"), + /** Going backward. */ BACK("back"), + /** Going both ways. */ BOTH("both"); + + /** The text to display. */ + private final String displayText; + + /** Constructs a new DotDirection object. */ + private DotDirection(String text) { this.displayText = text; } + + /** Returns the String that should be written into the dot file for this value, when used with the given palette. */ + public String getDotText() { return displayText; } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4graph/DotPalette.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4graph/DotPalette.java new file mode 100644 index 00000000..26c5315e --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4graph/DotPalette.java @@ -0,0 +1,48 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4graph; + +/** Immutable; this defines the set of possible color palettes. + * + *

Thread Safety: Can be called only by the AWT event thread. + */ + +public enum DotPalette { + + // Note: if you change the order, you must also change the ordering of the colors in DotColor class. + /** Classic palette. */ CLASSIC("Classic"), + /** Standard palette. */ STANDARD("Standard"), + /** Martha palette. */ MARTHA("Martha"), + /** Neon palette. */ NEON("Neon"); + + /** The text to display. */ + private final String displayText; + + /** Constructs a DotPalette object with the given label. */ + private DotPalette(String displayText) { this.displayText = displayText; } + + /** This method is used in parsing the XML value into a valid DotPalette; returns null if there is no match. */ + public static DotPalette parse(String x) { + if (x != null) for(DotPalette d: values()) if (d.toString().equals(x)) return d; + return null; + } + + /** Returns the String that will be displayed in the GUI to represent this value. */ + public String getDisplayedText() { return displayText; } + + /** This value is used in writing XML. */ + @Override public String toString() { return displayText; } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4graph/DotShape.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4graph/DotShape.java new file mode 100644 index 00000000..2d722109 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4graph/DotShape.java @@ -0,0 +1,82 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4graph; + +import javax.swing.Icon; +import edu.mit.csail.sdg.alloy4.OurUtil; + +/** Immutable; this defines the set of possible node shapes (BOX, CIRCLE, ELLIPSE...) + * + *

Thread Safety: Can be called only by the AWT event thread. + */ + +public enum DotShape { + + /** Ellipse */ ELLIPSE("Ellipse", "ellipse"), + /** Box */ BOX("Box", "box"), + /** Circle */ CIRCLE("Circle", "circle"), + /** Egg */ EGG("Egg", "egg"), + /** Triangle */ TRIANGLE("Triangle", "triangle"), + /** Diamond */ DIAMOND("Diamond", "diamond"), + /** Trapezoid */ TRAPEZOID("Trapezoid", "trapezium"), + /** Parallelogram */ PARALLELOGRAM("Parallelogram", "parallelogram"), + /** House */ HOUSE("House", "house"), + /** Hexagon */ HEXAGON("Hexagon", "hexagon"), + /** Octagon */ OCTAGON("Octagon", "octagon"), + /** Double Circle */ DOUBLE_CIRCLE("Dbl Circle", "doublecircle"), + /** Double Octagon */ DOUBLE_OCTAGON("Dbl Octagon", "doubleoctagon"), + /** Triple Octagon */ TRIPLE_OCTAGON("Tpl Octagon", "tripleoctagon"), + /** Inverted Triangle */ INV_TRIANGLE("Inv Triangle", "invtriangle"), + /** Inverted House */ INV_HOUSE("Inv House", "invhouse"), + /** Inverted Trapezoid */ INV_TRAPEZOID("Inv Trapezoid", "invtrapezium"), + /** Lined Diamond */ M_DIAMOND("Lined Diamond", "Mdiamond"), + /** Lined Square */ M_SQUARE("Lined Square", "Msquare"), + /** Lined Circle */ M_CIRCLE("Lined Circle", "Mcircle"); + + /** The description of this line style. */ + private final String name; + + /** The icon for this line style. */ + private final Icon icon; + + /** The corresponding DOT attribute. */ + private final String dotName; + + /** Constructs a DotShape object. */ + private DotShape(String name, String dotName) { + this.name = name; + this.icon = OurUtil.loadIcon("icons/ShapeIcons/" + dotName + ".gif"); + this.dotName = dotName; + } + + /** Returns the String that will be displayed in the GUI to represent this value. */ + public String getDisplayedText() { return name; } + + /** Returns the String that should be written into the dot file for this value, when used with the given palette. */ + public String getDotText() { return dotName; } + + /** Returns the Icon that will be displayed in the GUI to represent this value, when used with the given palette. */ + public Icon getIcon() { return icon; } + + /** This method is used in parsing the XML value into a valid Shape; returns null if there is no match. */ + public static DotShape parse(String x) { + if (x != null) for(DotShape d: values()) if (d.name.equals(x)) return d; + return null; + } + + /** This value is used in writing XML. */ + @Override public String toString() { return name; } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4graph/DotStyle.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4graph/DotStyle.java new file mode 100644 index 00000000..7e9221e5 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4graph/DotStyle.java @@ -0,0 +1,73 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4graph; + +import javax.swing.Icon; +import edu.mit.csail.sdg.alloy4.OurUtil; + +/** Immutable; this defines the set of possible line styles (SOLID, DASHED, DOTTED...) + * + *

Thread Safety: Can be called only by the AWT event thread. + */ + +public enum DotStyle { + + /** Solid line. */ + SOLID("Solid", "solid"), + + /** Dashed line. */ + DASHED("Dashed", "dashed"), + + /** Dotted line. */ + DOTTED("Dotted", "dotted"), + + /** Bold line. */ + BOLD("Bold", "bold"); + + /** The description of this line style. */ + private final String name; + + /** The icon for this line style. */ + private final Icon icon; + + /** The corresponding DOT attribute. */ + private final String dotName; + + /** Constructs a DotStyle object. */ + private DotStyle(String name, String dotName) { + this.name = name; + this.icon = OurUtil.loadIcon("icons/StyleIcons/" + dotName + ".gif"); + this.dotName = dotName; + } + + /** Returns the String that will be displayed in the GUI to represent this value. */ + public String getDisplayedText() { return name; } + + /** Returns the String that should be written into the dot file for this value, when used with the given palette. */ + public String getDotText() { return dotName; } + + /** Returns the Icon that will be displayed in the GUI to represent this value, when used with the given palette. */ + public Icon getIcon() { return icon; } + + /** This method is used in parsing the XML value into a valid style; returns null if there is no match. */ + public static DotStyle parse(String x) { + if (x != null) for(DotStyle d: values()) if (d.name.equals(x)) return d; + return null; + } + + /** This value is used in writing XML. */ + @Override public String toString() { return name; } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4graph/Graph.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4graph/Graph.java new file mode 100644 index 00000000..ea327590 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4graph/Graph.java @@ -0,0 +1,762 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4graph; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.IdentityHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.SortedMap; +import java.util.TreeMap; +import java.awt.Color; +import java.awt.geom.Line2D; +import java.awt.geom.RoundRectangle2D; +import edu.mit.csail.sdg.alloy4.Pair; +import edu.mit.csail.sdg.alloy4.Util; +import static edu.mit.csail.sdg.alloy4graph.Artist.getBounds; + +/** Mutable; represents a graph. + * + *

Thread Safety: Can be called only by the AWT event thread. + */ + +public final strictfp class Graph { + + //================================ adjustable options ========================================================================// + + /** Minimum horizontal distance between adjacent nodes. */ + static final int xJump = 30; + + /** Minimum vertical distance between adjacent layers. */ + static final int yJump = 60; + + /** The horizontal distance between the first self-loop and the node itself. */ + static final int selfLoopA = 40; + + /** The horizontal padding to put on the left side of a self-loop's edge label. */ + static final int selfLoopGL = 2; + + /** The horizontal padding to put on the right side of a self-loop's edge label. */ + static final int selfLoopGR = 20; + + /** The maximum ascent and descent. We deliberately do NOT make this field "static" because only AWT thread can call Artist. */ + private final int ad = Artist.getMaxAscentAndDescent(); + + //=============================== fields ======================================================================================// + + /** The default magnification. */ + final double defaultScale; + + /** The left edge. */ + private int left = 0; + + /** The top edge. */ + private int top = 0; + + /** The bottom edge. */ + private int bottom = 0; + + /** The total width of the graph; this value is computed by layout(). */ + private int totalWidth = 0; + + /** The total height of the graph; this value is computed by layout(). */ + private int totalHeight = 0; + + /** The height of each layer. */ + int[] layerPH = null; + + /** The list of layers; must stay in sync with GraphNode.graph and GraphNode.layer + * (empty iff there are no nodes; every node is always in exactly one layer, and appears exactly once in that layer) + */ + final List> layerlist = new ArrayList>(); + + /** The list of nodes; must stay in sync with GraphNode.graph and GraphNode.pos + * (every node is in exactly one graph's nodelist, and appears exactly once in that graph's nodelist) + */ + final List nodelist = new ArrayList(); + + /** The list of edges; must stay in sync with GraphEdge.a.graph and GraphEdge.b.graph + * (every edge is in exactly one graph's edgelist, and appears exactly once in that graph's edgelist) + */ + final List edgelist = new ArrayList(); + + /** An unmodifiable view of the list of nodes. */ + public final List nodes = Collections.unmodifiableList(nodelist); + + /** An unmodifiable view of the list of edges. */ + public final List edges = Collections.unmodifiableList(edgelist); + + /** An unmodifiable empty list. */ + private final List emptyListOfNodes = Collections.unmodifiableList(new ArrayList(0)); + + //============================================================================================================================// + + /** Constructs an empty Graph object. */ + public Graph(double defaultScale) { this.defaultScale = defaultScale; } + + /** Assuming layout() has been called, this returns the left edge. */ + public int getLeft() { return left; } + + /** Assuming layout() has been called, this returns the top edge. */ + public int getTop() { return top; } + + /** Assuming layout() has been called, this returns the total width. */ + public int getTotalWidth() { return totalWidth; } + + /** Assuming layout() has been called, this returns the total height. */ + public int getTotalHeight() { return totalHeight; } + + /** Returns an unmodifiable view of the list of nodes in the given layer (0..#layer-1); return an empty list if no such layer. */ + List layer(int i) { + if (i>=0 && i list = layerlist.get(layer); + GraphNode n1 = list.get(node1), n2 = list.get(node2); + list.set(node1, n2); + list.set(node2, n1); + } + + /** Sort the list of nodes according to the order in the given list. */ + void sortNodes(Iterable newOrder) { + // The nodes that are common to this.nodelist and newOrder are moved to the front of the list, in the given order. + // The nodes that are in this.nodelist but not in newOrder are moved to the back in an unspecified order. + // The nodes that are in newOrder but not in this.nodelist are ignored. + int i=0, n=nodelist.size(); + again: + for(GraphNode x:newOrder) for(int j=i; j comparator) { Collections.sort(layerlist.get(layer), comparator); } + + /** A list of legends; each legend is an Object with the associated text label and color. */ + private final SortedMap,Pair> legends = new TreeMap,Pair>(); + + /** Add a legend with the given object and the associated text label; if color==null, that means we will + * still add this legend into the list of legends, but this legend will be hidden. + */ + public void addLegend(Comparable object, String label, Color color) { legends.put(object, new Pair(label,color)); } + + //============================================================================================================================// + + /** Layout step #1: assign a total order on the nodes. */ + private void layout_assignOrder() { + // This is an implementation of the GR algorithm described by Peter Eades, Xuemin Lin, and William F. Smyth + // in "A Fast & Effective Heuristic for the Feedback Arc Set Problem" + // in Information Processing Letters, Volume 47, Number 6, Pages 319-323, 1993 + final int num = nodes.size(); + if ((Integer.MAX_VALUE-1)/2 < num) throw new OutOfMemoryError(); + // Now, allocate 2n+1 bins labeled -n .. n + // Note: inside this method, whenever we see #in and #out, we ignore repeated edges. + // Note: since Java ArrayList always start at 0, we'll index it by adding "n" to it. + final List> bins = new ArrayList>(2*num+1); + for(int i=0; i<2*num+1; i++) bins.add(new LinkedList()); + // For each N, figure out its in-neighbors and out-neighbors, then put it in the correct bin + ArrayList> grIN=new ArrayList>(num); + ArrayList> grOUT=new ArrayList>(num); + int[] grBIN=new int[num]; + for(GraphNode n: nodes) { + int ni = n.pos(); + LinkedList in=new LinkedList(), out=new LinkedList(); + for(GraphEdge e: n.ins) { GraphNode a = e.a(); if (!in.contains(a)) in.add(a); } + for(GraphEdge e: n.outs) { GraphNode b = e.b(); if (!out.contains(b)) out.add(b); } + grIN.add(in); + grOUT.add(out); + grBIN[ni] = (out.size()==0) ? 0 : (in.size()==0 ? (2*num) : (out.size()-in.size()+num)); + bins.get(grBIN[ni]).add(n); + // bin[0] = { v | #out=0 } + // bin[n + d] = { v | d=#out-#in and #out!=0 and #in!=0 } for -n < d < n + // bin[n + n] = { v | #in=0 and #out>0 } + } + // Main loop + final LinkedList s1=new LinkedList(), s2=new LinkedList(); + while(true) { + GraphNode x=null; + if (!bins.get(0).isEmpty()) { + // If a sink exists, take a sink X and prepend X to S2 + x = bins.get(0).remove(bins.get(0).size()-1); + s1.add(x); + } else for(int j=2*num; j>0; j--) { + // Otherwise, let x be a source if one exists, or a node with the highest #out-#in. Then append X to S1. + List bin=bins.get(j); + int sz=bin.size(); + if (sz>0) { x=bin.remove(sz-1); s2.addFirst(x); break; } + } + if (x==null) break; // This means we're done; else, delete X from its bin, and move each of X's neighbor into their new bin + bins.get(grBIN[x.pos()]).remove(x); + for(GraphNode n:grIN.get(x.pos())) grOUT.get(n.pos()).remove(x); + for(GraphNode n:grOUT.get(x.pos())) grIN.get(n.pos()).remove(x); + for(GraphNode n:Util.fastJoin(grIN.get(x.pos()), grOUT.get(x.pos()))) { + int ni=n.pos(), out=grOUT.get(ni).size(), in=grIN.get(ni).size(); + int b=(out==0)?0:(in==0?(2*num):(out-in+num)); + if (grBIN[ni]!=b) { bins.get(grBIN[ni]).remove(n); grBIN[ni]=b; bins.get(b).add(n); } + } + } + sortNodes(Util.fastJoin(s1,s2)); + } + + //============================================================================================================================// + + /** Layout step #2: reverses all backward edges. */ + private void layout_backEdges() { + for(GraphEdge e: edges) if (e.a().pos() < e.b().pos()) e.set(e.bhead(), e.ahead()).reverse(); + } + + //============================================================================================================================// + + /** Layout step #3: assign the nodes into one or more layers, then return the number of layers. */ + private int layout_decideLayer() { + // Here, for each node X, I compute its maximum length to a sink; if X is a sink, its length to sink is 0. + final int n = nodes.size(); + int[] len = new int[n]; + for(GraphNode x: nodes) { + // Since we ensured that arrows only ever go from a node with bigger pos() to a node with smaller pos(), + // we can compute the "len" array in O(n) time by visiting each node IN THE SORTED ORDER + int max=0; + for(GraphEdge e: x.outs) { + GraphNode y = e.b(); + int yLen = len[y.pos()]+1; + if (max < yLen) max = yLen; + } + len[x.pos()] = max; + } + // Now, we simply do the simplest thing: assign each node to the layer corresponding to its max-length-to-sink. + for(GraphNode x: nodes) x.setLayer(len[x.pos()]); + // Now, apply a simple trick: whenever every one of X's incoming edge is more than one layer above, then move X up + while(true) { + boolean changed = false; + for(GraphNode x: nodes) if (x.ins.size() > 0) { + int closestLayer=layers()+1; + for(GraphEdge e: x.ins) { + int y = e.a().layer(); + if (closestLayer>y) closestLayer=y; + } + if (closestLayer-1>x.layer()) { x.setLayer(closestLayer-1); changed=true; } + } + if (!changed) break; + } + // All done! + return layers(); + } + + //============================================================================================================================// + + /** Layout step #4: add dummy nodes so that each edge only goes between adjacent layers. */ + private void layout_dummyNodesIfNeeded() { + for(final GraphEdge edge: new ArrayList(edges)) { + GraphEdge e = edge; + GraphNode a = e.a(), b=e.b(); + while(a.layer() - b.layer() > 1) { + GraphNode tmp = a; + a = new GraphNode(a.graph, e.uuid).set((DotShape)null); + a.setLayer(tmp.layer()-1); + // now we have three nodes in the vertical order of "tmp", "a", then "b" + e.change(a); // let old edge go from "tmp" to "a" + e = new GraphEdge(a, b, e.uuid, "", e.ahead(), e.bhead(), e.style(), e.color(), e.group); // let new edge go from "a" to "b" + } + } + } + + //============================================================================================================================// + + /** Layout step #5: decide the order of the nodes within each layer. */ + private void layout_reorderPerLayer() { + // This uses the original Barycenter heuristic + final IdentityHashMap map = new IdentityHashMap(); + final double[] bc = new double[nodes.size()+1]; + int i=1; for(GraphNode n:layer(0)) { bc[n.pos()] = i; i++; } + for(int layer=0; layer() { + public int compare(GraphNode o1, GraphNode o2) { + // If the two nodes have the same barycenter, we use their ordering that was established during layout_assignOrder() + if (o1==o2) return 0; + int n = Double.compare(bc[o1.pos()], bc[o2.pos()]); if (n!=0) return n; else if (o1.pos() nodes) { + // This implementation uses the iterative approach described in the paper "Layout of Bayesian Networks" + // by Kim Marriott, Peter Moulder, Lucas Hope, and Charles Twardy + final int n = nodes.size(); + if (n==0) return; + final Block[] block = new Block[n+1]; + block[0] = new Block(); // The sentinel block + for(int i=1; i<=n; i++) { + Block b = new Block(nodes.get(i-1), i); + block[i] = b; + while(block[b.first-1].posn + block[b.first-1].width > b.posn) { + b = new Block(block[b.first-1], b); + block[b.last] = b; + block[b.first] = b; + } + } + int i=1; + while(true) { + Block b = block[i]; + double tmp = b.posn + (nodes.get(b.first-1).getWidth() + nodes.get(b.first-1).getReserved() + xJump)/2D; + nodes.get(i-1).setX((int)tmp); + for(i=i+1; i<=b.last; i++) { + GraphNode v1 = nodes.get(i-1); + GraphNode v2 = nodes.get(i-2); + int xsep = (v1.getWidth() + v1.getReserved() + v2.getWidth() + v2.getReserved())/2 + xJump; + v1.setX(v2.x() + xsep); + } + i=b.last+1; + if (i>n) break; + } + } + + /** This computes the des() value as described in the paper. + *

The desired location of V = ("sum e:in(V) | wt(e) * phi(start of e)" + "sum e:out(V) | wt(e) * phi(end of e)") / wt(v) + */ + private static double des(GraphNode n) { + int wt = wt(n); + if (wt==0) return 0; // This means it has no "in" edges and no "out" edges + double ans=0; + for(GraphEdge e: n.ins) ans += ((double)e.weight()) * e.a().x(); + for(GraphEdge e: n.outs) ans += ((double)e.weight()) * e.b().x(); + return ans/wt; + } + + /** This computes the wt() value as described in the paper. + *

The weight of a node is the sum of the weights of its in-edges and out-edges. + */ + private static int wt(GraphNode n) { + int ans=0; + for(GraphEdge e: n.ins) ans += e.weight(); + for(GraphEdge e: n.outs) ans += e.weight(); + return ans; + } + + /** This corresponds to the Block structure described in the paper. */ + private static final class Block { + /** These fields are described in the paper. */ + private final int first, last, weight; + /** These fields are described in the paper. */ + private final double width, posn, wposn; + /** This constructs a regular block. */ + public Block(GraphNode v, int i) { + first=i; last=i; width=v.getWidth()+v.getReserved()+xJump; posn=des(v)-(width/2); weight=wt(v); wposn=weight*posn; + } + /** This merges the two existing blocks into a new block. */ + public Block(Block a, Block b) { + first=a.first; last=b.last; width=a.width+b.width; + wposn=a.wposn+b.wposn-a.width*b.weight; weight=a.weight+b.weight; posn=wposn/weight; + } + /** This constructs a sentinel block. */ + public Block() { + posn=Double.NEGATIVE_INFINITY; first=0; last=0; weight=0; width=0; wposn=0; + } + } + + //============================================================================================================================// + + /** For each edge coming out of this layer of nodes, add bends to it if it currently overlaps some nodes inappropriately. */ + private void checkUpperCollision(List top) { + final int room=2; // This is how much we need to stay clear of a node's boundary + for(int i=0; i=right) for(int j=i+1; j=0; j--) { // This edge goes from top-right to bottom-left + GraphNode c=top.get(j); + if (c.shape()==null) continue; // You can intersect thru a dummy node + double ctop=c.y()-c.getHeight()/2, cright=c.x()+c.getWidth()/2, cbottom=c.y()+c.getHeight()/2; + e.path().bendDown(cright, ctop-room, cbottom+room, 3); + } + } + } + } + + //============================================================================================================================// + + /** For each edge going into this layer of nodes, add bends to it if it currently overlaps some nodes inappropriately. */ + private void checkLowerCollision(List bottom) { + final int room=2; // This is how much we need to stay clear of a node's boundary + for(int i=0; i=0; j--) { // This edge goes from top-left to bottom-right + GraphNode c=bottom.get(j); + if (c.shape()==null) continue; // You can intersect thru a dummy node + double ctop=c.y()-c.getHeight()/2, cright=c.x()+c.getWidth()/2, cbottom=c.y()+c.getHeight()/2; + e.path().bendUp(cright, ctop-room, cbottom+room, 3); + } + else if (a.x()>=right) for(int j=i+1; j b.layer()) { GraphNode tmp=a; a=b; b=tmp; } + Line2D.Double line = new Line2D.Double(a.x(), a.y(), b.x(), b.y()); + for(GraphNode n:nodes) if (n!=a && n!=b && a.layer()=0; layer--) { + int x=5; // So that we're not touching the left-edge of the window + int h=0; + for(GraphNode n: layer(layer)) { + int nHeight = n.getHeight(), nWidth = n.getWidth(); + n.setX(x + nWidth/2); + if (h < nHeight) h = nHeight; + x = x + nWidth + n.getReserved() + 20; + } + layerPH[layer] = h; + } + + // If there are more than one layer, then iteratively refine the X position of each component 3 times; 4 is a good number + if (layers>1) { + // It's important to NOT DO THIS when layers<=1, because without edges the nodes will overlap each other into the center + for(int i=0; i<3; i++) for(int layer=0; layer=0; layer--) { + final int ph = layerPH[layer]; + for(GraphNode n:layer(layer)) n.setY(py + ph/2); + py = py + ph + yJump; + } + + relayout_edges(true); + + // Since we're doing layout for the first time, we need to explicitly set top and bottom, since + // otherwise "recalcBound" will merely "extend top and bottom" as needed. + recalcBound(true); + } + + //============================================================================================================================// + + /** Re-establish top/left/width/height. */ + void recalcBound(boolean fresh) { + if (nodes.size()==0) { top=0; bottom=10; totalHeight=10; left=0; totalWidth=10; return; } + if (fresh) { top=nodes.get(0).y()-nodes.get(0).getHeight()/2-5; bottom=nodes.get(0).y()+nodes.get(0).getHeight()/2+5; } + // Find the leftmost and rightmost pixel + int minX = nodes.get(0).x() - nodes.get(0).getWidth()/2 - 5; + int maxX = nodes.get(0).x() + nodes.get(0).getWidth()/2 + nodes.get(0).getReserved() + 5; + for(GraphNode n: nodes) { + int min = n.x() - n.getWidth()/2 - 5; if (minX>min) minX=min; + int max = n.x() + n.getWidth()/2 + n.getReserved() + 5; if (maxX0 && e.getLabelH()>0) { + int x1=e.getLabelX(), x2=x1+e.getLabelW()-1; + if (minX>x1) minX=x1; + if (maxX=0; layer--) { + for(GraphNode n: layer(layer)) { + int ytop = n.y()-n.getHeight()/2-5; if (top > ytop) top=ytop; + int ybottom = n.y()+n.getHeight()/2+5; if (bottom < ybottom) bottom=ybottom; + } + } + totalHeight = bottom-top; + int widestLegend = 0, legendHeight = 30; + for(Pair e: legends.values()) { + if (e.b==null) continue; // that means this legend is not visible + int widthOfLegend = (int) getBounds(true, e.a).getWidth(); + if (widestLegend < widthOfLegend) widestLegend = widthOfLegend; + legendHeight += ad; + } + if (widestLegend>0) { + left -= (widestLegend+10); + totalWidth += (widestLegend*2+10); + if (totalHeight() { + public int compare(GraphNode o1, GraphNode o2) { + if (o1.x()o2.x()) return 1; + return 0; + } + }); + // Ensure that nodes are not bunched up together horizontally. + List layer=new ArrayList(layer(i)); + for(int j=layer.size()/2; j>=0 && j0 && j0; layer--) { + List top=layer(layer), bottom=layer(layer-1); + checkUpperCollision(top); checkLowerCollision(bottom); checkUpperCollision(top); + } + // Now, for each edge, adjust its arrowhead and label. + AvailableSpace sp = new AvailableSpace(); + for(GraphNode n: nodes) if (n.shape()!=null) sp.add(n.x()-n.getWidth()/2, n.y()-n.getHeight()/2, n.getWidth()+n.getReserved(), n.getHeight()); + for(GraphEdge e: edges) { e.layout_arrowHead(); e.repositionLabel(sp); } + } + + //============================================================================================================================// + + /** Assuming everything was laid out already, but nodes in layer[i] just moved horizontally, this re-layouts edges to+from layer i. */ + void relayout_edges(int i) { + if (nodes.size()==0) return; // The rest of the code assumes there is at least one node + for(GraphNode n: layer(i)) for(GraphEdge e: n.selfs) { e.resetPath(); e.layout_arrowHead(); } + if (i>0) { + List top=layer(i), bottom=layer(i-1); + for(GraphNode n: top) for(GraphEdge e: n.outs) e.resetPath(); + checkUpperCollision(top); checkLowerCollision(bottom); checkUpperCollision(top); + } + if (i top=layer(i+1), bottom=layer(i); + for(GraphNode n: top) for(GraphEdge e: n.outs) e.resetPath(); + checkUpperCollision(top); checkLowerCollision(bottom); checkUpperCollision(top); + } + // Now, for each edge, adjust its arrowhead and label. + AvailableSpace sp = new AvailableSpace(); + for(GraphNode n:nodes) if (n.shape()!=null) sp.add(n.x()-n.getWidth()/2, n.y()-n.getHeight()/2, n.getWidth()+n.getReserved(), n.getHeight()); + for(GraphEdge e:edges) { e.layout_arrowHead(); e.repositionLabel(sp); } + } + + //============================================================================================================================// + + /** Locates the node or edge at the given (X,Y) location. */ + public Object find(double scale, int mouseX, int mouseY) { + int h = getTop() + 10 - ad; + double x = mouseX/scale + getLeft(), y = mouseY/scale + getTop(); + for(Map.Entry,Pair> e: legends.entrySet()) { + if (e.getValue().b == null) continue; + h = h + ad; + if (y=h+ad) continue; + int w = (int) getBounds(true, e.getValue().a).getWidth(); + if (x>=getLeft()+10 && x<=getLeft()+10+w) return e.getKey(); + } + for(GraphNode n: nodes) { + if (n.shape()==null && Math.abs(n.x()-x)<10 && Math.abs(n.y()-y)<10) return n; + if (n.contains(x,y)) return n; + } + for(GraphEdge e: edges) { + if (e.a() != e.b()) { + double dx; + dx = e.path().getXatY(y, 0, 1, Double.NaN); if (!Double.isNaN(dx) && StrictMath.abs(x-dx)<12/scale) return e; + } else { + double dx; + dx = e.path().getXatY(y, 0.25, 0.75, Double.NaN); if (!Double.isNaN(dx) && StrictMath.abs(x-dx)<12/scale) return e; + dx = e.path().getXatY(y, 0, 0.25, Double.NaN); if (!Double.isNaN(dx) && StrictMath.abs(x-dx)<12/scale) return e; + dx = e.path().getXatY(y, 0.75, 1, Double.NaN); if (!Double.isNaN(dx) && StrictMath.abs(x-dx)<12/scale) return e; + } + } + return null; + } + + //============================================================================================================================// + + /** Assuming layout has been performed, this draws the graph with the given magnification scale. */ + void draw(Artist gr, double scale, Object highlight, boolean showLegends) { + if (nodes.size()==0) return; // The rest of this procedure assumes there is at least one node + Object group = null; + GraphNode highFirstNode = null, highLastNode = null; + GraphEdge highFirstEdge = null, highLastEdge = null; + if (highlight instanceof GraphEdge) { + highFirstEdge = (GraphEdge)highlight; + highLastEdge = highFirstEdge; + group = highFirstEdge.group; + while(highFirstEdge.a().shape() == null) highFirstEdge = highFirstEdge.a().ins.get(0); + while(highLastEdge.b().shape() == null) highLastEdge = highLastEdge.b().outs.get(0); + highFirstNode=highFirstEdge.a(); + highLastNode=highLastEdge.b(); + } else if (!(highlight instanceof GraphNode) && highlight!=null) { + group = highlight; + } + // Since drawing an edge will automatically draw all segments if they're connected via dummy nodes, + // we must make sure we only draw out edges from non-dummy-nodes + int maxAscent = Artist.getMaxAscent(); + for(GraphNode n:nodes) if (n.shape()!=null) { + for(GraphEdge e:n.outs) if (e.group!=group) e.draw(gr, scale, highFirstEdge, group); + for(GraphEdge e:n.selfs) if (e.group!=group) e.draw(gr, scale, highFirstEdge, group); + } + if (group!=null) { + for(GraphNode n:nodes) if (n.shape()!=null) { + for(GraphEdge e:n.outs) if (e.group==group && e!=highFirstEdge) e.draw(gr, scale, highFirstEdge, group); + for(GraphEdge e:n.selfs) if (e.group==group && e!=highFirstEdge) e.draw(gr, scale, highFirstEdge, group); + } + if (highFirstEdge!=null) highFirstEdge.draw(gr, scale, highFirstEdge, group); + } + for(GraphNode n:nodes) if (highFirstNode!=n && highLastNode!=n) n.draw(gr, scale, n==highlight); + if (highFirstNode!=null) highFirstNode.draw(gr, scale, true); + if (highLastNode!=null && highLastNode!=highFirstNode) highLastNode.draw(gr, scale, true); + if (highFirstEdge!=null) highFirstEdge.drawLabel(gr, highFirstEdge.color(), new Color(255,255,255,160)); + // show legends? + if (!showLegends || legends.size()==0) return; + boolean groupFound=false; + int y=0, maxWidth=0; + for(Map.Entry,Pair> e:legends.entrySet()) { + if (e.getValue().b==null) continue; + if (group!=null && e.getKey()==group) groupFound=true; + int w = (int) getBounds(true, e.getValue().a).getWidth(); + if (maxWidth,Pair> e:legends.entrySet()) { + Color color = e.getValue().b; + if (color==null) continue; + gr.setFont((groupFound && e.getKey()==group) || !groupFound); + gr.setColor((!groupFound || e.getKey()==group) ? color : Color.GRAY); + gr.drawString(e.getValue().a, 8, y+maxAscent); + y = y + ad; + } + } + + //============================================================================================================================// + + /** Helper method that encodes a String for printing into a DOT file. */ + static String esc(String name) { + if (name.indexOf('\"') < 0) return name; + StringBuilder out = new StringBuilder(); + for(int i=0; i + * Thread Safety: Can be called only by the AWT event thread. + */ + +public final strictfp class GraphEdge { + + // =============================== adjustable options + // =========================================================================== + + /** The angle (in radian) to fan out the arrow head, if the line is not bold. */ + private final double smallFan = StrictMath.toRadians(16); + + /** The angle (in radian) to fan out the arrow head, if the line is bold. */ + private final double bigFan = StrictMath.toRadians(32); + + // =============================== cached for performance efficiency + // ============================================================ + + /** + * The maximum ascent and descent. We deliberately do NOT make this field "static" because only + * AWT thread can call Artist. + */ + private final int ad = Artist.getMaxAscentAndDescent(); + + // =============================== fields + // ======================================================================================= + + /** + * a user-provided annotation that will be associated with this edge (can be null) (need not be + * unique) + */ + public final Object uuid; + + /** + * a user-provided annotation that will be associated with this edge (all edges with same group + * will be highlighted together) + */ + public final Object group; + + /** + * The "from" node; must stay in sync with GraphNode.ins and GraphNode.outs and GraphNode.selfs + */ + private GraphNode a; + + /** The "to" node; must stay in sync with GraphNode.ins and GraphNode.outs and GraphNode.selfs */ + private GraphNode b; + + /** + * The label (can be ""); NOTE: label will be drawn only if the start node is not a dummy node. + */ + private final String label; + + /** Whether to draw an arrow head on the "from" node; default is false. */ + private boolean ahead = false; + + /** Whether to draw an arrow head on the "to" node; default is true. */ + private boolean bhead = true; + + /** The color of the edge; default is BLACK; never null. */ + private Color color = Color.BLACK; + + /** The line-style of the edge; default is SOLID; never null. */ + private DotStyle style = DotStyle.SOLID; + + /** The edge weight; default is 1; always between 1 and 10000 inclusively. */ + private int weight = 1; + + /** The location and size of the label box; initially (0, 0, label.width, label.height) */ + private final AvailableSpace.Box labelbox; + + /** The actual path corresponding to this edge; initially null until it's computed. */ + private Curve path = null; + + // =========================================================================s==================================================== + + /** + * Construct an edge from "from" to "to" with the given arrow head settings, then add the edge to + * the graph. + */ + GraphEdge(final GraphNode from, final GraphNode to, final Object uuid, final String label, + final boolean drawArrowHeadOnFrom, final boolean drawArrowHeadOnTo, final DotStyle style, + final Color color, Object group) { + if (group instanceof GraphNode) { + throw new IllegalArgumentException("group cannot be a GraphNode"); + } + if (group instanceof GraphEdge) { + throw new IllegalArgumentException("group cannot be a GraphEdge"); + } + if (group == null) { + group = new Object(); + } + this.a = from; + this.b = to; + if (this.a.graph != this.b.graph) { + throw new IllegalArgumentException("You cannot draw an edge between two different graphs."); + } + if (this.a == this.b) { + this.a.selfs.add(this); + } else { + this.a.outs.add(this); + this.b.ins.add(this); + } + this.a.graph.edgelist.add(this); + this.uuid = uuid; + this.group = group; + this.label = label == null ? "" : label; + this.ahead = drawArrowHeadOnFrom; + this.bhead = drawArrowHeadOnTo; + if (style != null) { + this.style = style; + } + if (color != null) { + this.color = color; + } + if (this.label.length() > 0) { + final Rectangle2D box = Artist.getBounds(false, label); + this.labelbox = new AvailableSpace.Box(0, 0, (int) box.getWidth(), (int) box.getHeight()); + } else { + this.labelbox = new AvailableSpace.Box(0, 0, 0, 0); + } + } + + /** Construct an edge from "from" to "to", then add the edge to the graph. */ + public GraphEdge(final GraphNode from, final GraphNode to, final Object uuid, final String label, + final Object group) { + this(from, to, uuid, label, false, true, null, null, group); + } + + /** Returns the "from" node. */ + public GraphNode a() { + return this.a; + } + + /** Returns true if we will draw an arrow head on the "from" node. */ + public boolean ahead() { + return this.ahead; + } + + /** Returns the "to" node. */ + public GraphNode b() { + return this.b; + } + + /** Returns true if we will draw an arrow head on the "to" node. */ + public boolean bhead() { + return this.bhead; + } + + /** Changes the "to" node to the given node. */ + void change(final GraphNode newTo) { + if (this.b.graph != newTo.graph) { + throw new IllegalArgumentException("You cannot draw an edge between two different graphs."); + } + if (this.a == this.b) { + this.a.selfs.remove(this); + } else { + this.a.outs.remove(this); + this.b.ins.remove(this); + } + this.b = newTo; + if (this.a == this.b) { + this.a.selfs.add(this); + } else { + this.a.outs.add(this); + this.b.ins.add(this); + } + } + + /** Returns the line color; never null. */ + public Color color() { + return this.color; + } + + /** + * Assuming this edge's coordinates have been properly assigned, and given the current zoom scale, + * draw the edge. + */ + void draw(final Artist gr, final double scale, final GraphEdge highEdge, final Object highGroup) { + final int top = this.a.graph.getTop(), left = this.a.graph.getLeft(); + gr.translate(-left, -top); + if (highEdge == this) { + gr.setColor(this.color); + gr.set(DotStyle.BOLD, scale); + } else if (highEdge == null && highGroup == null || highGroup == this.group) { + gr.setColor(this.color); + gr.set(this.style, scale); + } else { + gr.setColor(Color.LIGHT_GRAY); + gr.set(this.style, scale); + } + if (this.a == this.b) { + gr.draw(this.path); + } else { + // Concatenate this path and its connected segments into a single VizPath object, then draw it + Curve p = null; + GraphEdge e = this; + while (e.a.shape() == null) { + e = e.a.ins.get(0); // Let e be the first segment of this chain of connected segments + } + while (true) { + p = p == null ? e.path : p.join(e.path); + if (e.b.shape() != null) { + break; + } + e = e.b.outs.get(0); + } + gr.drawSmoothly(p); + } + // if (this.style == DotStyle.DOTTED || this.style == DotStyle.DASHED) { + // gr.set(DotStyle.SOLID, scale); + // } + if (this.uuid instanceof AlloyTuple && ((AlloyTuple) this.uuid).isDashed) { + gr.set(DotStyle.DASHED, scale); + } else { + gr.set(DotStyle.SOLID, scale); + } + gr.translate(left, top); + if (highEdge == null && highGroup == null && this.label.length() > 0) { + this.drawLabel(gr, this.color, null); + } + this.drawArrowhead(gr, scale, highEdge, highGroup); + } + + /** + * Assuming this edge's coordinates have been assigned, and given the current zoom scale, draw the + * arrow heads. + */ + private void drawArrowhead(final Artist gr, final double scale, final GraphEdge highEdge, + final Object highGroup) { + final double tipLength = this.ad * 0.6D; + final int top = this.a.graph.getTop(), left = this.a.graph.getLeft(); + // Check to see if this edge is highlighted or not + double fan = this.style == DotStyle.BOLD ? this.bigFan : this.smallFan; + if (highEdge == this) { + fan = this.bigFan; + gr.setColor(this.color); + gr.set(DotStyle.BOLD, scale); + } else if (highEdge == null && highGroup == null || highGroup == this.group) { + gr.setColor(this.color); + gr.set(this.style, scale); + } else { + gr.setColor(Color.LIGHT_GRAY); + gr.set(this.style, scale); + } + for (GraphEdge e = this;; e = e.b.outs.get(0)) { + if (e.ahead && e.a.shape() != null || e.bhead && e.b.shape() != null) { + final Curve cv = e.path(); + if (e.ahead && e.a.shape() != null) { + final CubicCurve2D.Double bez = cv.list.get(0); + final double ax = bez.x1, ay = bez.y1, bx = bez.ctrlx1, by = bez.ctrly1; + final double t = StrictMath.PI + StrictMath.atan2(ay - by, ax - bx); + final double gx1 = ax + tipLength * StrictMath.cos(t - fan), + gy1 = ay + tipLength * StrictMath.sin(t - fan); + final double gx2 = ax + tipLength * StrictMath.cos(t + fan), + gy2 = ay + tipLength * StrictMath.sin(t + fan); + final GeneralPath gp = new GeneralPath(); + gp.moveTo((float) (gx1 - left), (float) (gy1 - top)); + gp.lineTo((float) (ax - left), (float) (ay - top)); + gp.lineTo((float) (gx2 - left), (float) (gy2 - top)); + gp.closePath(); + gr.draw(gp, true); + if (((AlloyTuple) this.uuid).getAtoms().get(0).changed + && ((AlloyTuple) this.uuid).getAtoms().get(1).impacted + .contains(((AlloyTuple) this.uuid).getAtoms().get(0).getOriginalName())) { + gr.setColor(Color.BLACK); + gr.drawString(" !", (int) gx1 - left + 5, (int) gy1 - top); + } + } + if (e.bhead && e.b.shape() != null) { + final CubicCurve2D.Double bez = cv.list.get(cv.list.size() - 1); + final double bx = bez.x2, by = bez.y2, ax = bez.ctrlx2, ay = bez.ctrly2; + final double t = StrictMath.PI + StrictMath.atan2(by - ay, bx - ax); + final double gx1 = bx + tipLength * StrictMath.cos(t - fan), + gy1 = by + tipLength * StrictMath.sin(t - fan); + final double gx2 = bx + tipLength * StrictMath.cos(t + fan), + gy2 = by + tipLength * StrictMath.sin(t + fan); + final GeneralPath gp = new GeneralPath(); + gp.moveTo((float) (gx1 - left), (float) (gy1 - top)); + gp.lineTo((float) (bx - left), (float) (by - top)); + gp.lineTo((float) (gx2 - left), (float) (gy2 - top)); + gp.closePath(); + gr.draw(gp, true); + if (((AlloyTuple) this.uuid).getAtoms().get(0).changed + && ((AlloyTuple) this.uuid).getAtoms().get(1).impacted + .contains(((AlloyTuple) this.uuid).getAtoms().get(0).getOriginalName())) { + gr.setColor(Color.BLACK); + gr.drawString(" !", (int) gx1 - left + 3, (int) gy1 - top); + } + } + } + if (e.b.shape() != null) { + break; + } + } + } + + /** + * Assuming this edge's coordinates have been properly assigned, and given the desired color, draw + * the edge label. + */ + void drawLabel(final Artist gr, final Color color, final Color erase) { + if (this.label.length() > 0) { + final int top = this.a.graph.getTop(), left = this.a.graph.getLeft(); + gr.translate(-left, -top); + if (erase != null && this.a != this.b) { + final Rectangle2D.Double rect = new Rectangle2D.Double(this.labelbox.x, this.labelbox.y, + this.labelbox.w, this.labelbox.h); + gr.setColor(erase); + gr.draw(rect, true); + } + gr.setColor(color); + gr.drawString(this.label, this.labelbox.x, this.labelbox.y + Artist.getMaxAscent()); + // if (((AlloyTuple) uuid).getAtoms().get(0).changed + // && ((AlloyTuple) uuid).getAtoms().get(1).impacted) + // gr.drawString(" !", labelbox.x - 15, labelbox.y + Artist.getMaxAscent()); + gr.translate(left, top); + return; + } + } + + /** Return the height of the label box. */ + public int getLabelH() { + return this.labelbox.h; + } + + /** Return the width of the label box. */ + public int getLabelW() { + return this.labelbox.w; + } + + /** Return the X coordinate of the top-left corner of the label box. */ + public int getLabelX() { + return this.labelbox.x; + } + + /** Return the Y coordinate of the top-left corner of the label box. */ + public int getLabelY() { + return this.labelbox.y; + } + + /** Returns the label on this edge. */ + public String label() { + return this.label; + } + + /** Positions the arrow heads of the given edge properly. */ + void layout_arrowHead() { + final Curve c = this.path(); + if (this.ahead() && this.a.shape() != null) { + double in = 0D, out = 1D; + while (StrictMath.abs(out - in) > 0.0001D) { + final double t = (in + out) / 2; + if (this.a.contains(c.getX(t), c.getY(t))) { + in = t; + } else { + out = t; + } + } + c.chopStart(in); + } + if (this.bhead() && this.b.shape() != null) { + double in = 1D, out = this.a == this.b ? 0.5D : 0D; + while (StrictMath.abs(out - in) > 0.0001D) { + final double t = (in + out) / 2; + if (this.b.contains(c.getX(t), c.getY(t))) { + in = t; + } else { + out = t; + } + } + c.chopEnd(in); + } + } + + /** + * Returns the current path; if the path was not yet assigned, it returns a straight line from + * "from" node to "to" node. + */ + Curve path() { + if (this.path == null) { + this.resetPath(); + } + return this.path; + } + + /** + * Given that this edge is already well-laid-out, this method moves the label hoping to + * avoid/minimize overlap. + */ + void repositionLabel(final AvailableSpace sp) { + if (this.label.length() == 0 || this.a == this.b) { + return; // labels on self-edges are already re-positioned by GraphEdge.resetPath() + } + final int gap = this.style == DotStyle.BOLD ? 4 : 2; // If the line is bold, we need to shift + // the + // label to the right a little bit + boolean failed = false; + Curve p = this.path; + for (GraphNode a = this.a; a.shape() == null;) { + final GraphEdge e = a.ins.get(0); + a = e.a; + p = e.path().join(p); + } + for (GraphNode b = this.b; b.shape() == null;) { + final GraphEdge e = b.outs.get(0); + b = e.b; + p = p.join(e.path()); + } + for (double t = 0.5D;; t = t + 0.05D) { + if (t >= 1D) { + failed = true; + t = 0.7D; + } + double x1 = p.getX(t), y = p.getY(t), x2 = p.getXatY(y + this.labelbox.h, t, 1D, x1); + int x = (int) (x1 < x2 ? x2 + gap : x1 + gap); + if (failed || sp.ok(x, (int) y, this.labelbox.w, this.labelbox.h)) { + sp.add(this.labelbox.x = x, this.labelbox.y = (int) y, this.labelbox.w, this.labelbox.h); + return; + } + final double t2 = 1D - t; + x1 = p.getX(t2); + y = p.getY(t2); + x2 = p.getXatY(y + this.labelbox.h, t2, 1D, x1); + x = (int) (x1 < x2 ? x2 + gap : x1 + gap); + if (sp.ok(x, (int) y, this.labelbox.w, this.labelbox.h)) { + sp.add(this.labelbox.x = x, this.labelbox.y = (int) y, this.labelbox.w, this.labelbox.h); + return; + } + } + } + + /** + * Reset the path as a straightline from the center of the "from" node to the center of the "to" + * node. + */ + void resetPath() { + final double ax = this.a.x(), ay = this.a.y(); + if (this.a == this.b) { + double w = 0; + for (int n = this.a.selfs.size(), i = 0; i < n; i++) { + if (i == 0) { + w = this.a.getWidth() / 2 + Graph.selfLoopA; + } else { + w = w + Artist.getBounds(false, this.a.selfs.get(i - 1).label()).getWidth() + + Graph.selfLoopGL + Graph.selfLoopGR; + } + final GraphEdge e = this.a.selfs.get(i); + if (e != this) { + continue; + } + final double h = this.a.getHeight() / 2D * 0.7D, k = 0.55238D, + wa = this.a.getWidth() / 2.0D, wb = w - wa; + e.path = new Curve(ax, ay); + e.path.cubicTo(ax, ay - k * h, ax + wa - k * wa, ay - h, ax + wa, ay - h); + e.path.cubicTo(ax + wa + k * wb, ay - h, ax + wa + wb, ay - k * h, ax + wa + wb, ay); + e.path.cubicTo(ax + wa + wb, ay + k * h, ax + wa + k * wb, ay + h, ax + wa, ay + h); + e.path.cubicTo(ax + wa - k * wa, ay + h, ax, ay + k * h, ax, ay); + e.labelbox.x = (int) (ax + w + Graph.selfLoopGL); + e.labelbox.y = (int) (ay - Artist.getBounds(false, e.label()).getHeight() / 2); + break; + } + } else { + int i = 0, n = 0; + for (final GraphEdge e : this.a.outs) { + if (e == this) { + i = n++; + } else if (e.b == this.b) { + n++; + } + } + final double cx = this.b.x(), cy = this.b.y(); + double bx = (ax + cx) / 2; + final double by = (ay + cy) / 2; + this.path = new Curve(ax, ay); + if (n > 1 && (n & 1) == 1) { + if (i < n / 2) { + bx = bx - (n / 2 - i) * 10; + } else if (i > n / 2) { + bx = bx + (i - n / 2) * 10; + } + this.path.lineTo(bx, by).lineTo(cx, cy); + } else if (n > 1) { + if (i < n / 2) { + bx = bx - (n / 2 - i) * 10 + 5; + } else { + bx = bx + (i - n / 2) * 10 + 5; + } + this.path.lineTo(bx, by).lineTo(cx, cy); + } else { + this.path.lineTo(cx, cy); + } + } + } + + /** Swaps the "from" node and "to" node. */ + void reverse() { + if (this.a == this.b) { + return; + } + this.a.outs.remove(this); + this.b.ins.remove(this); + this.a.ins.add(this); + this.b.outs.add(this); + final GraphNode x = this.a; + this.a = this.b; + this.b = x; + } + + /** + * Sets whether we will draw an arrow head on the "from" node, and whether we will draw an arrow + * head on the "to" node. + */ + public GraphEdge set(final boolean from, final boolean to) { + this.ahead = from; + this.bhead = to; + return this; + } + + /** Sets the line color. */ + public GraphEdge set(final Color color) { + if (color != null) { + this.color = color; + } + return this; + } + + /** Sets the line style. */ + public GraphEdge set(final DotStyle style) { + if (style != null) { + this.style = style; + } + return this; + } + + /** Sets the edge weight between 1 and 10000. */ + public GraphEdge set(int weightBetween1And10000) { + if (weightBetween1And10000 < 1) { + weightBetween1And10000 = 1; + } + if (weightBetween1And10000 > 10000) { + weightBetween1And10000 = 10000; + } + this.weight = weightBetween1And10000; + return this; + } + + /** Returns the line style; never null. */ + public DotStyle style() { + return this.style; + } + + /** Returns a DOT representation of this edge (or "" if the start node is a dummy node) */ + @Override + public String toString() { + final GraphNode a = this.a; + GraphNode b = this.b; + if (a.shape() == null) { + return ""; // This means this edge is virtual + } + while (b.shape() == null) { + b = b.outs.get(0).b; + } + String color = Integer.toHexString(this.color.getRGB() & 0xFFFFFF); + while (color.length() < 6) { + color = "0" + color; + } + final StringBuilder out = new StringBuilder(); + out.append("\"N" + a.pos() + "\""); + out.append(" -> "); + out.append("\"N" + b.pos() + "\""); + out.append(" ["); + out.append("uuid = \"" + (this.uuid == null ? "" : Graph.esc(this.uuid.toString())) + "\""); + out.append(", color = \"#" + color + "\""); + out.append(", fontcolor = \"#" + color + "\""); + out.append(", style = \"" + this.style.getDotText() + "\""); + out.append(", label = \"" + Graph.esc(this.label) + "\""); + out.append(", dir = \"" + (this.ahead && this.bhead ? "both" : this.bhead ? "forward" : "back") + + "\""); + out.append(", weight = \"" + this.weight + "\""); + out.append("]\n"); + return out.toString(); + } + + /** Returns the edge weight (which is always between 1 and 10000 inclusively). */ + public int weight() { + return this.weight; + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4graph/GraphNode.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4graph/GraphNode.java new file mode 100644 index 00000000..43d9491a --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4graph/GraphNode.java @@ -0,0 +1,1028 @@ +/* + * Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4graph; + +import java.awt.Color; +import java.awt.Polygon; +import java.awt.Shape; +import java.awt.geom.GeneralPath; +import java.awt.geom.Rectangle2D; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +import edu.mit.csail.sdg.alloy4viz.AlloyAtom; + +/** + * Mutable; represents a graphical node. + * + *

+ * Thread Safety: Can be called only by the AWT event thread. + */ + +public final strictfp class GraphNode { + + // =============================== adjustable options + // ================================================== + + /** This determines the minimum width of a dummy node. */ + private static final int dummyWidth = 30; + + /** This determines the minimum height of a dummy node. */ + private static final int dummyHeight = 10; + + /** + * This determines the minimum amount of padding added above, left, right, and below the text + * label. + */ + private static final int labelPadding = 5; + + /** Color to use to show a highlighted node. */ + private static final Color COLOR_CHOSENNODE = Color.LIGHT_GRAY; + + // =============================== cached for performance =================================== + + /** + * Caches the value of sqrt(3.0). The extra digits in the definition will be truncated by the Java + * compiler. + */ + private static final double sqrt3 = 1.7320508075688772935274463415058723669428052538103806280558D; + + /** + * Caches the value of sin(36 degree). The extra digits in the definition will be truncated by the + * Java compiler. + */ + private static final double sin36 = 0.5877852522924731291687059546390727685976524376431459910723D; + + /** + * Caches the value of cos(36 degree). The extra digits in the definition will be truncated by the + * Java compiler. + */ + private static final double cos36 = 0.8090169943749474241022934171828190588601545899028814310677D; + + /** + * Caches the value of cos(18 degree). The extra digits in the definition will be truncated by the + * Java compiler. + */ + private static final double cos18 = 0.9510565162951535721164393333793821434056986341257502224473D; + + /** + * Caches the value of tan(18 degree). The extra digits in the definition will be truncated by the + * Java compiler. + */ + private static final double tan18 = 0.3249196962329063261558714122151344649549034715214751003078D; + + public static int xLabel, yLabel; + + // =============================== these fields do not affect the computed bounds + // =============================================== + + /** + * The maximum ascent and descent. We deliberately do NOT make this field "static" because only + * AWT thread can call Artist. + */ + private final int ad = Artist.getMaxAscentAndDescent(); + + /** + * a user-provided annotation that will be associated with this node (can be null) (need not be + * unique) + */ + public final Object uuid; + + /** + * The X coordinate of the center of the node; modified by tweak(), layout_computeX(), layout(), + * and relayout_edges() + */ + private int centerX = 0; + + /** + * The Y coordinate of the center of the node; modified by tweak(), layout_computeX(), layout(), + * and relayout_edges() + */ + private int centerY = 0; + + /** + * The graph that this node belongs to; must stay in sync with Graph.nodelist and Graph.layerlist + */ + final Graph graph; + + /** The layer that this node is in; must stay in sync with Graph.layerlist */ + private int layer = 0; + + /** + * The current position of this node in the graph's node list; must stay in sync with + * Graph.nodelist + */ + int pos; + + /** + * The "in" edges not including "self" edges; must stay in sync with GraphEdge.a and GraphEdge.b + */ + final LinkedList ins = new LinkedList<>(); + + // =============================== these fields affect the computed bounds + // =================================================== + + /** + * The "out" edges not including "self" edges; must stay in sync with GraphEdge.a and GraphEdge.b + */ + final LinkedList outs = new LinkedList<>(); + + /** + * The "self" edges; must stay in sync with GraphEdge.a and GraphEdge.b + *

+ * When this value changes, we should invalidate the previously computed bounds information. + */ + final LinkedList selfs = new LinkedList<>(); + + /** + * The font boldness. + *

+ * When this value changes, we should invalidate the previously computed bounds information. + */ + private boolean fontBold = false; + + /** + * The node labels; if null or empty, then the node has no labels. + *

+ * When this value changes, we should invalidate the previously computed bounds information. + */ + private List labels = null; + + /** + * The node color; never null. + *

+ * When this value changes, we should invalidate the previously computed bounds information. + */ + private Color color = Color.WHITE; + + /** + * The line style; never null. + *

+ * When this value changes, we should invalidate the previously computed bounds information. + */ + private DotStyle style = DotStyle.SOLID; + + // ============================ these fields are computed by calcBounds() + // ========================================= + + /** + * The node shape; if null, then the node is a dummy node. + *

+ * When this value changes, we should invalidate the previously computed bounds information. + */ + private DotShape shape = DotShape.BOX; + + /** If (updown>=0), this is the distance from the center to the top edge. */ + private int updown = -1; + + /** If (updown>=0), this is the distance from the center to the left edge. */ + private int side = 0; + + /** + * If (updown>=0), this is the vertical distance between the center of the text label and the + * center of the node. + */ + private int yShift = 0; + + /** If (updown>=0), this is the width of the text label. */ + private int width = 0; + + /** If (updown>=0), this is the height of the text label. */ + private int height = 0; + + /** + * If (updown>=0), this is the amount of space on the right set-aside for self-loops (which is 0 + * if node has no self loops) + */ + private int reserved = 0; + + /** + * If (updown>=0 and shape!=null), this is the bounding polygon. Note: if not null, it must be + * either a GeneralPath or a Polygon. + */ + private Shape poly = null; + + /** + * If (updown>=0 and shape!=null and poly2!=null), then poly2 will also be drawn during the draw() + * method. Note: if not null, it must be either a GeneralPath or a Polygon. + */ + private Shape poly2 = null; + + /** + * If (updown>=0 and shape!=null and poly3!=null), then poly3 will also be drawn during the draw() + * method. Note: if not null, it must be either a GeneralPath or a Polygon. + */ + private Shape poly3 = null; + + public Artist artist; + + // =================================================================================================== + + /** Create a new node with the given list of labels, then add it to the given graph. */ + public GraphNode(final Graph graph, final Object uuid, final String... labels) { + this.uuid = uuid; + this.graph = graph; + this.pos = graph.nodelist.size(); + graph.nodelist.add(this); + if (graph.layerlist.size() == 0) { + graph.layerlist.add(new ArrayList()); + } + graph.layerlist.get(0).add(this); + if (labels != null && labels.length > 0) { + this.labels = new ArrayList<>(labels.length); + for (int i = 0; i < labels.length; i++) { + this.labels.add(labels[i]); + } + } + } + + /** Add the given label after the existing labels, then invalidate the computed bounds. */ + public GraphNode addLabel(final String label) { + if (label == null || label.length() == 0) { + return this; + } + if (this.labels == null) { + this.labels = new ArrayList<>(); + } + this.labels.add(label); + this.updown = -1; + return this; + } + + /** (Re-)calculate this node's bounds. */ + void calcBounds() { + this.reserved = this.yShift = 0; + this.width = 2 * GraphNode.labelPadding; + if (this.width < GraphNode.dummyWidth) { + this.side = GraphNode.dummyWidth / 2; + } + this.height = this.width; + if (this.height < GraphNode.dummyHeight) { + this.updown = GraphNode.dummyHeight / 2; + } + this.poly = this.poly2 = this.poly3 = null; + if (this.shape == null) { + return; + } + Polygon poly = new Polygon(); + if (this.labels != null) { + for (int i = 0; i < this.labels.size(); i++) { + final String t = this.labels.get(i); + final Rectangle2D rect = Artist.getBounds(this.fontBold, t); + final int ww = (int) rect.getWidth() + 1; // Round it up + if (this.width < ww) { + this.width = ww; + } + this.height = this.height + this.ad; + } + } + int hw = (this.width + 1) / 2 + GraphNode.labelPadding; + if (hw < this.ad / 2) { + hw = this.ad / 2; + } + this.width = hw * 2; + this.side = hw; + int hh = (this.height + 1) / 2 + GraphNode.labelPadding; + if (hh < this.ad / 2) { + hh = this.ad / 2; + } + this.height = hh * 2; + this.updown = hh; + switch (this.shape) { + case HOUSE: { + this.yShift = this.ad / 2; + this.updown = this.updown + this.yShift; + poly.addPoint(-hw, this.yShift - hh); + poly.addPoint(0, -this.updown); + poly.addPoint(hw, this.yShift - hh); + poly.addPoint(hw, this.yShift + hh); + poly.addPoint(-hw, this.yShift + hh); + break; + } + case INV_HOUSE: { + this.yShift = -this.ad / 2; + this.updown = this.updown - this.yShift; + poly.addPoint(-hw, this.yShift - hh); + poly.addPoint(hw, this.yShift - hh); + poly.addPoint(hw, this.yShift + hh); + poly.addPoint(0, this.updown); + poly.addPoint(-hw, this.yShift + hh); + break; + } + case TRIANGLE: + case INV_TRIANGLE: { + int dx = (int) (this.height / GraphNode.sqrt3); + dx = dx + 1; + if (dx < 6) { + dx = 6; + } + int dy = (int) (hw * GraphNode.sqrt3); + dy = dy + 1; + if (dy < 6) { + dy = 6; + } + dy = dy / 2 * 2; + this.side += dx; + this.updown += dy / 2; + if (this.shape == DotShape.TRIANGLE) { + this.yShift = dy / 2; + poly.addPoint(0, -this.updown); + poly.addPoint(hw + dx, this.updown); + poly.addPoint(-hw - dx, this.updown); + } else { + this.yShift = -dy / 2; + poly.addPoint(0, this.updown); + poly.addPoint(hw + dx, -this.updown); + poly.addPoint(-hw - dx, -this.updown); + } + break; + } + case HEXAGON: { + this.side += this.ad; + poly.addPoint(-hw - this.ad, 0); + poly.addPoint(-hw, -hh); + poly.addPoint(hw, -hh); + poly.addPoint(hw + this.ad, 0); + poly.addPoint(hw, hh); + poly.addPoint(-hw, hh); + break; + } + case TRAPEZOID: { + this.side += this.ad; + poly.addPoint(-hw, -hh); + poly.addPoint(hw, -hh); + poly.addPoint(hw + this.ad, hh); + poly.addPoint(-hw - this.ad, hh); + break; + } + case INV_TRAPEZOID: { + this.side += this.ad; + poly.addPoint(-hw - this.ad, -hh); + poly.addPoint(hw + this.ad, -hh); + poly.addPoint(hw, hh); + poly.addPoint(-hw, hh); + break; + } + case PARALLELOGRAM: { + this.side += this.ad; + poly.addPoint(-hw, -hh); + poly.addPoint(hw + this.ad, -hh); + poly.addPoint(hw, hh); + poly.addPoint(-hw - this.ad, hh); + break; + } + case M_DIAMOND: + case DIAMOND: { + if (this.shape == DotShape.M_DIAMOND) { + if (hw < 10) { + hw = 10; + this.side = 10; + this.width = 20; + } + if (hh < 10) { + hh = 10; + this.updown = 10; + this.height = 20; + } + } + this.updown += hw; + this.side += hh; + poly.addPoint(-hw - hh, 0); + poly.addPoint(0, -hh - hw); + poly.addPoint(hw + hh, 0); + poly.addPoint(0, hh + hw); + break; + } + case M_SQUARE: { + if (hh < hw) { + hh = hw; + } else { + hw = hh; + } + if (hh < 6) { + hh = 6; + hw = 6; + } + this.width = hw * 2; + this.side = hw; + this.height = hh * 2; + this.updown = hh; + this.side += 4; + this.updown += 4; + poly.addPoint(-hw - 4, -hh - 4); + poly.addPoint(hw + 4, -hh - 4); + poly.addPoint(hw + 4, hh + 4); + poly.addPoint(-hw - 4, hh + 4); + break; + } + case OCTAGON: + case DOUBLE_OCTAGON: + case TRIPLE_OCTAGON: { + final int dx = this.width / 3, dy = this.ad; + this.updown += dy; + poly.addPoint(-hw, -hh); + poly.addPoint(-hw + dx, -hh - dy); + poly.addPoint(hw - dx, -hh - dy); + poly.addPoint(hw, -hh); + poly.addPoint(hw, hh); + poly.addPoint(hw - dx, hh + dy); + poly.addPoint(-hw + dx, hh + dy); + poly.addPoint(-hw, hh); + if (this.shape == DotShape.OCTAGON) { + break; + } + final double c = StrictMath.sqrt(dx * dx + dy * dy), a = dx * dy / c, k = (a + 5) * dy / dx, + r = StrictMath.sqrt((a + 5) * (a + 5) + k * k) - dy; + final double dx1 = (r - 5) * dx / dy, dy1 = -((dx + 5D) * dy / dx - dy - r); + int x1 = (int) StrictMath.round(dx1), y1 = (int) StrictMath.round(dy1); + this.updown += 5; + this.side += 5; + this.poly2 = poly; + poly = new Polygon(); + poly.addPoint(-hw - 5, -hh - y1); + poly.addPoint(-hw + dx - x1, -hh - dy - 5); + poly.addPoint(hw - dx + x1, -hh - dy - 5); + poly.addPoint(hw + 5, -hh - y1); + poly.addPoint(hw + 5, hh + y1); + poly.addPoint(hw - dx + x1, hh + dy + 5); + poly.addPoint(-hw + dx - x1, hh + dy + 5); + poly.addPoint(-hw - 5, hh + y1); + if (this.shape == DotShape.DOUBLE_OCTAGON) { + break; + } + this.updown += 5; + this.side += 5; + this.poly3 = poly; + poly = new Polygon(); + x1 = (int) StrictMath.round(dx1 * 2); + y1 = (int) StrictMath.round(dy1 * 2); + poly.addPoint(-hw - 10, -hh - y1); + poly.addPoint(-hw + dx - x1, -hh - dy - 10); + poly.addPoint(hw - dx + x1, -hh - dy - 10); + poly.addPoint(hw + 10, -hh - y1); + poly.addPoint(hw + 10, hh + y1); + poly.addPoint(hw - dx + x1, hh + dy + 10); + poly.addPoint(-hw + dx - x1, hh + dy + 10); + poly.addPoint(-hw - 10, hh + y1); + break; + } + case M_CIRCLE: + case CIRCLE: + case DOUBLE_CIRCLE: { + int radius = (int) StrictMath.sqrt(hw * (double) hw + (double) hh * hh) + 2; + if (this.shape == DotShape.DOUBLE_CIRCLE) { + radius = radius + 5; + } + final int L = (int) (radius / GraphNode.cos18) + 2, a = (int) (L * GraphNode.sin36), + b = (int) (L * GraphNode.cos36), c = (int) (radius * GraphNode.tan18); + poly.addPoint(-L, 0); + poly.addPoint(-b, a); + poly.addPoint(-c, L); + poly.addPoint(c, L); + poly.addPoint(b, a); + poly.addPoint(L, 0); + poly.addPoint(b, -a); + poly.addPoint(c, -L); + poly.addPoint(-c, -L); + poly.addPoint(-b, -a); + this.updown = L; + this.side = L; + break; + } + case EGG: + case ELLIPSE: { + final int pad = this.ad / 2; + this.side += pad; + this.updown += pad; + final int d = this.shape == DotShape.ELLIPSE ? 0 : this.ad / 2; + final GeneralPath path = new GeneralPath(); + path.moveTo(-this.side, d); + path.quadTo(-this.side, -this.updown, 0, -this.updown); + path.quadTo(this.side, -this.updown, this.side, d); + path.quadTo(this.side, this.updown, 0, this.updown); + path.quadTo(-this.side, this.updown, -this.side, d); + path.closePath(); + this.poly = path; + } + default: { // BOX + if (this.shape != DotShape.BOX) { + final int d = this.ad / 2; + hw = hw + d; + this.side = hw; + hh = hh + d; + this.updown = hh; + } + poly.addPoint(-hw, -hh); + poly.addPoint(hw, -hh); + poly.addPoint(hw, hh); + poly.addPoint(-hw, hh); + } + } + if (this.shape != DotShape.EGG && this.shape != DotShape.ELLIPSE) { + this.poly = poly; + } + for (int i = 0; i < this.selfs.size(); i++) { + if (i == 0) { + this.reserved = this.side + Graph.selfLoopA; + continue; + } + final String label = this.selfs.get(i - 1).label(); + this.reserved = this.reserved + (int) Artist.getBounds(false, label).getWidth() + + Graph.selfLoopGL + Graph.selfLoopGR; + } + if (this.reserved > 0) { + final String label = this.selfs.get(this.selfs.size() - 1).label(); + this.reserved = this.reserved + (int) Artist.getBounds(false, label).getWidth() + + Graph.selfLoopGL + Graph.selfLoopGR; + } + } + + /** Returns true if the node contains the given point or not. */ + boolean contains(final double x, final double y) { + if (this.shape == null) { + return false; + } else if (this.updown < 0) { + this.calcBounds(); + } + return this.poly.contains(x - this.centerX, y - this.centerY); + } + + /** + * Draws this node at its current (x, y) location; this method will call calcBounds() if + * necessary. + */ + void draw(final Artist gr, final double scale, final boolean highlight) { + if (this.shape == null) { + return; + } else if (this.updown < 0) { + this.calcBounds(); + } + final int top = this.graph.getTop(), left = this.graph.getLeft(); + gr.set(this.style, scale); + gr.translate(this.centerX - left, this.centerY - top); + gr.setFont(this.fontBold); + if (highlight) { + gr.setColor(GraphNode.COLOR_CHOSENNODE); + } else { + gr.setColor(this.color); + } + if (this.shape == DotShape.CIRCLE || this.shape == DotShape.M_CIRCLE + || this.shape == DotShape.DOUBLE_CIRCLE) { + final int hw = this.width / 2, hh = this.height / 2; + int radius = (int) StrictMath.sqrt(hw * (double) hw + (double) hh * hh) + 2; + if (this.shape == DotShape.DOUBLE_CIRCLE) { + radius = radius + 5; + } + gr.fillCircle(radius); + gr.setColor(Color.BLACK); + gr.drawCircle(radius); + // if (this.style == DotStyle.DOTTED || this.style == DotStyle.DASHED) { + // gr.set(DotStyle.SOLID, scale); + // } + if (this.uuid instanceof AlloyAtom && ((AlloyAtom) this.uuid).isDashed) { + gr.set(DotStyle.DASHED, scale); + } else { + gr.set(DotStyle.SOLID, scale); + } + if (this.shape == DotShape.M_CIRCLE && 10 * radius >= 25 && radius > 5) { + final int d = (int) StrictMath.sqrt(10 * radius - 25.0D); + if (d > 0) { + gr.drawLine(-d, -radius + 5, d, -radius + 5); + gr.drawLine(-d, radius - 5, d, radius - 5); + } + } + if (this.shape == DotShape.DOUBLE_CIRCLE) { + gr.drawCircle(radius - 5); + } + } else { + gr.draw(this.poly, true); + gr.setColor(Color.BLACK); + gr.draw(this.poly, false); + if (this.poly2 != null) { + gr.draw(this.poly2, false); + } + if (this.poly3 != null) { + gr.draw(this.poly3, false); + } + // if (this.style == DotStyle.DOTTED || this.style == DotStyle.DASHED) { + // gr.set(DotStyle.SOLID, scale); + // } + if (this.uuid instanceof AlloyAtom && ((AlloyAtom) this.uuid).isDashed) { + gr.set(DotStyle.DASHED, scale); + } else { + gr.set(DotStyle.SOLID, scale); + } + if (this.shape == DotShape.M_DIAMOND) { + gr.drawLine(-this.side + 8, -8, -this.side + 8, 8); + gr.drawLine(-8, -this.side + 8, 8, -this.side + 8); + gr.drawLine(this.side - 8, -8, this.side - 8, 8); + gr.drawLine(-8, this.side - 8, 8, this.side - 8); + } + if (this.shape == DotShape.M_SQUARE) { + gr.drawLine(-this.side, -this.side + 8, -this.side + 8, -this.side); + gr.drawLine(this.side, -this.side + 8, this.side - 8, -this.side); + gr.drawLine(-this.side, this.side - 8, -this.side + 8, this.side); + gr.drawLine(this.side, this.side - 8, this.side - 8, this.side); + } + } + gr.set(DotStyle.SOLID, scale); + final int clr = this.color.getRGB() & 0xFFFFFF; + gr.setColor(clr == 0x000000 || clr == 0xff0000 || clr == 0x0000ff ? Color.WHITE : Color.BLACK); + if (this.labels != null && this.labels.size() > 0) { + final int x = -this.width / 2; + int y = this.yShift + -this.labels.size() * this.ad / 2; + for (int i = 0; i < this.labels.size(); i++) { + final String t = this.labels.get(i); + int w = (int) Artist.getBounds(this.fontBold, t).getWidth() + 1; // Round it up + if (this.width > w) { + w = (this.width - w) / 2; + } else { + w = 0; + } + gr.drawString(t, x + w, y + Artist.getMaxAscent()); + if (this.uuid instanceof AlloyAtom && ((AlloyAtom) this.uuid).changed) { + gr.drawString(" *", x + w - 15, y + Artist.getMaxAscent() - 15); + } + this.artist = gr; + GraphNode.xLabel = x + w; + GraphNode.yLabel = y + Artist.getMaxAscent(); + + y = y + this.ad; + } + } + gr.translate(left - this.centerX, top - this.centerY); + } + + /** + * Returns the bounding rectangle (with 2*xfluff added to the width, and 2*yfluff added to the + * height) + */ + Rectangle2D getBoundingBox(final int xfluff, final int yfluff) { + if (this.updown < 0) { + this.calcBounds(); + } + return new Rectangle2D.Double(this.x() - this.side - xfluff, this.y() - this.updown - yfluff, + this.side + this.side + xfluff + xfluff, this.updown + this.updown + yfluff + yfluff); + } + + /** Returns the node height. */ + int getHeight() { + if (this.updown < 0) { + this.calcBounds(); + } + return this.updown + this.updown; + } + + /** + * Returns the amount of space we need to reserve on the right hand side for the self edges (0 if + * this has no self edges now) + */ + int getReserved() { + if (this.selfs.isEmpty()) { + return 0; + } else if (this.updown < 0) { + this.calcBounds(); + } + return this.reserved; + } + + /** Returns the node width. */ + int getWidth() { + if (this.updown < 0) { + this.calcBounds(); + } + return this.side + this.side; + } + + /** Returns an unmodifiable view of the list of "in" edges. */ + public List inEdges() { + return Collections.unmodifiableList(this.ins); + } + + /** Returns the layer that this node is in. */ + int layer() { + return this.layer; + } + + /** Returns an unmodifiable view of the list of "out" edges. */ + public List outEdges() { + return Collections.unmodifiableList(this.outs); + } + + /** + * Returns the node's current position in the node list, which is always between 0 and + * node.size()-1 + */ + int pos() { + return this.pos; + } + + /** Returns an unmodifiable view of the list of "self" edges. */ + public List selfEdges() { + return Collections.unmodifiableList(this.selfs); + } + + /** Changes the node color, then invalidate the computed bounds. */ + public GraphNode set(final Color color) { + if (this.color != color && color != null) { + this.color = color; + this.updown = -1; + } + return this; + } + + /** + * Changes the node shape (where null means change the node into a dummy node), then invalidate + * the computed bounds. + */ + public GraphNode set(final DotShape shape) { + if (this.shape != shape) { + this.shape = shape; + this.updown = -1; + } + return this; + } + + /** Changes the line style, then invalidate the computed bounds. */ + public GraphNode set(final DotStyle style) { + if (this.style != style && style != null) { + this.style = style; + this.updown = -1; + } + return this; + } + + /** Changes the font boldness, then invalidate the computed bounds. */ + public GraphNode setFontBoldness(final boolean bold) { + if (this.fontBold != bold) { + this.fontBold = bold; + this.updown = -1; + } + return this; + } + + /** + * Changes the layer that this node is in; the new layer must be 0 or greater. + *

+ * If a node is removed from a layer, the order of the other nodes in that layer remain unchanged. + *

+ * If a node is added to a new layer, then it is added to the right of the original rightmost node + * in that layer. + */ + void setLayer(final int newLayer) { + if (newLayer < 0) { + throw new IllegalArgumentException("The layer cannot be negative!"); + } + if (this.layer == newLayer) { + return; + } + this.graph.layerlist.get(this.layer).remove(this); + this.layer = newLayer; + while (this.layer >= this.graph.layerlist.size()) { + this.graph.layerlist.add(new ArrayList()); + } + this.graph.layerlist.get(this.layer).add(this); + } + + /** + * Changes the X coordinate of the center of the node, without invalidating the computed bounds. + */ + void setX(final int x) { + this.centerX = x; + } + + /** + * Changes the Y coordinate of the center of the node, without invalidating the computed bounds. + */ + void setY(final int y) { + this.centerY = y; + } + + /** Helper method that sets the Y coordinate of every node in a given layer. */ + private void setY(final int layer, final int y) { + for (final GraphNode n : this.graph.layer(layer)) { + n.centerY = y; + } + } + + /** Returns the node shape (or null if the node is a dummy node). */ + DotShape shape() { + return this.shape; + } + + /** Helper method that shifts a node down. */ + private void shiftDown(int y) { + final int[] ph = this.graph.layerPH; + final int yJump = Graph.yJump / 6; + int i = this.layer(); + this.setY(i, y); + y = y + ph[i] / 2; // y is now the bottom-most edge of this layer + for (i--; i >= 0; i--) { + final List list = this.graph.layer(i); + final GraphNode first = list.get(0); + if (first.centerY - ph[i] / 2 - yJump < y) { + this.setY(i, y + ph[i] / 2 + yJump); + } + y = first.centerY + ph[i] / 2; + } + this.graph.relayout_edges(false); + } + + /** Helper method that shifts a node left. */ + private void shiftLeft(final List peers, int i, int x) { + final int xJump = Graph.xJump / 3; + this.centerX = x; + x = x - (this.shape == null ? 0 : this.side); // x is now the left-most edge of this node + for (i--; i >= 0; i--) { + final GraphNode node = peers.get(i); + final int side = node.shape == null ? 0 : node.side; + if (node.centerX + side + node.getReserved() + xJump > x) { + node.centerX = x - side - node.getReserved() - xJump; + } + x = node.centerX - side; + } + } + + /** Helper method that shifts a node right. */ + private void shiftRight(final List peers, int i, int x) { + final int xJump = Graph.xJump / 3; + this.centerX = x; + x = x + (this.shape == null ? 0 : this.side) + this.getReserved(); // x is now the right most + // edge of this node + for (i++; i < peers.size(); i++) { + final GraphNode node = peers.get(i); + final int side = node.shape == null ? 0 : node.side; + if (node.centerX - side - xJump < x) { + node.centerX = x + side + xJump; + } + x = node.centerX + side + node.getReserved(); + } + } + + /** Helper method that shifts a node up. */ + private void shiftUp(int y) { + final int[] ph = this.graph.layerPH; + final int yJump = Graph.yJump / 6; + int i = this.layer(); + this.setY(i, y); + y = y - ph[i] / 2; // y is now the top-most edge of this layer + for (i++; i < this.graph.layers(); i++) { + final List list = this.graph.layer(i); + final GraphNode first = list.get(0); + if (first.centerY + ph[i] / 2 + yJump > y) { + this.setY(i, y - ph[i] / 2 - yJump); + } + y = first.centerY - ph[i] / 2; + } + this.graph.relayout_edges(false); + } + + /** Helper method that swaps a node towards the left. */ + private void swapLeft(final List peers, int i, final int x) { + final int side = this.shape == null ? 2 : this.side; + final int left = x - side; + while (true) { + if (i == 0) { + this.centerX = x; + return; + } // no clash possible + final GraphNode other = peers.get(i - 1); + final int otherSide = other.shape == null ? 0 : other.side; + final int otherRight = other.centerX + otherSide + other.getReserved(); + if (otherRight < left) { + this.centerX = x; + return; + } // no clash + this.graph.swapNodes(this.layer(), i, i - 1); + i--; + if (other.shape != null) { + other.shiftRight(peers, i + 1, x + side + this.getReserved() + otherSide); + } + } + } + + /** Helper method that swaps a node towards the right. */ + private void swapRight(final List peers, int i, final int x) { + final int side = this.shape == null ? 2 : this.side; + final int right = x + side + this.getReserved(); + while (true) { + if (i == peers.size() - 1) { + this.centerX = x; + return; + } // no clash possible + final GraphNode other = peers.get(i + 1); + final int otherSide = other.shape == null ? 0 : other.side; + final int otherLeft = other.centerX - otherSide; + if (otherLeft > right) { + this.centerX = x; + return; + } // no clash + this.graph.swapNodes(this.layer(), i, i + 1); + i++; + if (other.shape != null) { + other.shiftLeft(peers, i - 1, x - side - other.getReserved() - otherSide); + } + } + } + + /** Returns a DOT representation of this node (or "" if this is a dummy node) */ + @Override + public String toString() { + if (this.shape == null) { + return ""; // This means it's a virtual node + } + final int rgb = this.color.getRGB() & 0xFFFFFF; + final String text = rgb == 0xFF0000 || rgb == 0x0000FF || rgb == 0 ? "FFFFFF" : "000000"; + String main = Integer.toHexString(rgb); + while (main.length() < 6) { + main = "0" + main; + } + final StringBuilder out = new StringBuilder(); + out.append("\"N" + this.pos + "\""); + out.append(" ["); + out.append("uuid=\""); + if (this.uuid != null) { + out.append(Graph.esc(this.uuid.toString())); + } + out.append("\", label=\""); + boolean first = true; + if (this.labels != null) { + for (final String label : this.labels) { + if (label.length() > 0) { + out.append((first ? "" : "\\n") + Graph.esc(label)); + first = false; + } + } + } + out.append("\", color=\"#" + main + "\""); + out.append(", fontcolor = \"#" + text + "\""); + out.append(", shape = \"" + this.shape.getDotText() + "\""); + out.append(", style = \"filled, " + this.style.getDotText() + "\""); + out.append("]\n"); + return out.toString(); + } + + /** + * Assuming the graph is already laid out, this shifts this node (and re-layouts nearby + * nodes/edges as necessary) + */ + void tweak(final int x, final int y) { + if (this.centerX == x && this.centerY == y) { + return; // If no change, then return right away + } + final List layer = this.graph.layer(this.layer()); + final int n = layer.size(); + int i; + for (i = 0; i < n; i++) { + if (layer.get(i) == this) { + break; // Figure out this node's position in its layer + } + } + if (this.centerX > x) { + this.swapLeft(layer, i, x); + } else if (this.centerX < x) { + this.swapRight(layer, i, x); + } + if (this.centerY > y) { + this.shiftUp(y); + } else if (this.centerY < y) { + this.shiftDown(y); + } else { + this.graph.relayout_edges(this.layer()); + } + this.graph.recalcBound(false); + } + + // =================================================================================================== + + /** Returns the X coordinate of the center of the node. */ + public int x() { + return this.centerX; + } + + // =================================================================================================== + + /** Returns the Y coordinate of the center of the node. */ + public int y() { + return this.centerY; + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4graph/GraphViewer.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4graph/GraphViewer.java new file mode 100644 index 00000000..a307d9fb --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4graph/GraphViewer.java @@ -0,0 +1,440 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4graph; + +import static java.awt.event.InputEvent.BUTTON1_MASK; +import static java.awt.event.InputEvent.BUTTON3_MASK; +import static java.awt.event.InputEvent.CTRL_MASK; +import static java.awt.Color.WHITE; +import static java.awt.Color.BLACK; +import java.awt.Color; +import java.awt.Component; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Rectangle; +import java.awt.RenderingHints; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.event.MouseMotionAdapter; +import java.awt.geom.AffineTransform; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import javax.swing.JLabel; +import javax.swing.JMenuItem; +import javax.swing.JPanel; +import javax.swing.JPopupMenu; +import javax.swing.JRadioButton; +import javax.swing.JTextField; +import javax.swing.JViewport; +import javax.swing.border.EmptyBorder; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import edu.mit.csail.sdg.alloy4.OurDialog; +import edu.mit.csail.sdg.alloy4.OurPDFWriter; +import edu.mit.csail.sdg.alloy4.OurPNGWriter; +import edu.mit.csail.sdg.alloy4.OurUtil; +import edu.mit.csail.sdg.alloy4.Util; + +/** This class displays the graph. + * + *

Thread Safety: Can be called only by the AWT event thread. + */ + +public final strictfp class GraphViewer extends JPanel { + + /** This ensures the class can be serialized reliably. */ + private static final long serialVersionUID = 0; + + /** The graph that we are displaying. */ + private final Graph graph; + + /** The current amount of zoom. */ + private double scale = 1d; + + /** The currently hovered GraphNode or GraphEdge or group, or null if there is none. */ + private Object highlight = null; + + /** The currently selected GraphNode or GraphEdge or group, or null if there is none. */ + private Object selected = null; + + /** The button that initialized the drag-and-drop; this value is undefined when we're not currently doing drag-and-drop. */ + private int dragButton = 0; + + /** The right-click context menu associated with this JPanel. */ + public final JPopupMenu pop = new JPopupMenu(); + + /** Locates the node or edge at the given (X,Y) location. */ + private Object alloyFind(int mouseX, int mouseY) { return graph.find(scale, mouseX, mouseY); } + + /** Returns the annotation for the node or edge at location x,y (or null if none) */ + public Object alloyGetAnnotationAtXY(int mouseX, int mouseY) { + Object obj = alloyFind(mouseX, mouseY); + if (obj instanceof GraphNode) return ((GraphNode)obj).uuid; + if (obj instanceof GraphEdge) return ((GraphEdge)obj).uuid; + return null; + } + + /** Returns the annotation for the currently selected node/edge (or null if none) */ + public Object alloyGetSelectedAnnotation() { + if (selected instanceof GraphNode) return ((GraphNode)selected).uuid; + if (selected instanceof GraphEdge) return ((GraphEdge)selected).uuid; + return null; + } + + /** Returns the annotation for the currently highlighted node/edge (or null if none) */ + public Object alloyGetHighlightedAnnotation() { + if (highlight instanceof GraphNode) return ((GraphNode)highlight).uuid; + if (highlight instanceof GraphEdge) return ((GraphEdge)highlight).uuid; + return null; + } + + /** Stores the mouse positions needed to calculate drag-and-drop. */ + private int oldMouseX=0, oldMouseY=0, oldX=0, oldY=0; + + /** Repaint this component. */ + public void alloyRepaint() { + Container c=getParent(); + while(c!=null) { if (c instanceof JViewport) break; else c=c.getParent(); } + setSize((int)(graph.getTotalWidth()*scale), (int)(graph.getTotalHeight()*scale)); + if (c!=null) { c.invalidate(); c.repaint(); c.validate(); } else { invalidate(); repaint(); validate(); } + } + + /** Construct a GraphViewer that displays the given graph. */ + public GraphViewer(final Graph graph) { + OurUtil.make(this, BLACK, WHITE, new EmptyBorder(0,0,0,0)); + setBorder(null); + this.scale = graph.defaultScale; + this.graph = graph; + graph.layout(); + final JMenuItem zoomIn = new JMenuItem("Zoom In"); + final JMenuItem zoomOut = new JMenuItem("Zoom Out"); + final JMenuItem zoomToFit = new JMenuItem("Zoom to Fit"); + final JMenuItem print = new JMenuItem("Export to PNG or PDF"); + pop.add(zoomIn); + pop.add(zoomOut); + pop.add(zoomToFit); + pop.add(print); + ActionListener act = new ActionListener() { + public void actionPerformed(ActionEvent e) { + Container c=getParent(); + while(c!=null) { if (c instanceof JViewport) break; else c=c.getParent(); } + if (e.getSource() == print) alloySaveAs(); + if (e.getSource() == zoomIn) { scale=scale*1.33d; if (!(scale<500d)) scale=500d; } + if (e.getSource() == zoomOut) { scale=scale/1.33d; if (!(scale>0.1d)) scale=0.1d; } + if (e.getSource() == zoomToFit) { + if (c==null) return; + int w=c.getWidth()-15, h=c.getHeight()-15; // 15 gives a comfortable round-off margin + if (w<=0 || h<=0) return; + double scale1 = ((double)w)/graph.getTotalWidth(), scale2 = ((double)h)/graph.getTotalHeight(); + if (scale1 Note: we intentionally choose to make it an instance field rather than a static field, + * since we want to make sure we only instantiate it from the AWT Event Dispatching thread. + */ + private final Color badColor = new Color(255,200,200); + + /** This synchronized field stores the most recent DPI value. */ + private static volatile double oldDPI=72; + + /** True if we are currently in the middle of a DocumentListener already. */ + private boolean recursive=false; + + /** This updates the three input boxes and the three accompanying text labels, then return the width in pixels. */ + private int alloyRefresh (int who, double ratio, JTextField w1, JLabel w2, JTextField h1, JLabel h2, JTextField d1, JLabel d2, JLabel msg) { + if (recursive) return 0; + try { + recursive=true; + w1.setBackground(WHITE); h1.setBackground(WHITE); d1.setBackground(WHITE); + boolean bad=false; + double w; try { w=Double.parseDouble(w1.getText()); } catch(NumberFormatException ex) { w=0; } + double h; try { h=Double.parseDouble(h1.getText()); } catch(NumberFormatException ex) { h=0; } + double d; try { d=Double.parseDouble(d1.getText()); } catch(NumberFormatException ex) { d=0; } + if (who==1) { h=((int)(w*100/ratio))/100D; h1.setText(""+h); } // Maintains aspect ratio + if (who==2) { w=((int)(h*100*ratio))/100D; w1.setText(""+w); } // Maintains aspect ratio + if (!(d>=0.01) || !(d<=10000)) { + bad=true; + d1.setBackground(badColor); + msg.setText("DPI must be between 0.01 and 10000"); + } + if (!(h>=0.01) || !(h<=10000)) { + bad=true; + h1.setBackground(badColor); + msg.setText("Height must be between 0.01 and 10000"); + if (who==1) h1.setText(""); + } + if (!(w>=0.01) || !(w<=10000)) { + bad=true; + w1.setBackground(badColor); + msg.setText("Width must be between 0.01 and 10000"); + if (who==2) w1.setText(""); + } + if (bad) { w2.setText(" inches"); h2.setText(" inches"); return 0; } else msg.setText(" "); + w2.setText(" inches ("+(int)(w*d)+" pixels)"); + h2.setText(" inches ("+(int)(h*d)+" pixels)"); + return (int)(w*d); + } finally { + recursive=false; + } + } + + /** Export the current drawing as a PNG or PDF file by asking the user for the filename and the image resolution. */ + public void alloySaveAs() { + // Figure out the initial width, height, and DPI that we might want to suggest to the user + final double ratio=((double)(graph.getTotalWidth()))/graph.getTotalHeight(); + double dpi, iw=8.5D, ih=((int)(iw*100/ratio))/100D; // First set the width to be 8.5inch and compute height accordingly + if (ih>11D) { ih=11D; iw=((int)(ih*100*ratio))/100D; } // If too tall, then set height=11inch, and compute width accordingly + synchronized(GraphViewer.class) { dpi=oldDPI; } + // Prepare the dialog box + final JLabel msg = OurUtil.label(" ", Color.RED); + final JLabel w = OurUtil.label("Width: "+((int)(graph.getTotalWidth()*scale))+" pixels"); + final JLabel h = OurUtil.label("Height: "+((int)(graph.getTotalHeight()*scale))+" pixels"); + final JTextField w1 = new JTextField(""+iw); final JLabel w0 = OurUtil.label("Width: "), w2 = OurUtil.label(""); + final JTextField h1 = new JTextField(""+ih); final JLabel h0 = OurUtil.label("Height: "), h2 = OurUtil.label(""); + final JTextField d1 = new JTextField(""+(int)dpi); final JLabel d0 = OurUtil.label("Resolution: "), d2 = OurUtil.label(" dots per inch"); + final JTextField dp1 = new JTextField(""+(int)dpi);final JLabel dp0 = OurUtil.label("Resolution: "), dp2 = OurUtil.label(" dots per inch"); + alloyRefresh(0, ratio, w1, w2, h1, h2, d1, d2, msg); + Dimension dim = new Dimension(100,20); + w1.setMaximumSize(dim); w1.setPreferredSize(dim); w1.setEnabled(false); + h1.setMaximumSize(dim); h1.setPreferredSize(dim); h1.setEnabled(false); + d1.setMaximumSize(dim); d1.setPreferredSize(dim); d1.setEnabled(false); + dp1.setMaximumSize(dim); dp1.setPreferredSize(dim); dp1.setEnabled(false); + w1.getDocument().addDocumentListener(new DocumentListener() { + public void changedUpdate(DocumentEvent e) { alloyRefresh(1,ratio,w1,w2,h1,h2,d1,d2,msg); } + public void insertUpdate(DocumentEvent e) { changedUpdate(null); } + public void removeUpdate(DocumentEvent e) { changedUpdate(null); } + }); + h1.getDocument().addDocumentListener(new DocumentListener() { + public void changedUpdate(DocumentEvent e) { alloyRefresh(2,ratio,w1,w2,h1,h2,d1,d2,msg); } + public void insertUpdate(DocumentEvent e) { changedUpdate(null); } + public void removeUpdate(DocumentEvent e) { changedUpdate(null); } + }); + d1.getDocument().addDocumentListener(new DocumentListener() { + public void changedUpdate(DocumentEvent e) { alloyRefresh(3,ratio,w1,w2,h1,h2,d1,d2,msg); } + public void insertUpdate(DocumentEvent e) { changedUpdate(null); } + public void removeUpdate(DocumentEvent e) { changedUpdate(null); } + }); + final JRadioButton b1 = new JRadioButton("As a PNG with the window's current magnification:", true); + final JRadioButton b2 = new JRadioButton("As a PNG with a specific width, height, and resolution:", false); + final JRadioButton b3 = new JRadioButton("As a PDF with the given resolution:", false); + b1.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + b2.setSelected(false); b3.setSelected(false); + if (!b1.isSelected()) b1.setSelected(true); + w1.setEnabled(false); h1.setEnabled(false); d1.setEnabled(false); dp1.setEnabled(false); msg.setText(" "); + w1.setBackground(WHITE); h1.setBackground(WHITE); d1.setBackground(WHITE); + } + }); + b2.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + b1.setSelected(false); b3.setSelected(false); + if (!b2.isSelected()) b2.setSelected(true); + w1.setEnabled(true); h1.setEnabled(true); d1.setEnabled(true); dp1.setEnabled(false); msg.setText(" "); + alloyRefresh(1,ratio,w1,w2,h1,h2,d1,d2,msg); + } + }); + b3.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + b1.setSelected(false); b2.setSelected(false); + if (!b3.isSelected()) b3.setSelected(true); + w1.setEnabled(false); h1.setEnabled(false); d1.setEnabled(false); dp1.setEnabled(true); msg.setText(" "); + w1.setBackground(WHITE); h1.setBackground(WHITE); d1.setBackground(WHITE); + } + }); + // Ask whether the user wants to change the width, height, and DPI + double myScale; + while(true) { + if (!OurDialog.getInput("Export as PNG or PDF", new Object[]{ + b1, OurUtil.makeH(20, w, null), OurUtil.makeH(20, h, null), " ", + b2, OurUtil.makeH(20, w0, w1, w2, null), + OurUtil.makeH(20, h0, h1, h2, null), + OurUtil.makeH(20, d0, d1, d2, null), + OurUtil.makeH(20, msg, null), + b3, OurUtil.makeH(20, dp0, dp1, dp2, null) + })) return; + // Let's validate the values + if (b2.isSelected()) { + int widthInPixel=alloyRefresh(3,ratio,w1,w2,h1,h2,d1,d2,msg); + String err = msg.getText().trim(); + if (err.length()>0) continue; + dpi=Double.parseDouble(d1.getText()); + myScale=((double)widthInPixel)/graph.getTotalWidth(); + int heightInPixel=(int)(graph.getTotalHeight()*myScale); + if (widthInPixel>4000 || heightInPixel>4000) + if (!OurDialog.yesno("The image dimension ("+widthInPixel+"x"+heightInPixel+") is very large. Are you sure?")) + continue; + } else if (b3.isSelected()) { + try { dpi=Double.parseDouble(dp1.getText()); } catch(NumberFormatException ex) { dpi=(-1); } + if (dpi<50 || dpi>3000) { OurDialog.alert("The DPI must be between 50 and 3000."); continue; } + myScale=0; // This field is unused for PDF generation + } else { + dpi=72; + myScale=scale; + } + break; + } + // Ask the user for a filename + File filename; + if (b3.isSelected()) + filename = OurDialog.askFile(false, null, ".pdf", "PDF file"); + else + filename = OurDialog.askFile(false, null, ".png", "PNG file"); + if (filename==null) return; + if (filename.exists() && !OurDialog.askOverwrite(filename.getAbsolutePath())) return; + // Attempt to write the PNG or PDF file + try { + System.gc(); // Try to avoid possible premature out-of-memory exceptions + if (b3.isSelected()) + alloySaveAsPDF(filename.getAbsolutePath(), (int)dpi); + else + alloySaveAsPNG(filename.getAbsolutePath(), myScale, dpi, dpi); + synchronized(GraphViewer.class) { oldDPI=dpi; } + Util.setCurrentDirectory(filename.getParentFile()); + } catch(Throwable ex) { + OurDialog.alert("An error has occured in writing the output file:\n" + ex); + } + } + + /** Export the current drawing as a PDF file with the given image resolution. */ + public void alloySaveAsPDF(String filename, int dpi) throws IOException { + try { + double xwidth = dpi*8L+(dpi/2L); // Width is up to 8.5 inch + double xheight = dpi*11L; // Height is up to 11 inch + double scale1 = (xwidth-dpi) / graph.getTotalWidth(); // We leave 0.5 inch on the left and right + double scale2 = (xheight-dpi) / graph.getTotalHeight(); // We leave 0.5 inch on the left and right + if (scale1 + +This package performs graph layout. + + diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4viz/AlloyAtom.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4viz/AlloyAtom.java new file mode 100644 index 00000000..9f617ea5 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4viz/AlloyAtom.java @@ -0,0 +1,177 @@ +/* + * Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4viz; + +import java.util.ArrayList; + +/** + * Immutable; represents an Alloy atom in an instance. + * + *

+ * Thread Safety: Can be called only by the AWT event thread. + */ + +public final class AlloyAtom implements Comparable { + public boolean isDashed = false; + public boolean changed = false; + + public ArrayList impacted = new ArrayList<>(); + + /** The original name of this atom from the original Kodkod or other analysis. */ + private final String originalName; + + /** The most specific AlloyType that this atom belongs to. */ + private final AlloyType type; + + /** + * The index is a number that differentiates atoms of the same AlloyType; one special convention: + * (this atom is the only atom with this type) iff (index==Integer.MAX_VALUE) + */ + private final int index; + + /** Create a new AlloyAtom with the given type and index. */ + public AlloyAtom(final AlloyType type, final int index) { + this.type = type; + this.index = index; + this.originalName = type.getName() + "." + index; + } + + /** Create a new AlloyAtom with the given type, index, and label. */ + public AlloyAtom(final AlloyType type, final int index, final String originalName) { + this.type = type; + this.index = index; + this.originalName = originalName; + } + + /** + * Compare first by type, then by index, then by the original names.
+ * We guarantee x.equals(y) iff x.compareTo(y)==0 + * + *

+ * As a special cosmetic enhancement: if we're comparing integer atoms, we want to ignore the + * difference between seqInt and Int. + */ + @Override + public int compareTo(final AlloyAtom otherAtom) { + if (otherAtom == null) { + return 1; + } + AlloyType at = this.type; + if (at.equals(AlloyType.SEQINT)) { + at = AlloyType.INT; + } + AlloyType bt = otherAtom.type; + if (bt.equals(AlloyType.SEQINT)) { + bt = AlloyType.INT; + } + // This renaming is necessary in order to make sure the comparison is transitive. + // For example, assuming seq/Int comprises 0..3, then we want atom0 < atom5, + // even though atom0's TYPENAME > atom5's TYPENAME. + // On the other hand, if you have an atom X with type X, then we want to make sure X>5 just like + // X>0 + // (even though lexically, the type name "X" < the type name "seq/Int" + if (at.equals(AlloyType.INT) && bt.equals(AlloyType.INT)) { + return this.index < otherAtom.index ? -1 : this.index > otherAtom.index ? 1 : 0; + } + final int result = at.compareTo(bt); + if (result != 0) { + return result; + } + // We don't want to use the "return (index-otherAtom.index);" trick, + // especially since singleton sets will have index of Integer.MAX_VALUE. + if (this.index != otherAtom.index) { + return this.index < otherAtom.index ? -1 : 1; + } + return this.originalName.compareTo(otherAtom.originalName); + } + + /** Two AlloyAtoms are equal if they have the same type, same index, and same original name. */ + @Override + public boolean equals(final Object other) { + if (!(other instanceof AlloyAtom)) { + return false; + } + if (other == this) { + return true; + } + final AlloyAtom otherAtom = (AlloyAtom) other; + return this.index == otherAtom.index && this.type.equals(otherAtom.type) + && this.originalName.equals(otherAtom.originalName); + } + + public String getOriginalName() { + return this.originalName; + } + + /** Return the type of the AlloyAtom. */ + public AlloyType getType() { + return this.type; + } + + /** + * Return a label for this atom as recommended by a theme (theme can be null if there's no theme + * to consult). + */ + public String getVizName(final VizState theme, final boolean numberAtoms) { + if (theme != null) { + if (theme.useOriginalName() || this.type.getName().equals("String")) { + return this.originalName; + } + if (this.index == Integer.MAX_VALUE && this.type.getName().equals("Int") + && theme.label.get(this.type).length() == 0) { + // Special handling for Meta Model. (Only meta model could have index==MAX_VALUE) + return "Int"; + } + if (this.index == Integer.MIN_VALUE && this.type.getName().equals("seq/Int") + && theme.label.get(this.type).length() == 0) { + // Special handling for Meta Model. (Only meta model could have index==MIN_VALUE) + return "seq/Int"; + } + if (this.index == Integer.MAX_VALUE || !numberAtoms) { + return theme.label.get(this.type); + } else { + return theme.label.get(this.type) + this.index; + } + } + if (this.type.getName().equals("Int")) { + return "" + this.index; // Special override to display integers better + } + if (this.type.getName().equals("seq/Int")) { + return "" + this.index; // Special override to display integers better + } + if (this.index == Integer.MAX_VALUE || !numberAtoms) { + return this.type.getName(); + } else { + return this.type.getName() + this.index; + } + } + + /** Returns a hash code based on the type and index. */ + @Override + public int hashCode() { + return 7 * this.type.hashCode() + 5 * this.index + 17 * this.originalName.hashCode(); + } + + /** Provides a human-readable label for debugging purpose. */ + @Override + public String toString() { + return this.getVizName(null, true); + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4viz/AlloyElement.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4viz/AlloyElement.java new file mode 100644 index 00000000..3f0f1cdf --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4viz/AlloyElement.java @@ -0,0 +1,33 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4viz; + +/** Immutable; it's the abstract superclass extended by AlloyType, AlloySet, and AlloyRelation. + * + *

Thread Safety: Can be called only by the AWT event thread. + */ + +public abstract class AlloyElement implements Comparable { + + /** The name of the element. */ + private final String name; + + /** Returns the name of this element. */ + public String getName() { return name; } + + /** Constructs a new AlloyElement with that name. */ + AlloyElement(String name) { this.name=name; } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4viz/AlloyInstance.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4viz/AlloyInstance.java new file mode 100644 index 00000000..d1cb3046 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4viz/AlloyInstance.java @@ -0,0 +1,316 @@ +/* + * Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4viz; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +import java.util.TreeSet; +import edu.mit.csail.sdg.alloy4.ConstList; +import edu.mit.csail.sdg.alloy4compiler.translator.A4Solution; + +/** + * Immutable; represents an Alloy instance that can be displayed in the visualizer. + * + *

+ * Thread Safety: Can be called only by the AWT event thread. + */ + +public final class AlloyInstance { + + /** The original A4Solution object. */ + final A4Solution originalA4; // FIXTHIS: eventually we shouldn't need this field... + + /** If true, it is a metamodel, else it is not a metamodel. */ + public final boolean isMetamodel; + + /** The original filename of the model that generated this instance; can be "" if unknown. */ + public final String filename; + + /** The original command that generated this instance; can be "" if unknown. */ + public final String commandname; + + /** The AlloyModel that this AlloyInstance is an instance of. */ + public final AlloyModel model; + + /** + * Maps each AlloyAtom to the AlloySet(s) it is in; its keySet is considered the universe of all + * atoms.
+ * The constructor ensures every AlloySet here is in model.getSets()
+ * Furthermore, every AlloyAtom's type is in model.getTypes()
+ * Finally, if an atom A is in a set S, we guarantee that A.type is equal or subtype of S.type + */ + public Map> atom2sets; + + /** + * Maps each AlloyType to the AlloyAtom(s) in that type; it is derived from atom2sets.keySet() + * directly.
+ * Thus, every AlloyType here is in model.getTypes(), and every AlloyAtom here is in + * atom2sets.keySet()
+ * Furthermore, the constructor ensures that if an atom is in a subtype, it is also in the + * supertype. + */ + private final Map> type2atoms; + + /** + * Maps each AlloySet to the AlloyAtom(s) in that set; it is derived from atom2sets directly.
+ * Thus, every AlloySet here is in model.getSets(), and every AlloyAtom here is in + * atom2sets.keySet()
+ * Finally, if an atom A is in a set S, we guarantee that A.type is equal or subtype of S.type + */ + private final Map> set2atoms; + + /** + * Maps each AlloyRelation to a set of AlloyTuple(s).
+ * The constructor ensures every AlloyRelation here is in model.getRelations()
+ * Furthermore, every AlloyAtom in every AlloyTuple here is in atom2sets.keySet()
+ * Finally, if a tuple T is in a relation R, we guarantee that T is a legal tuple for R (Meaning + * T.arity==R.arity, and for each i, T.getAtom(i).type is equal or subtype of R.getType(i).) + */ + public final Map> rel2tuples; + + /** This always stores an empty unmodifiable list of atoms. */ + private static final List noAtom = ConstList.make(); + + /** This always stores an empty unmodifiable list of sets. */ + private static final List noSet = ConstList.make(); + + /** This always stores an empty unmodifiable set of tuples. */ + private static final Set noTuple = + Collections.unmodifiableSet(new TreeSet()); + + /** + * Create a new instance. + * + * @param filename - the original filename of the model that generated this instance; can be "" if + * unknown + * @param commandname - the original command that generated this instance; can be "" if unknown + * @param model - the AlloyModel that this AlloyInstance is an instance of + * @param atom2sets - maps each atom to the set(s) it is in; its KeySet is considered the universe + * of all atoms + * @param rel2tuples - maps each relation to the tuple(s) it is in + *

+ * (The constructor will ignore any atoms, sets, types, and relations not in the model. So + * there's no need to explicitly filter them out prior to passing "atom2sets" and + * "rel2tuples" to the constructor.) + */ + public AlloyInstance(A4Solution originalA4, String filename, String commandname, AlloyModel model, + Map> atom2sets, Map> rel2tuples, + boolean isMetamodel) { + this.originalA4 = originalA4; + this.filename = filename; + this.commandname = commandname; + this.model = model; + this.isMetamodel = isMetamodel; + // First, construct atom2sets (Use a treemap because we want its keyset to be sorted) + { + Map> a2s = new TreeMap>(); + for (Map.Entry> e : atom2sets.entrySet()) { + AlloyAtom atom = e.getKey(); + if (!model.hasType(atom.getType())) + continue; // We discard any AlloyAtom whose type is not in this model + // We discard any AlloySet not in this model; and we discard AlloySet(s) that don't match + // the atom's type + List sets = new ArrayList(); + for (AlloySet set : e.getValue()) + if (model.getSets().contains(set) + && model.isEqualOrSubtype(atom.getType(), set.getType())) + sets.add(set); + Collections.sort(sets); + a2s.put(atom, ConstList.make(sets)); + } + this.atom2sets = Collections.unmodifiableMap(a2s); + } + // Next, construct set2atoms + { + Map> s2a = new LinkedHashMap>(); + for (Map.Entry> e : this.atom2sets.entrySet()) + for (AlloySet set : e.getValue()) { + List atoms = s2a.get(set); + if (atoms == null) { + atoms = new ArrayList(); + s2a.put(set, atoms); + } + atoms.add(e.getKey()); + } + for (AlloySet set : model.getSets()) { + List atoms = s2a.get(set); + if (atoms == null) + continue; + Collections.sort(atoms); + s2a.put(set, Collections.unmodifiableList(atoms)); + } + this.set2atoms = Collections.unmodifiableMap(s2a); + } + // Next, construct type2atoms + { + Map> t2a = new LinkedHashMap>(); + for (AlloyAtom a : this.atom2sets.keySet()) { + for (AlloyType t = a.getType(); t != null; t = model.getSuperType(t)) { + List atoms = t2a.get(t); + if (atoms == null) { + atoms = new ArrayList(); + t2a.put(t, atoms); + } + atoms.add(a); + } + } + for (AlloyType t : model.getTypes()) { + List atoms = t2a.get(t); + if (atoms == null) + continue; + Collections.sort(atoms); + t2a.put(t, Collections.unmodifiableList(atoms)); + } + this.type2atoms = Collections.unmodifiableMap(t2a); + } + // Finally, construct rel2tuples + Map> r2t = new LinkedHashMap>(); + for (Map.Entry> e : rel2tuples.entrySet()) { + AlloyRelation rel = e.getKey(); + if (!model.getRelations().contains(rel)) + continue; // We discard any AlloyRelation not in this model + Set tuples = new TreeSet(); + for (AlloyTuple tuple : e.getValue()) { + if (tuple.getArity() != rel.getArity()) + continue; // The arity must match + for (int i = 0;; i++) { + if (i == tuple.getArity()) { + tuples.add(tuple); + break; + } + AlloyAtom a = tuple.getAtoms().get(i); + if (!this.atom2sets.containsKey(a)) + break; // Every atom must exist + if (!model.isEqualOrSubtype(a.getType(), rel.getTypes().get(i))) + break; // Atom must match the type + } + } + if (tuples.size() != 0) + r2t.put(rel, Collections.unmodifiableSet(tuples)); + } + this.rel2tuples = Collections.unmodifiableMap(r2t); + } + + /** Returns an unmodifiable sorted set of all AlloyAtoms in this AlloyInstance. */ + public Set getAllAtoms() { + return Collections.unmodifiableSet(atom2sets.keySet()); + } + + /** + * Returns an unmodifiable sorted list of AlloySet(s) that this atom is in; answer can be an empty + * list. + */ + public List atom2sets(AlloyAtom atom) { + List answer = atom2sets.get(atom); + return answer != null ? answer : noSet; + } + + /** + * Returns an unmodifiable sorted list of AlloyAtom(s) in this type; answer can be an empty list. + */ + public List type2atoms(AlloyType type) { + List answer = type2atoms.get(type); + return answer != null ? answer : noAtom; + } + + /** + * Returns an unmodifiable sorted list of AlloyAtom(s) in this set; answer can be an empty list. + */ + public List set2atoms(AlloySet set) { + List answer = set2atoms.get(set); + return answer != null ? answer : noAtom; + } + + /** + * Returns an unmodifiable sorted set of AlloyTuple(s) in this relation; answer can be an empty + * set. + */ + public Set relation2tuples(AlloyRelation rel) { + Set answer = rel2tuples.get(rel); + return answer != null ? answer : noTuple; + } + + /** + * Two instances are equal if they have the same filename, same commands, same model, and same + * atoms and tuples relationships. + */ + @Override + public boolean equals(Object other) { + if (!(other instanceof AlloyInstance)) + return false; + if (other == this) + return true; + AlloyInstance x = (AlloyInstance) other; + if (!filename.equals(x.filename)) + return false; + if (!commandname.equals(x.commandname)) + return false; + if (!model.equals(x.model)) + return false; + if (!atom2sets.equals(x.atom2sets)) + return false; + if (!type2atoms.equals(x.type2atoms)) + return false; + if (!set2atoms.equals(x.set2atoms)) + return false; + if (!rel2tuples.equals(x.rel2tuples)) + return false; + return true; + } + + /** Computes a hash code based on the same information used in equals(). */ + @Override + public int hashCode() { + int n = 5 * filename.hashCode() + 7 * commandname.hashCode(); + n = n + 7 * atom2sets.hashCode() + 31 * type2atoms.hashCode() + 71 * set2atoms.hashCode() + + 3 * rel2tuples.hashCode(); + return 17 * n + model.hashCode(); + } + + /** Returns a textual dump of the instance. */ + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("Instance's model:\n"); + sb.append(model.toString()); + sb.append("Instance's atom2sets:\n"); + for (Map.Entry> entry : atom2sets.entrySet()) { + sb.append(" "); + sb.append(entry.getKey()); + sb.append(" "); + sb.append(entry.getValue()); + sb.append('\n'); + } + sb.append("Instance's rel2tuples:\n"); + for (Map.Entry> entry : rel2tuples.entrySet()) { + sb.append(" "); + sb.append(entry.getKey()); + sb.append(" "); + sb.append(entry.getValue()); + sb.append('\n'); + } + return sb.toString(); + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4viz/AlloyModel.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4viz/AlloyModel.java new file mode 100644 index 00000000..5e56581d --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4viz/AlloyModel.java @@ -0,0 +1,241 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4viz; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; +import edu.mit.csail.sdg.alloy4.ConstList; +import edu.mit.csail.sdg.alloy4.ConstList.TempList; + +/** Immutable; represents an Alloy model. + * + *

Thread Safety: Can be called only by the AWT event thread. + */ + +public final class AlloyModel { + + /** An unmodifiable sorted set of all types in this model. */ + private final Set types; + + /** An unmodifiable sorted set of all sets in this model. + *
AlloyModel's constructor guarantees that, for each set here, set.getType() is in this.types + */ + private final Set sets; + + /** An unmodifiable sorted set of all relations in this model. + *
AlloyModel's constructor guarantees that, for each relation here, relation.getTypes() are in this.types + */ + private final Set relations; + + /** If A extends B, then "(A,B)" will be in this map. + * + *

AlloyModel's constructor ensures the following: + *
(1) hierachy.keySet() is always a subset of this.types + *
(2) hierachy.valueSet() is always a subset of this.types + *
(3) "univ" is never in the keySet + *
(4) null is never in the keySet nor valueSet + *
(5) there is no cycle in this relation + */ + private final Map hierarchy; + + /** The map from name to AlloyType. + *
AlloyModel's constructor guarantees that this.name2types.values() has the same entries as this.types + */ + private final Map name2types = new HashMap(); + + /** Returns true iff the nodes x, map.get(x), map.get(map.get(x))... form an infinite chain of nonnull objects. + * @param map - a map from AlloyType to AlloyType + * @param x - the AlloyType object we want to check + */ + public static boolean isCycle(Map map, AlloyType x) { + int i=(-5), n=map.size(); + while(x!=null) { x=map.get(x); i++; if (i>=n) return true; } + return false; + } + + /** Construct a new AlloyModel object. + * @param types - the types; we will always add "univ" to it if it's not there already + * @param sets - the sets + * @param rels - the relations + * @param map - we consult this "sig to parent sig" map and extract the mappings relevant to this model. + * (If we detect a cycle, we will arbitrarily break the cycle) + */ + public AlloyModel(Collection types + , Collection sets + , Collection rels + , Map map) { + // The following 3 have to be tree sets, since we want to keep them sorted + Set allTypes = new TreeSet(); + Set allSets = new TreeSet(); + Set allRelations = new TreeSet(); + allTypes.addAll(types); + allTypes.add(AlloyType.UNIV); + for(AlloySet s:sets) if (allTypes.contains(s.getType())) allSets.add(s); + for(AlloyRelation r:rels) if (allTypes.containsAll(r.getTypes())) allRelations.add(r); + this.types=Collections.unmodifiableSet(allTypes); + this.sets=Collections.unmodifiableSet(allSets); + this.relations=Collections.unmodifiableSet(allRelations); + Map newmap=new LinkedHashMap(); + for(AlloyType type:allTypes) { + AlloyType sup = isCycle(map,type) ? null : map.get(type); + if (sup==null || !allTypes.contains(sup)) sup=AlloyType.UNIV; + newmap.put(type,sup); + } + newmap.remove(AlloyType.UNIV); // This ensures univ is not in hierarchy's keySet + this.hierarchy=Collections.unmodifiableMap(newmap); + for(AlloyType t: this.types) this.name2types.put(t.getName(), t); + } + + /** Construct a new AlloyModel object. + * @param types - the types ; we will always add "univ" to it if it's not there already + * @param sets - the sets + * @param rels - the relations + * @param old - we consult this model's "sig to parent sig" map, and extract the mappings relevant to this model. + */ + public AlloyModel(Collection types, + Collection sets, + Collection rels, + AlloyModel old) { + this(types, sets, rels, old.hierarchy); + } + + /** If type==univ, return null; otherwise, return a nonnull AlloyType object representing its super type. + *
(In particular, if "type" does not exist in this model, we'll return "univ" as the answer). + */ + public AlloyType getSuperType(AlloyType type) { + if (type.getName().equals("univ")) return null; + AlloyType answer=hierarchy.get(type); + return answer==null ? AlloyType.UNIV : answer; + } + + /** If type==univ, return null; otherwise, + * return a nonnull AlloyType object representing its topmost non-univ super type. + * + *

Thus, if "type" is in this model, but its supertype is univ, then we'll return type as-is. + *

Note: if "type" does not exist in this model, we'll return it as-is. + */ + public AlloyType getTopmostSuperType(AlloyType type) { + if (type==null || type.equals(AlloyType.UNIV)) return null; + while (true) { + AlloyType top = getSuperType(type); + if (top==null || top.equals(AlloyType.UNIV)) break; + type=top; + } + return type; + } + + /** Returns a sorted, unmodifiable list of types that are direct or indirect subtypes of the given type. + *
This method will search recursively, so if the subtypes themselves have subtypes, they too are included. + *
If type==null, or it does not exist in this model, or it has no subsigs, then we return an empty set. + */ + public ConstList getSubTypes(AlloyType type) { + TempList subtypes = new TempList(); + for (AlloyType subType:types) if (isSubtype(subType,type)) subtypes.add(subType); + return subtypes.makeConst(); // Since this.types is sorted, the result is already sorted. + } + + /** Returns a sorted, unmodifiable list of types that are direct subtypes of the given type. + *
This method will only return types that are direct subtypes of the given argument. + *
If type==null, or it does not exist in this model, or it has no subsigs, then we return an empty set. + */ + public ConstList getDirectSubTypes(AlloyType type) { + TempList subtypes = new TempList(); + for (AlloyType subType: types) if (isDirectSubtype(subType,type)) subtypes.add(subType); + return subtypes.makeConst(); // Since this.types is sorted, the result is already sorted. + } + + /** Returns true iff "subType" is a direct or indirect subsig of "superType". + *
If subType==null or superType==null, it always returns false. + */ + public boolean isSubtype(AlloyType subType, AlloyType superType) { + if (subType==null || superType==null || !types.contains(subType) || subType.equals(AlloyType.UNIV)) + return false; + while(subType!=null) { + subType=getSuperType(subType); // Do this before calling equals(), since we want isSubtype(X,X)==false + if (superType.equals(subType)) return true; + } + return false; + } + + /** Returns true iff "subType" is a direct subsig of "superType". + *
If subType==null or superType==null, it always returns false. + */ + public boolean isDirectSubtype(AlloyType subType, AlloyType superType) { + if (subType==null || superType==null || !types.contains(subType) || subType.equals(AlloyType.UNIV)) + return false; + if (superType.equals(AlloyType.UNIV) && hierarchy.get(subType)==null) return true; + return superType.equals(hierarchy.get(subType)); + } + + /** Returns true iff "subType" is equal to, or is a direct or indirect subsig of "superType". + *
If subType==null or superType==null, it always returns false. + */ + public boolean isEqualOrSubtype(AlloyType subType, AlloyType superType) { + if (superType==null) return false; + while(subType!=null) { + if (superType.equals(subType)) return true; + subType=getSuperType(subType); + } + return false; + } + + /** Two AlloyModel objects are equal if they have the same types, sets, relations, and extension relationship. */ + @Override public boolean equals(Object other) { + if (!(other instanceof AlloyModel)) return false; + if (other==this) return true; + AlloyModel x=(AlloyModel)other; + return types.equals(x.types) && sets.equals(x.sets) + && relations.equals(x.relations) && hierarchy.equals(x.hierarchy); + } + + /** Compute a hashcode based on the types, sets, relations, and the extension relationship. */ + @Override public int hashCode() { + return types.hashCode()+3*sets.hashCode()+5*relations.hashCode()+7*hierarchy.hashCode(); + } + + /** Returns true if this model contains the given type. */ + public boolean hasType(AlloyType type) { return types.contains(type); } + + /** Returns the AlloyType object if this model contains the given type; or return null otherwise. */ + public AlloyType hasType(String name) { return name2types.get(name); } + + /** Returns the AlloyRelation object if this model contains the given relation; or return null otherwise. */ + public AlloySet hasSet(String name, AlloyType type) { + for(AlloySet s:sets) if (s.getName().equals(name) && s.getType().equals(type)) return s; + return null; + } + + /** Returns the AlloyRelation object if this model contains the given relation; or return null otherwise. */ + public AlloyRelation hasRelation(String name, List types) { + for(AlloyRelation r:relations) if (r.getName().equals(name) && r.getTypes().equals(types)) return r; + return null; + } + + /** Returns an unmodifiable sorted set of all AlloyType(s) in this model. */ + public Set getTypes() { return types; } + + /** Returns an unmodifiable sorted set of all AlloySet(s) in this model. */ + public Set getSets() { return sets; } + + /** Returns an unmodifiable sorted set of all AlloyRelation(s) in this model. */ + public Set getRelations() { return relations; } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4viz/AlloyNodeElement.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4viz/AlloyNodeElement.java new file mode 100644 index 00000000..f817815b --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4viz/AlloyNodeElement.java @@ -0,0 +1,27 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4viz; + +/** Immutable; it's the abstract superclass extended by both AlloyType and AlloySet. + * + *

Thread Safety: Can be called only by the AWT event thread. + */ + +public abstract class AlloyNodeElement extends AlloyElement { + + /** Constructs an AlloyNodeElement object with that name. */ + AlloyNodeElement(String name) { super(name); } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4viz/AlloyProjection.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4viz/AlloyProjection.java new file mode 100644 index 00000000..4222a3e3 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4viz/AlloyProjection.java @@ -0,0 +1,95 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4viz; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.TreeMap; + +/** Immutable; represents a set of types to be projected, plus the exact atom chosen for each type to be projected. + * + *

Thread Safety: Can be called only by the AWT event thread. + */ + +public final class AlloyProjection { + + /** Its keySet is the set of types to be projected; each type is associated with the atom chosen to be projected. + * + *

Its keySet is guaranteed to be sorted. + * + *

For each type t in the keyset, map.get(t) is an AlloyAtom + * (indicating the atom in t that we chose to project over). + * + *

Note: there's no way for this class to confirm that a chosen atom is really + * in that type (since the atom may be in a subtype, and thus the atom.type() won't be exactly the same as t). + * Thus, classes that use AlloyProjection objects need to do their own sanity check. + */ + private final Map map; + + /** Constructs a new AlloyProjection object based on the set of types to be projected and the exact atoms chosen. + * @param map - this map describes the set of types to be projected and the exact atoms chosen to be projected + * + *

For each type t in map.keySet(), + *
map.get(t) is an AlloyAtom (indicating the atom in t that we chose to project over). + * + *

Note: there's no way for this class to confirm that a chosen atom is really + * in that type (since the atom may be in a subtype, and thus the atom.type() won't be exactly the same). + * Thus, classes that use AlloyProjection objects need to do their own sanity check. + */ + public AlloyProjection(Map map) { + Map mymap = new TreeMap(); + for(Map.Entry e:map.entrySet()) { + if (e.getKey()!=null && e.getValue()!=null) + mymap.put(e.getKey(), e.getValue()); + } + this.map=Collections.unmodifiableMap(mymap); + } + + /** Constructs an empty AlloyProjection object, with an empty projection list. */ + public AlloyProjection() { + this.map=Collections.unmodifiableMap(new TreeMap()); + } + + /** Return the sorted unmodifiable collection of types we are projecting. */ + public Collection getProjectedTypes() { return map.keySet(); } + + /** Return the atom chosen for that type; + * returns null if that type is not projected. + */ + public AlloyAtom getProjectedAtom(AlloyType type) { return map.get(type); } + + /** Returns a human readable dump of this object. */ + @Override public String toString() { + boolean first=true; + String ans="Projection["; + for(Map.Entry e:map.entrySet()) { + if (first) first=false; else ans=ans+", "; + ans=ans+e.getKey().getName()+":"+e.getValue().getVizName(null,true); + } + return ans+"]"; + } + + /** AlloyProjections are equal if they are projecting over the same types, each type with the same chosen value. */ + @Override public boolean equals(Object other) { + if (!(other instanceof AlloyProjection)) return false; + if (other==this) return true; + return map.equals(((AlloyProjection)other).map); + } + + /** Computes a hashcode based on the types and the atoms chosen for each type. */ + @Override public int hashCode() { return map.hashCode(); } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4viz/AlloyRelation.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4viz/AlloyRelation.java new file mode 100644 index 00000000..05cc88bc --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4viz/AlloyRelation.java @@ -0,0 +1,118 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4viz; + +import java.util.Collection; +import java.util.List; +import edu.mit.csail.sdg.alloy4.ConstList; +import edu.mit.csail.sdg.alloy4.Util; +import edu.mit.csail.sdg.alloy4.ConstList.TempList; + +/** Immutable; represents an Alloy relation of 2 or higher arity. + * + *

Thread Safety: Can be called only by the AWT event thread. + */ + +public final class AlloyRelation extends AlloyElement { + + /** This caches an instance of the "extends" AlloyRelation, so we don't have to keep re-constructing it. */ + public static final AlloyRelation EXTENDS = new AlloyRelation("extends", false, false, Util.asList(AlloyType.UNIV, AlloyType.UNIV)); + + /** This caches an instance of the "in" AlloyRelation, so we don't have to keep re-constructing it. */ + public static final AlloyRelation IN = new AlloyRelation("in", false, false, Util.asList(AlloyType.SET, AlloyType.UNIV)); + + /** The unmodifiable list of types. */ + private final ConstList types; + + /** Records whether this relation is known to be "private"; NOTE: this value is NOT USED during equals() comparison. */ + public final boolean isPrivate; + + /** Records whether this relation is known to be "meta"; NOTE: this value is NOT USED during equals() comparison. */ + public final boolean isMeta; + + /** Constructs a new AlloyRelation with that name and that list of types; types.size() must be 2 or above. */ + public AlloyRelation(String name, boolean isPrivate, boolean isMeta, List types) { + super(name); + if (types==null || types.size()<2) + throw new RuntimeException("An AlloyRelation object must have 2 or more types."); + this.types = ConstList.make(types); + this.isPrivate = isPrivate; + this.isMeta = isMeta; + } + + /** Project this relation and return an unmodifiable list of remaining types (after removing zero or more columns) + * @param columns - the collection of columns to remove (0 is the first column, 1 is the second column...) + */ + public List project(Collection columns) { + TempList list = new TempList(types.size()); + for(int i=0; i getTypes() { return types; } + + /** Returns the arity of the relation. */ + public int getArity() { return types.size(); } + + /** When comparing two AlloyRelation objects, we first compare the name, then the arity, then the types. + *
We guarantee x.equals(y) iff x.compareTo(y)==0 + */ + public int compareTo(AlloyRelation other) { + if (other==null) return 1; + // First compare the names. + int n=Util.slashComparator.compare(getName(), other.getName()); if (n!=0) return n; + // Now compare the arity of the two relations + int arity = types.size(); + if (arity!=other.types.size()) return (arity We guarantee x.equals(y) iff x.compareTo(y)==0 + */ + public int compareTo(AlloyElement other) { + if (!(other instanceof AlloyRelation)) return 1; + return compareTo((AlloyRelation)other); + } + + /** This value is used to display this type in the Visualizer's customization screen. */ + @Override public String toString() { + String answer = ""; + boolean first = true; + for(AlloyType type: getTypes()) { + if (first) {first=false; answer=answer+getName()+" : ";} else answer=answer+" -> "; + answer=answer+type.getName(); + } + return answer; + } + + /** Two relations are equal if they have the same name, and the same list of types. */ + @Override public boolean equals(Object other) { + if (!(other instanceof AlloyRelation)) return false; + if (other==this) return true; + AlloyRelation otherRelation = (AlloyRelation)other; + return getName().equals(otherRelation.getName()) && types.equals(otherRelation.types); + } + + /** Computes a hash code based on the name and the list of types. */ + @Override public int hashCode() { return 5*getName().hashCode() + 7*types.hashCode(); } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4viz/AlloySet.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4viz/AlloySet.java new file mode 100644 index 00000000..70df7ff6 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4viz/AlloySet.java @@ -0,0 +1,87 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4viz; + +import edu.mit.csail.sdg.alloy4.Util; + +/** Immutable; represents an Alloy set in an instance. + * + *

Thread Safety: Can be called only by the AWT event thread. + */ + +public final class AlloySet extends AlloyNodeElement { + + /** The parent type of this AlloySet. */ + private final AlloyType type; + + /** Records whether this relation is known to be "private"; NOTE: this value is NOT USED during equals() comparison. */ + public final boolean isPrivate; + + /** Records whether this relation is known to be "meta"; NOTE: this value is NOT USED during equals() comparison. */ + public final boolean isMeta; + + /** Constructs a new AlloySet object. */ + public AlloySet(String name, boolean isPrivate, boolean isMeta, AlloyType type) { + super(name); this.type=type; this.isPrivate=isPrivate; this.isMeta=isMeta; + } + + /** Returns the parent type of the AlloySet. */ + public AlloyType getType() { return type; } + + /** When comparing two AlloySet objects, we first compare their names, then their types. + *
We guarantee x.equals(y) iff x.compareTo(y)==0 + */ + public int compareTo(AlloySet other) { + if (other==null) return 1; + int n=Util.slashComparator.compare(getName(), other.getName()); + return n!=0 ? n : type.compareTo(other.type); + } + + /** When comparing two AlloySet objects, we first compare their names, then their types. + *
We guarantee x.equals(y) iff x.compareTo(y)==0 + */ + public int compareTo(AlloyNodeElement other) { + if (!(other instanceof AlloySet)) return 1; + AlloySet x=(AlloySet)other; + int n=Util.slashComparator.compare(getName(), x.getName()); + return n!=0 ? n : type.compareTo(x.type); + } + + /** When comparing two AlloySet objects, we first compare their names, then their types. + *
We guarantee x.equals(y) iff x.compareTo(y)==0 + */ + public int compareTo(AlloyElement other) { + if (other instanceof AlloyRelation) return -1; + if (!(other instanceof AlloySet)) return 1; + AlloySet x = (AlloySet)other; + int n=Util.slashComparator.compare(getName(), x.getName()); + return n!=0 ? n : type.compareTo(x.type); + } + + /** This value is used to display this type in the Visualizer's customization screen. */ + @Override public String toString() { return getName()+" : "+getType().getName(); } + + /** Two sets are equal if they have the same name and the same type. */ + @Override public boolean equals(Object other) { + if (!(other instanceof AlloySet)) return false; + if (other==this) return true; + AlloySet otherSet = (AlloySet)other; + return getName().equals(otherSet.getName()) && type.equals(otherSet.type); + } + + /** Compute a hash code based on the name and the type. */ + @Override public int hashCode() { return 5*type.hashCode() + 7*getName().hashCode(); } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4viz/AlloyTuple.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4viz/AlloyTuple.java new file mode 100644 index 00000000..94658f7e --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4viz/AlloyTuple.java @@ -0,0 +1,107 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4viz; + +import java.util.Collection; +import java.util.ArrayList; +import java.util.List; +import edu.mit.csail.sdg.alloy4.ConstList; +import edu.mit.csail.sdg.alloy4.Util; +import edu.mit.csail.sdg.alloy4.ConstList.TempList; + +/** Immutable; represents an Alloy tuple. + * + *

Thread Safety: Can be called only by the AWT event thread. + */ + +public final class AlloyTuple implements Comparable { + public boolean isDashed = false; + /** The unmodifiable list of atoms in this tuple. */ + private final ConstList atoms; + + /** Creates a new AlloyTuple containing the atoms specified by the list; atoms.size() must be 2 or above. */ + public AlloyTuple(List atoms) { + if (atoms==null || atoms.size()<2) + throw new RuntimeException("An AlloyTuple object must have 2 or more atoms."); + this.atoms = ConstList.make(atoms); + } + + /** Creates a new AlloyTuple containing the atoms specified by the list; atoms.size() must be 2 or above. */ + public AlloyTuple(AlloyAtom... atoms) { + if (atoms==null || atoms.length<2) + throw new RuntimeException("An AlloyTuple object must have 2 or more atoms."); + this.atoms = Util.asList(atoms); + } + + /** Project this tuple and return an unmodifiable list of remaining atoms (after removing zero or more columns) + * @param columns - the collection of columns to remove (0 is the first column, 1 is the second column...) + */ + public ConstList project(Collection columns) { + TempList list = new TempList(atoms.size()); + for(int i=0; i getAtoms() { return atoms; } + + /** Returns the first AlloyAtom in this AlloyTuple. */ + public AlloyAtom getStart() { return atoms.get(0); } + + /** Returns the last AlloyAtom in this AlloyTuple. */ + public AlloyAtom getEnd() { return atoms.get(atoms.size()-1); } + + /** Returns a new AlloyTuple whose list of atoms is the same but in reverse. */ + public AlloyTuple reverse() { + List newlist = new ArrayList(atoms.size()); + for(int i=atoms.size()-1; i>=0; i--) newlist.add(atoms.get(i)); + return new AlloyTuple(newlist); + } + + /** Provides a human-readable description of this AlloyTuple. */ + @Override public String toString() { + String s="<"; + for(int i=0; i"; + } + + /** Two tuples are first compared based on length; if the length is the same, we compare atom-by-atom. + *
We guarantee x.equals(y) iff x.compareTo(y)==0 + */ + public int compareTo(AlloyTuple that) { + if (that==null) return 1; + if (atoms.size() < that.atoms.size()) return -1; + if (atoms.size() > that.atoms.size()) return 1; + for(int i=0; iThread Safety: Can be called only by the AWT event thread. + */ + +public final class AlloyType extends AlloyNodeElement { + + /** This caches an instance of the "univ" AlloyType, so we don't have to keep re-constructing it. */ + public static final AlloyType UNIV=new AlloyType("univ", false, false, true, false, false, false); + + /** This caches an instance of the "Int" AlloyType, so we don't have to keep re-constructing it. */ + public static final AlloyType INT=new AlloyType("Int", false, false, true, false, false, false); + + /** This caches an instance of the "seq/Int" AlloyType, so we don't have to keep re-constructing it. */ + public static final AlloyType SEQINT=new AlloyType("seq/Int", false, false, true, false, false, false); + + /** This caches an instance of the "String" AlloyType, so we don't have to keep re-constructing it. */ + public static final AlloyType STRING=new AlloyType("String", false, false, true, false, false, false); + + /** This caches an instance of the "set" AlloyType, so we don't have to keep re-constructing it. */ + public static final AlloyType SET=new AlloyType("set", false, false, false, false, false, false); + + /** Constructs an AlloyType object with that name. */ + public AlloyType(String name, boolean isOne, boolean isAbstract, boolean isBuiltin, boolean isPrivate, boolean isMeta, boolean isEnum) { + super(name); + this.isOne = isOne; + this.isAbstract = isAbstract; + this.isBuiltin = isBuiltin; + this.isPrivate = isPrivate; + this.isMeta = isMeta; + this.isEnum = isEnum; + } + + /** Records whether this sig is known to be "one"; NOTE: this value is NOT USED during equals() comparison. */ + public final boolean isOne; + + /** Records whether this sig is known to be "abstract"; NOTE: this value is NOT USED during equals() comparison. */ + public final boolean isAbstract; + + /** Records whether this sig is known to be "builtin"; NOTE: this value is NOT USED during equals() comparison. */ + public final boolean isBuiltin; + + /** Records whether this sig is known to be "private"; NOTE: this value is NOT USED during equals() comparison. */ + public final boolean isPrivate; + + /** Records whether this sig is known to be "meta"; NOTE: this value is NOT USED during equals() comparison. */ + public final boolean isMeta; + + /** Records whether this sig is known to be "enum"; NOTE: this value is NOT USED during equals() comparison. */ + public final boolean isEnum; + + /** When comparing two AlloyType objects, we compare their names. + *
We guarantee x.equals(y) iff x.compareTo(y)==0 + */ + public int compareTo(AlloyType other) { + if (other==null) return 1; + return Util.slashComparator.compare(getName(), other.getName()); + } + + /** When comparing two AlloyType objects, we compare their names. + *
We guarantee x.equals(y) iff x.compareTo(y)==0 + */ + public int compareTo(AlloyNodeElement other) { + if (other==null) return 1; + if (!(other instanceof AlloyType)) return -1; + return Util.slashComparator.compare(getName(), ((AlloyType)other).getName()); + } + + /** When comparing two AlloyType objects, we compare their names. + *
We guarantee x.equals(y) iff x.compareTo(y)==0 + */ + public int compareTo(AlloyElement other) { + if (other==null) return 1; + if (!(other instanceof AlloyType)) return -1; + return Util.slashComparator.compare(getName(), ((AlloyType)other).getName()); + } + + /** This value is used to display this type in the Visualizer's customization screen. */ + @Override public String toString() { return getName(); } + + /** Two types are equal if they have the same name. */ + @Override public boolean equals(Object other) { + if (!(other instanceof AlloyType)) return false; + if (other==this) return true; + return getName().equals(((AlloyType)other).getName()); + } + + /** Compute a hash code based on the name. */ + @Override public int hashCode() { return getName().hashCode(); } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4viz/MagicColor.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4viz/MagicColor.java new file mode 100644 index 00000000..afb6cfd6 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4viz/MagicColor.java @@ -0,0 +1,238 @@ +/* + * Alloy Analyzer 4 -- Copyright (c) 2007-2008, Derek Rayside + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4viz; + +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import edu.mit.csail.sdg.alloy4.ConstList; +import edu.mit.csail.sdg.alloy4.Util; +import edu.mit.csail.sdg.alloy4.ConstList.TempList; +import edu.mit.csail.sdg.alloy4graph.DotColor; +import edu.mit.csail.sdg.alloy4graph.DotPalette; +import edu.mit.csail.sdg.alloy4graph.DotShape; +import static edu.mit.csail.sdg.alloy4graph.DotShape.BOX; +import static edu.mit.csail.sdg.alloy4graph.DotShape.DIAMOND; +import static edu.mit.csail.sdg.alloy4graph.DotShape.TRAPEZOID; +import static edu.mit.csail.sdg.alloy4graph.DotShape.HOUSE; +import static edu.mit.csail.sdg.alloy4graph.DotShape.ELLIPSE; +import static edu.mit.csail.sdg.alloy4graph.DotShape.EGG; +import static edu.mit.csail.sdg.alloy4graph.DotShape.HEXAGON; +import static edu.mit.csail.sdg.alloy4graph.DotShape.OCTAGON; +import static edu.mit.csail.sdg.alloy4graph.DotShape.INV_HOUSE; +import static edu.mit.csail.sdg.alloy4graph.DotShape.INV_TRAPEZOID; +import static edu.mit.csail.sdg.alloy4graph.DotShape.INV_TRIANGLE; +import static edu.mit.csail.sdg.alloy4graph.DotShape.DOUBLE_OCTAGON; +import static edu.mit.csail.sdg.alloy4graph.DotShape.TRIPLE_OCTAGON; +import static edu.mit.csail.sdg.alloy4graph.DotShape.M_CIRCLE; +import static edu.mit.csail.sdg.alloy4graph.DotShape.M_DIAMOND; +import static edu.mit.csail.sdg.alloy4graph.DotShape.M_SQUARE; +import static edu.mit.csail.sdg.alloy4graph.DotShape.PARALLELOGRAM; +import edu.mit.csail.sdg.alloy4graph.DotStyle; + +/** + * This class implements the automatic visualization inference. + * + *

+ * Thread Safety: Can be called only by the AWT event thread. + */ + +public final class MagicColor { + + /** The VizState object that we're going to configure. */ + private final VizState vizState; + + /** Constructor. */ + private MagicColor(final VizState vizState) { + this.vizState = vizState; + } + + /** Main method to infer settings. */ + public static void magic(final VizState vizState) { + vizState.setNodePalette(DotPalette.MARTHA); + final MagicColor st = new MagicColor(vizState); + st.nodeNames(); + st.nodeShape(); + st.nodeColour(); + st.skolemColour(); + } + + + /** + * SYNTACTIC/VISUAL: Determine colours for nodes. + * + * when do we color things and what is the meaning of color + *

    + *
  • symmetry breaking: colors only matter up to recoloring (diff from shape!) + *
  • color substitutes for name/label + *
+ */ + private void nodeColour() { + final Set visibleUserTypes = MagicUtil.visibleUserTypes(vizState); + final Set uniqueColourTypes; + if (visibleUserTypes.size() <= 5) { + // can give every visible user type its own shape + uniqueColourTypes = visibleUserTypes; + } else { + // give every top-level visible user type its own shape + uniqueColourTypes = MagicUtil.partiallyVisibleUserTopLevelTypes(vizState); + } + int index = 0; + for (final AlloyType t : uniqueColourTypes) { + vizState.nodeColor.put(t, (DotColor) DotColor.valuesWithout(DotColor.MAGIC)[index]); + index = (index + 1) % DotColor.valuesWithout(DotColor.MAGIC).length; + } + } + + /** SYNTACTIC/VISUAL: Determine colour highlighting for skolem constants. */ + private void skolemColour() { + final Set sets = vizState.getCurrentModel().getSets(); + for (final AlloySet s : sets) { + // change the style + vizState.nodeStyle.put(s, DotStyle.BOLD); + // change the label + String label = vizState.label.get(s); + final int lastUnderscore = label.lastIndexOf('_'); + if (lastUnderscore >= 0) { + label = label.substring(lastUnderscore + 1); + } + vizState.label.put(s, label); + } + } + + /** The list of shape families. */ + private static final List> families; + + static { + TempList> list = new TempList>(); + list.add(Util.asList(BOX, TRAPEZOID, HOUSE)); + list.add(Util.asList(ELLIPSE, EGG)); + list.add(Util.asList(HEXAGON, OCTAGON, DOUBLE_OCTAGON, TRIPLE_OCTAGON)); + list.add(Util.asList(INV_TRIANGLE, INV_HOUSE, INV_TRAPEZOID)); + list.add(Util.asList(M_DIAMOND, M_SQUARE, M_CIRCLE)); + list.add(Util.asList(PARALLELOGRAM, DIAMOND)); + families = list.makeConst(); + } + + + /** + * SYNTACTIC/VISUAL: Determine shapes for nodes. + *
    + *
  • trapezoid, hexagon, rectangle, ellipse, circle, square -- no others + *
  • actual shape matters -- do not break symmetry as with color + *
  • ellipse by default + *
  • circle if special extension of ellipse + *
  • rectangle if lots of attributes + *
  • square if special extension of rectangle + *
  • when to use other shapes? + * + *
+ */ + private void nodeShape() { + final Set> usedShapeFamilies = new LinkedHashSet>(); + final Set topLevelTypes = MagicUtil.partiallyVisibleUserTopLevelTypes(vizState); + + for (final AlloyType t : topLevelTypes) { + + // get the type family + final Set subTypes = MagicUtil.visibleSubTypes(vizState, t); + final boolean isTvisible = MagicUtil.isActuallyVisible(vizState, t); + final int size = subTypes.size() + (isTvisible ? 1 : 0); + // log("TopLevelType: " + t + " -- " + subTypes + " " + size); + + // match it to a shape family + // 1. look for exact match + boolean foundExactMatch = false; + for (final List shapeFamily : families) { + if (size == shapeFamily.size() && !usedShapeFamilies.contains(shapeFamily)) { + // found a match! + usedShapeFamilies.add(shapeFamily); + assignNodeShape(t, subTypes, isTvisible, shapeFamily); + foundExactMatch = true; + break; + } + } + if (foundExactMatch) + continue; + // 2. look for approximate match + List approxShapeFamily = null; + int approxShapeFamilyDistance = Integer.MAX_VALUE; + for (final List shapeFamily : families) { + if (size <= shapeFamily.size() && !usedShapeFamilies.contains(shapeFamily)) { + // found a potential match + final int distance = shapeFamily.size() - size; + if (distance < approxShapeFamilyDistance) { + // it's a closer fit than the last match, keep it for now + approxShapeFamily = shapeFamily; + approxShapeFamilyDistance = distance; + } + } + } + if (approxShapeFamily != null) { + // use the best approximate match that we just found + usedShapeFamilies.add(approxShapeFamily); + assignNodeShape(t, subTypes, isTvisible, approxShapeFamily); + } + // 3. re-use a shape family matched to something else -- just give up for now + } + } + + + /** Helper for nodeShape(). */ + private void assignNodeShape(final AlloyType t, final Set subTypes, + final boolean isTvisible, final List shapeFamily) { + int index = 0; + // shape for t, if visible + if (isTvisible) { + final DotShape shape = shapeFamily.get(index++); + // log("AssignNodeShape " + t + " " + shape); + vizState.shape.put(t, shape); + } + // shapes for visible subtypes + for (final AlloyType subt : subTypes) { + final DotShape shape = shapeFamily.get(index++); + // log("AssignNodeShape " + subt + " " + shape); + vizState.shape.put(subt, shape); + } + } + + /** + * SYNTACTIC/VISUAL: Should the names of nodes be displayed on them? + * + * when should names be used? + *
    + *
  • not when only a single sig (e.g. state machine with only one 'node' sig) + *
  • not when only a single relation + *
  • check for single things _after_ hiding things by default + *
+ */ + private void nodeNames() { + final Set visibleUserTypes = MagicUtil.visibleUserTypes(vizState); + // trim names + for (final AlloyType t : visibleUserTypes) { + // trim label before last slash + MagicUtil.trimLabelBeforeLastSlash(vizState, t); + } + // hide names if there's only one node type visible + if (1 == visibleUserTypes.size()) { + vizState.label.put(visibleUserTypes.iterator().next(), ""); + } + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4viz/MagicLayout.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4viz/MagicLayout.java new file mode 100644 index 00000000..5815636a --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4viz/MagicLayout.java @@ -0,0 +1,302 @@ +/* + * Alloy Analyzer 4 -- Copyright (c) 2007-2008, Derek Rayside + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4viz; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import edu.mit.csail.sdg.alloy4.ConstList; +import edu.mit.csail.sdg.alloy4.Util; +import edu.mit.csail.sdg.alloy4graph.DotColor; + +/** + * This class implements the automatic visualization inference. + * + *

+ * Thread Safety: Can be called only by the AWT event thread. + */ + +public final class MagicLayout { + + /** The VizState object that we're going to configure. */ + private final VizState vizState; + + private Set enumerationTypes = new LinkedHashSet(); + private Set singletonTypes = new LinkedHashSet(); + private AlloyType projectionType = null; + private Set spineRelations = Collections.emptySet(); + + /** Constructor. */ + private MagicLayout(final VizState vizState) { + this.vizState = vizState; + } + + /** Main method to infer settings. */ + public static void magic(final VizState vizState) { + vizState.resetTheme(); + final MagicLayout st = new MagicLayout(vizState); + st.identifyEnumerationTypes(); + st.projection(); + st.nodeVisibility(); + st.spine(); + st.attributes(); + st.edgeLabels(); + } + + /** + * SYNTACTIC: An enumeration follows the pattern "abstract sig Colour; one sig Red; one sig Blue". + */ + private void identifyEnumerationTypes() { + final AlloyModel model = vizState.getCurrentModel(); + final Set types = model.getTypes(); + for (final AlloyType t : types) { + if (enumerationTypes.contains(t)) + continue; // we've already checked this one, don't muck with it now + if (t.isOne) + singletonTypes.add(t); + if (!t.isBuiltin && t.isAbstract) { + List subTypes = model.getSubTypes(t); + int numberOfSingletonSubtypes = 0; + for (AlloyType st : subTypes) { + if (st.isOne) { + numberOfSingletonSubtypes++; + singletonTypes.add(st); + } + } + if (subTypes.size() == numberOfSingletonSubtypes) { // we have a winner! + enumerationTypes.add(t); + enumerationTypes.addAll(subTypes); + for (final AlloyType st : subTypes) { + // all of the subtypes in the enumeration should have visibility inherited + // so that the user only needs to make the abstract supertype visible if we made a + // mistake hiding these things + vizState.nodeVisible.put(st, null); + } + // hide unless these are the source of some relation + boolean visible = false; + for (AlloyRelation r : model.getRelations()) { + AlloyType sourceType = r.getTypes().get(0); + if (t.equals(sourceType) || subTypes.contains(sourceType)) { + visible = true; + break; + } + } + vizState.nodeVisible.put(t, visible); // log("VizInference: visible status of enumeration + // type " + t + " " + visible); + } + } + } + + } + + /** + * SEMANTIC/LAYOUT: Determine at most one relation to project over. + * + * When do we project over a sig? Do we ever project over more than one? + *

    + *
  • pick 0 or 1 things to project over + *
  • match names: Time, State, Tick, TimeStep + *
  • if ord is opened over the sig + *
  • if present in several ternary relations (i.e. if it will help viz high arity relations) + *
  • position in relation (always end or always beginning) + *
  • should we try to catch projections such as the one over birthday books? + *
  • pattern match (b,b') to catch transition relations + *
  • add combo box in GUI (?) + *
+ */ + private void projection() { + // only fiddle with this if it hasn't already been set somewhere else + if (projectionType == null && vizState.getProjectedTypes().isEmpty()) { + AlloyModel model = vizState.getCurrentModel(); + // final Set candidateTypes = new HashSet(); + Map scores = new LinkedHashMap(); + for (AlloyType t : model.getTypes()) { + scores.put(t, 0); + // does it have a name like State, Time, etc + if (hasLikelyProjectionTypeName(t.getName())) { + scores.put(t, scores.get(t) + 1); + } + // is it in some ternary relation? + for (AlloyRelation r : model.getRelations()) { + if (r.getArity() > 2 && r.getTypes().contains(t)) + scores.put(t, scores.get(t) + 1); + } + } + // now we have the scores, see who the winners are: + int max = 0; + final Set winners = new LinkedHashSet(); + for (final Map.Entry e : scores.entrySet()) { + if (e.getValue() == max) + winners.add(e.getKey()); + if (e.getValue() > max) { + max = e.getValue(); + winners.clear(); + winners.add(e.getKey()); + } + } + if (max < 2) { + // no winner, don't project + // log("VizInference: no candidate type to project on."); + } else { + if (winners.size() > 1) { + // we have a tie ... what to do? + // log("VizInference: projection tie. " + winners); + } + final AlloyType winner = winners.iterator().next(); + // pick one arbitrarily for now ... + // log("VizInference: projecting on " + max + " " + winner); + projectionType = winner; + vizState.project(projectionType); + } + + } + } + + private final static ConstList LIKELY_PROJECTION_TYPE_NAMES = + Util.asList("State", "TrainState", "Time", "Tick", "TimeStep"); + + private final boolean hasLikelyProjectionTypeName(final String n) { + for (String s : LIKELY_PROJECTION_TYPE_NAMES) + if (n.startsWith(s) || n.endsWith(s)) + return true; + return false; + } + + /** + * SEMANTIC/LAYOUT: Determine some relations to be the spine (ie, influence the layout). + * + * Which relations should be used to layout? all? none? clever? + *
    + *
  • interesting example: 2d game grid + *
  • ex: toplogical sort -- layout tree and list, not cnxn between them + *
  • look for homogenius binary relation (a -> a) + *
  • may be several relations defining the spine + *
+ * + */ + private void spine() { + AlloyModel model = vizState.getCurrentModel(); + Set relations = model.getRelations(); + if (!relations.isEmpty()) { + // only mess with the relations if there are some + // only binary relations are candidates + Set spines = new LinkedHashSet(); + for (AlloyRelation r : relations) { + if (r.getArity() == 2) { + List rtypes = r.getTypes(); + AlloyType targetType = rtypes.get(1); + // only a spine if the target is not an enumeration type + if (!enumerationTypes.contains(targetType)) { + spines.add(r); + } + // however, binary relations named parent should be layed out backwards + if (r.getName().equals("parent")) { + vizState.layoutBack.put(r, true); + } + } + } + // do we have any spines? if so, use them, if not use all relations + spineRelations = spines.isEmpty() ? relations : spines; + } + // set everything to not influence layout + for (AlloyRelation r : relations) { + vizState.constraint.put(r, false); + vizState.edgeColor.put(r, DotColor.GRAY); + } + // set spines to influence layout + for (AlloyRelation s : spineRelations) { + vizState.constraint.put(s, null); + // inherit the default color, which should be black + vizState.edgeColor.put(s, null); + } + + } + + /** + * SEMANTIC/LAYOUT: Determine whether non-projection, non-spine relations should be shown as + * attributes or edges. + * + *
    + *
  • binary vs. higher arity -- only make binary attributes + *
  • use attributes on-demand to reduce clutter, not blindly + *
  • functional relations should be attributes (what about a tree?) + *
  • never make something an edge and an attribute + * + *
+ * + */ + private void attributes() { + AlloyModel model = vizState.getCurrentModel(); + for (AlloyRelation r : model.getRelations()) { + List rTypes = r.getTypes(); + if (r.getArity() == 2 && !rTypes.contains(projectionType) && !spineRelations.contains(r)) { + // it's binary, non-projection and non-spine + AlloyType targetType = rTypes.get(1); + if (enumerationTypes.contains(targetType)) { + // target is an enumeration: we have an attribute + vizState.attribute.put(r, true); + vizState.edgeVisible.put(r, false); + } + } + } + } + + + /** PRESENTATIONAL: Labels for edges. */ + private void edgeLabels() { + AlloyModel model = vizState.getCurrentModel(); + int relationsAsEdges = 0; + AlloyRelation visibleRelation = null; + for (AlloyRelation r : model.getRelations()) { + Boolean v = vizState.edgeVisible.get(r); + if (v == null || v.booleanValue()) { + // it's visible + relationsAsEdges++; + visibleRelation = r; + // remove text before last slash + MagicUtil.trimLabelBeforeLastSlash(vizState, r); + } + } + // If there's only one relation visible as an edge, and it's binary, then no need to label it. + if (1 == relationsAsEdges && visibleRelation.getArity() == 2) { + vizState.label.put(visibleRelation, ""); + } + } + + + /** SYNTACTIC/VISUAL: Hide some things. */ + private void nodeVisibility() { + AlloyModel model = vizState.getCurrentModel(); + Set types = model.getTypes(); + for (AlloyType t : types) + if (!t.isBuiltin && MagicUtil.isActuallyVisible(vizState, t) + && t.getName().endsWith("/Ord")) { + vizState.nodeVisible.put(t, false); + } + for (AlloySet s : model.getSets()) + if (MagicUtil.isActuallyVisible(vizState, s) && s.getName().endsWith("/Ord")) { + vizState.nodeVisible.put(s, false); + } + + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4viz/MagicUtil.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4viz/MagicUtil.java new file mode 100644 index 00000000..61f13bcd --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4viz/MagicUtil.java @@ -0,0 +1,148 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2007-2008, Derek Rayside + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4viz; + +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +/** This class implements the automatic visualization inference. + * + *

Thread Safety: Can be called only by the AWT event thread. + */ + +final class MagicUtil { + + /** Constructor. + */ + private MagicUtil() {} + + static void trimLabelBeforeLastSlash(final VizState vizState, final AlloyElement x) { + vizState.label.put(x, trimBeforeLastSlash(vizState.label.get(x))); + } + + static String trimBeforeLastSlash(final String label) { + final int lastSlash = label.lastIndexOf('/'); + if (lastSlash >= 0) { + return label.substring(lastSlash+1); + } else { + return label; + } + } + + + /** Determines whether a type is actually visible -- ie, if it has an inherited value, + * looks up the hierarchy until that is resolved. NB: abstract types are not actually visible. + * @param t + * @return true if this type will be shown to the user, false if this type will be hidden from the user + */ + static boolean isActuallyVisible(final VizState vizState, final AlloyType t) { + if (t.isAbstract) return false; + final Boolean V = vizState.nodeVisible.get(t); + if (V != null) return V; + + // inherited value, find out the real deal + final AlloyModel model = vizState.getCurrentModel(); + AlloyType parent = model.getSuperType(t); + while (parent != null) { + final Boolean pV = vizState.nodeVisible.get(parent); + if (pV != null) break; // found a real setting + parent = model.getSuperType(parent); + } + if (parent == null) { + // made it to univ without finding a real setting + return true; + } else { + // found a concrete setting, use it + return vizState.nodeVisible.get(parent); + } + } + + static boolean isActuallyVisible(final VizState vizState, final AlloySet s) { + final Boolean V = vizState.nodeVisible.get(s); + if (V != null) return V; + + return isActuallyVisible(vizState, s.getType()); + } + + /** Returns all of the visible user-types in the current model. + * @param vizState + */ + static Set visibleUserTypes(final VizState vizState) { + final Set result = new LinkedHashSet(); + final AlloyModel model = vizState.getCurrentModel(); + for (final AlloyType t : model.getTypes()) { + if (!t.isBuiltin && MagicUtil.isActuallyVisible(vizState, t)) { + result.add(t); + } + } + return Collections.unmodifiableSet(result); + } + + /** Returns all of the top-level types in the original model. + * @param vizState + */ + static Set topLevelTypes(final VizState vizState) { + final Set result = new LinkedHashSet(); + final AlloyModel model = vizState.getOriginalModel(); + for (final AlloyType t : model.getTypes()) { + if (vizState.isTopLevel(t)) { + result.add(t); + } + } + return Collections.unmodifiableSet(result); + } + + /** Returns every top-level user type that is itself visible or has a visible subtype. + * @param vizState + */ + static Set partiallyVisibleUserTopLevelTypes(final VizState vizState) { + final AlloyModel model = vizState.getOriginalModel(); + final Set visibleUserTypes = visibleUserTypes(vizState); + //final Set topLevelTypes = topLevelTypes(vizState); + + final Set result = new LinkedHashSet(); + + for (final AlloyType t : visibleUserTypes) { + if (visibleUserTypes.contains(t)) { + result.add(model.getTopmostSuperType(t)); + } + } + + return Collections.unmodifiableSet(result); + } + + /** Returns the set of visible subtypes for the given type. + * @param vizState + * @param type + */ + static Set visibleSubTypes(final VizState vizState, final AlloyType type) { + final AlloyModel model = vizState.getCurrentModel(); + final List subTypes = model.getSubTypes(type); + final Set visibleUserTypes = visibleUserTypes(vizState); + final Set result = new LinkedHashSet(); + + for (final AlloyType st : subTypes) { + if (visibleUserTypes.contains(st)) { + result.add(st); + } + } + + return Collections.unmodifiableSet(result); + } + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4viz/StaticGraphMaker.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4viz/StaticGraphMaker.java new file mode 100644 index 00000000..02ff4395 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4viz/StaticGraphMaker.java @@ -0,0 +1,361 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4viz; + +import java.awt.Color; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +import java.util.TreeSet; +import javax.swing.JPanel; +import edu.mit.csail.sdg.alloy4.ErrorFatal; +import edu.mit.csail.sdg.alloy4.Util; +import edu.mit.csail.sdg.alloy4graph.DotColor; +import edu.mit.csail.sdg.alloy4graph.DotDirection; +import edu.mit.csail.sdg.alloy4graph.DotPalette; +import edu.mit.csail.sdg.alloy4graph.DotShape; +import edu.mit.csail.sdg.alloy4graph.DotStyle; +import edu.mit.csail.sdg.alloy4graph.GraphEdge; +import edu.mit.csail.sdg.alloy4graph.Graph; +import edu.mit.csail.sdg.alloy4graph.GraphNode; +import edu.mit.csail.sdg.alloy4graph.GraphViewer; + +/** This utility class generates a graph for a particular index of the projection. + * + *

Thread Safety: Can be called only by the AWT event thread. + */ + +public final class StaticGraphMaker { + + /** The theme customization. */ + private final VizState view; + + /** The projected instance for the graph currently being generated. */ + private final AlloyInstance instance; + + /** The projected model for the graph currently being generated. */ + private final AlloyModel model; + + /** The map that contains all edges and what the AlloyTuple that each edge corresponds to. */ + private final Map edges = new LinkedHashMap(); + + /** The map that contains all nodes and what the AlloyAtom that each node corresponds to. */ + private final Map nodes = new LinkedHashMap(); + + /** This maps each atom to the node representing it; if an atom doesn't have a node, it won't be in the map. */ + private final Map atom2node = new LinkedHashMap(); + + /** This stores a set of additional labels we want to add to an existing node. */ + private final Map> attribs = new LinkedHashMap>(); + + /** The resulting graph. */ + private final Graph graph; + + /** Produces a single Graph from the given Instance and View and choice of Projection */ + public static JPanel produceGraph(AlloyInstance instance, VizState view, AlloyProjection proj) throws ErrorFatal { + view = new VizState(view); + if (proj == null) proj = new AlloyProjection(); + Graph graph = new Graph(view.getFontSize() / 12.0D); + new StaticGraphMaker(graph, instance, view, proj); + if (graph.nodes.size()==0) new GraphNode(graph, "", "Due to your theme settings, every atom is hidden.", "Please click Theme and adjust your settings."); + return new GraphViewer(graph); + } + + /** The list of colors, in order, to assign each legend. */ + private static final List colorsClassic = Util.asList( + new Color(228,26,28) + ,new Color(166,86,40) + ,new Color(255,127,0) + ,new Color(77,175,74) + ,new Color(55,126,184) + ,new Color(152,78,163) + ); + + /** The list of colors, in order, to assign each legend. */ + private static final List colorsStandard = Util.asList( + new Color(227,26,28) + ,new Color(255,127,0) + ,new Color(251*8/10,154*8/10,153*8/10) + ,new Color(51,160,44) + ,new Color(31,120,180) + ); + + /** The list of colors, in order, to assign each legend. */ + private static final List colorsMartha = Util.asList( + new Color(231,138,195) + ,new Color(252,141,98) + ,new Color(166,216,84) + ,new Color(102,194,165) + ,new Color(141,160,203) + ); + + /** The list of colors, in order, to assign each legend. */ + private static final List colorsNeon = Util.asList( + new Color(231,41,138) + ,new Color(217,95,2) + ,new Color(166,118,29) + ,new Color(102,166,30) + ,new Color(27,158,119) + ,new Color(117,112,179) + ); + + /** The constructor takes an Instance and a View, then insert the generate graph(s) into a blank cartoon. */ + private StaticGraphMaker (Graph graph, AlloyInstance originalInstance, VizState view, AlloyProjection proj) throws ErrorFatal { + final boolean hidePrivate = view.hidePrivate(); + final boolean hideMeta = view.hideMeta(); + final Map magicColor = new TreeMap(); + final Map rels = new TreeMap(); + this.graph = graph; + this.view = view; + instance = StaticProjector.project(originalInstance, proj); + model = instance.model; + for (AlloyRelation rel: model.getRelations()) { + rels.put(rel, null); + } + List colors; + if (view.getEdgePalette() == DotPalette.CLASSIC) colors = colorsClassic; + else if (view.getEdgePalette() == DotPalette.STANDARD) colors = colorsStandard; + else if (view.getEdgePalette() == DotPalette.MARTHA) colors = colorsMartha; + else colors = colorsNeon; + int ci = 0; + for (AlloyRelation rel: model.getRelations()) { + DotColor c = view.edgeColor.resolve(rel); + Color cc = (c==DotColor.MAGIC) ? colors.get(ci) : c.getColor(view.getEdgePalette()); + int count = ((hidePrivate && rel.isPrivate) || !view.edgeVisible.resolve(rel)) ? 0 : edgesAsArcs(hidePrivate, hideMeta, rel, colors.get(ci)); + rels.put(rel, count); + magicColor.put(rel, cc); + if (count>0) ci = (ci+1)%(colors.size()); + } + for (AlloyAtom atom: instance.getAllAtoms()) { + List sets = instance.atom2sets(atom); + if (sets.size()>0) { + for (AlloySet s: sets) + if (view.nodeVisible.resolve(s) && !view.hideUnconnected.resolve(s)) + {createNode(hidePrivate, hideMeta, atom); break;} + } else if (view.nodeVisible.resolve(atom.getType()) && !view.hideUnconnected.resolve(atom.getType())) { + createNode(hidePrivate, hideMeta, atom); + } + } + for (AlloyRelation rel: model.getRelations()) + if (!(hidePrivate && rel.isPrivate)) + if (view.attribute.resolve(rel)) + edgesAsAttribute(rel); + for(Map.Entry> e: attribs.entrySet()) { + Set set = e.getValue(); + if (set!=null) for(String s: set) if (s.length() > 0) e.getKey().addLabel(s); + } + for(Map.Entry e: rels.entrySet()) { + Color c = magicColor.get(e.getKey()); + if (c==null) c = Color.BLACK; + int n = e.getValue(); + if (n>0) graph.addLegend(e.getKey(), e.getKey().getName()+": "+n, c); else graph.addLegend(e.getKey(), e.getKey().getName(), null); + } + } + + /** Return the node for a specific AlloyAtom (create it if it doesn't exist yet). + * @return null if the atom is explicitly marked as "Don't Show". + */ + private GraphNode createNode(final boolean hidePrivate, final boolean hideMeta, final AlloyAtom atom) { + GraphNode node = atom2node.get(atom); + if (node!=null) return node; + if ( (hidePrivate && atom.getType().isPrivate) + || (hideMeta && atom.getType().isMeta) + || !view.nodeVisible(atom, instance)) return null; + // Make the node + DotColor color = view.nodeColor(atom, instance); + DotStyle style = view.nodeStyle(atom, instance); + if (atom.isDashed) { + style = DotStyle.DASHED; + } + DotShape shape = view.shape(atom, instance); + String label = atomname(atom, false); + node = new GraphNode(graph, atom, label).set(shape).set(color.getColor(view.getNodePalette())).set(style); + // Get the label based on the sets and relations + String setsLabel=""; + boolean showLabelByDefault = view.showAsLabel.get(null); + for (AlloySet set: instance.atom2sets(atom)) { + String x = view.label.get(set); if (x.length()==0) continue; + Boolean showLabel = view.showAsLabel.get(set); + if ((showLabel==null && showLabelByDefault) || (showLabel!=null && showLabel.booleanValue())) + setsLabel += ((setsLabel.length()>0?", ":"")+x); + } + if (setsLabel.length()>0) { + Set list = attribs.get(node); + if (list==null) attribs.put(node, list=new TreeSet()); + list.add("("+setsLabel+")"); + } + nodes.put(node,atom); + atom2node.put(atom,node); + return node; + } + + /** Create an edge for a given tuple from a relation (if neither start nor end node is explicitly invisible) */ + private boolean createEdge(final boolean hidePrivate, final boolean hideMeta, AlloyRelation rel, AlloyTuple tuple, boolean bidirectional, Color magicColor) { + // This edge represents a given tuple from a given relation. + // + // If the tuple's arity==2, then the label is simply the label of the relation. + // + // If the tuple's arity>2, then we append the node labels for all the intermediate nodes. + // eg. Say a given tuple is (A,B,C,D) from the relation R. + // An edge will be drawn from A to D, with the label "R [B, C]" + if ((hidePrivate && tuple.getStart().getType().isPrivate) + ||(hideMeta && tuple.getStart().getType().isMeta) + || !view.nodeVisible(tuple.getStart(), instance)) return false; + if ((hidePrivate && tuple.getEnd().getType().isPrivate) + ||(hideMeta && tuple.getEnd().getType().isMeta) + || !view.nodeVisible(tuple.getEnd(), instance)) return false; + GraphNode start = createNode(hidePrivate, hideMeta, tuple.getStart()); + GraphNode end = createNode(hidePrivate, hideMeta, tuple.getEnd()); + if (start==null || end==null) return false; + boolean layoutBack = view.layoutBack.resolve(rel); + String label = view.label.get(rel); + if (tuple.getArity() > 2) { + StringBuilder moreLabel = new StringBuilder(); + List atoms=tuple.getAtoms(); + for (int i=1; i1) moreLabel.append(", "); + moreLabel.append(atomname(atoms.get(i),false)); + } + if (label.length()==0) { /* label=moreLabel.toString(); */ } + else { label=label+(" ["+moreLabel+"]"); } + } + DotDirection dir = bidirectional ? DotDirection.BOTH : (layoutBack ? DotDirection.BACK : DotDirection.FORWARD); + DotStyle style = view.edgeStyle.resolve(rel); + if (tuple.isDashed) { + style = DotStyle.DASHED; + } + DotColor color = view.edgeColor.resolve(rel); + int weight = view.weight.get(rel); + GraphEdge e = new GraphEdge((layoutBack ? end : start), (layoutBack ? start : end), tuple, label, rel); + if (color == DotColor.MAGIC && magicColor != null) e.set(magicColor); else e.set(color.getColor(view.getEdgePalette())); + e.set(style); + e.set(dir!=DotDirection.FORWARD, dir!=DotDirection.BACK); + e.set(weight<1 ? 1 : (weight>100 ? 10000 : 100*weight)); + edges.put(e, tuple); + return true; + } + + /** Create edges for every visible tuple in the given relation. */ + private int edgesAsArcs(final boolean hidePrivate, final boolean hideMeta, AlloyRelation rel, Color magicColor) { + int count = 0; + if (!view.mergeArrows.resolve(rel)) { + // If we're not merging bidirectional arrows, simply create an edge for each tuple. + for (AlloyTuple tuple: instance.relation2tuples(rel)) if (createEdge(hidePrivate, hideMeta, rel, tuple, false, magicColor)) count++; + return count; + } + // Otherwise, find bidirectional arrows and only create one edge for each pair. + Set tuples = instance.relation2tuples(rel); + Set ignore = new LinkedHashSet(); + for (AlloyTuple tuple: tuples) { + if (!ignore.contains(tuple)) { + AlloyTuple reverse = tuple.getArity()>2 ? null : tuple.reverse(); + // If the reverse tuple is in the same relation, and it is not a self-edge, then draw it as a <-> arrow. + if (reverse!=null && tuples.contains(reverse) && !reverse.equals(tuple)) { + ignore.add(reverse); + if (createEdge(hidePrivate, hideMeta, rel, tuple, true, magicColor)) count = count + 2; + } else { + if (createEdge(hidePrivate, hideMeta, rel, tuple, false, magicColor)) count = count + 1; + } + } + } + return count; + } + + /** Attach tuple values as attributes to existing nodes. */ + private void edgesAsAttribute(AlloyRelation rel) { + // If this relation wants to be shown as an attribute, + // then generate the annotations and attach them to each tuple's starting node. + // Eg. + // If (A,B) and (A,C) are both in the relation F, + // then the A node would have a line that says "F: B, C" + // Eg. + // If (A,B,C) and (A,D,E) are both in the relation F, + // then the A node would have a line that says "F: B->C, D->E" + // Eg. + // If (A,B,C) and (A,D,E) are both in the relation F, and B belongs to sets SET1 and SET2, + // and SET1's "show in relational attribute" is on, + // and SET2's "show in relational attribute" is on, + // then the A node would have a line that says "F: B (SET1, SET2)->C, D->E" + // + Map map = new LinkedHashMap(); + for (AlloyTuple tuple: instance.relation2tuples(rel)) { + GraphNode start=atom2node.get(tuple.getStart()); + if (start==null) continue; // null means the node won't be shown, so we can't show any attributes + String attr=""; + List atoms=tuple.getAtoms(); + for (int i=1; i1) attr+="->"; + attr+=atomname(atoms.get(i),true); + } + if (attr.length()==0) continue; + String oldattr=map.get(start); + if (oldattr!=null && oldattr.length()>0) attr=oldattr+", "+attr; + if (attr.length()>0) map.put(start,attr); + } + for (Map.Entry e: map.entrySet()) { + GraphNode node = e.getKey(); + Set list = attribs.get(node); + if (list==null) attribs.put(node, list=new TreeSet()); + String attr = e.getValue(); + if (view.label.get(rel).length()>0) attr = view.label.get(rel) + ": " + attr; + list.add(attr); + } + } + + /** Return the label for an atom. + * @param atom - the atom + * @param showSets - whether the label should also show the sets that this atom belongs to + * + *

eg. If atom A is the 3rd atom in type T, and T's label is "Person", + * then the return value would be "Person3". + * + *

eg. If atom A is the only atom in type T, and T's label is "Person", + * then the return value would be "Person". + * + *

eg. If atom A is the 3rd atom in type T, and T's label is "Person", + * and T belongs to the sets Set1, Set2, and Set3. + * However, only Set1 and Set2 have "show in relational attribute == on", + * then the return value would be "Person (Set1, Set2)". + */ + private String atomname(AlloyAtom atom, boolean showSets) { + String label = atom.getVizName(view, view.number.resolve(atom.getType())); + if (!showSets) return label; + String attr = ""; + boolean showInAttrByDefault = view.showAsAttr.get(null); + for (AlloySet set: instance.atom2sets(atom)) { + String x = view.label.get(set); if (x.length()==0) continue; + Boolean showAsAttr = view.showAsAttr.get(set); + if ((showAsAttr==null && showInAttrByDefault) || (showAsAttr!=null && showAsAttr)) + attr += ((attr.length()>0?", ":"")+x); + } + if (label.length()==0) return (attr.length()>0) ? ("("+attr+")") : ""; + return (attr.length()>0) ? (label+" ("+attr+")") : label; + } + + static String esc(String name) { + if (name.indexOf('\"') < 0) return name; + StringBuilder out = new StringBuilder(); + for(int i=0; iThread Safety: Can be called only by the AWT event thread. + */ + +public final class StaticInstanceReader { + + /** The resulting AlloyInstance object. */ + private final AlloyInstance ans; + + /** This is the list of toplevel sigs. */ + private final List toplevels = new ArrayList(); + + /** This maps each Sig to its corresponding Visualizer AlloyType. */ + private final LinkedHashMap sig2type = new LinkedHashMap(); + + /** This maps each Sig ot its corresponding unique VIsualizer AlloyAtom (if isMeta is true). */ + private final LinkedHashMap sig2atom = new LinkedHashMap(); + + /** This stores the "extends" relationship among sigs (if isMeta is true). */ + private final LinkedHashSet exts = new LinkedHashSet(); + + /** This stores the "in" relationship among sigs (if isMeta is true). */ + private final LinkedHashSet ins = new LinkedHashSet(); + + /** This stores the set of Visualizer AlloySet objects we created. */ + private final Set sets = new LinkedHashSet(); + + /** This maps each Visualizer AlloyRelation to its set of (possibly 0) tuples. */ + private final Map> rels = new LinkedHashMap>(); + + /** For each sig A and B, if A extends B, and B is not univ, then (A,B) will be in this map. */ + private final Map ts = new LinkedHashMap(); + + /** This maps each Visualizer AlloyAtom to its set of (possibly 0) AlloySet that contains it. */ + private final Map> atom2sets = new LinkedHashMap>(); + + /** This maps each AlloyAtom label to the AlloyAtom we created for it. */ + private final Map string2atom = new LinkedHashMap(); + + /** Create a new AlloyType whose label is unambiguous with any existing one. */ + private AlloyType makeType(String label, boolean isOne, boolean isAbstract, boolean isBuiltin, boolean isPrivate, boolean isMeta, boolean isEnum) { + if (label.startsWith("this/")) label = label.substring(5); + while(true) { + AlloyType ans = new AlloyType(label, isOne, isAbstract, isBuiltin, isPrivate, isMeta, isEnum); + if (!sig2type.values().contains(ans)) return ans; + label=label+"'"; + } + } + + /** Create a new AlloySet whose label is unambiguous with any existing one. */ + private AlloySet makeSet(String label, boolean isPrivate, boolean isMeta, AlloyType type) { + while(label.equals(Sig.UNIV.label) || label.equals(Sig.SIGINT.label) || label.equals(Sig.SEQIDX.label) || label.equals(Sig.STRING.label)) label=label+"'"; + while(true) { + AlloySet ans = new AlloySet(label, isPrivate, isMeta, type); + if (!sets.contains(ans)) return ans; + label=label+"'"; + } + } + + /** Create a new AlloyRelation whose label is unambiguous with any existing one. */ + private AlloyRelation makeRel(String label, boolean isPrivate, boolean isMeta, List types) { + while(label.equals(Sig.UNIV.label) || label.equals(Sig.SIGINT.label) || label.equals(Sig.SEQIDX.label) || label.equals(Sig.STRING.label)) label=label+"'"; + while(true) { + AlloyRelation ans = new AlloyRelation(label, isPrivate, isMeta, types); + if (!rels.containsKey(ans)) return ans; + label=label+"'"; + } + } + + /** Returns the AlloyType corresponding to the given sig; create an AlloyType for it if none existed before. */ + private AlloyType sig(PrimSig s) throws Err { + if (s==Sig.NONE) throw new ErrorFatal("Unexpected sig \"none\" encountered."); + AlloyType ans = sig2type.get(s); + if (ans == null) { + ans = makeType(s.label, s.isOne!=null, s.isAbstract!=null, false, s.isPrivate!=null, s.isMeta!=null, s.isEnum!=null); + sig2type.put(s, ans); + if (s.parent!=Sig.UNIV) ts.put(ans, sig(s.parent)); + } + return ans; + } + + /** Returns the AlloyType corresponding to the given sig; create an AlloyType for it if none existed before. */ + private AlloyType sigMETA(PrimSig s) throws Err { + if (s==Sig.NONE) throw new ErrorFatal("Unexpected sig \"none\" encountered."); + AlloyType type = sig2type.get(s); + if (type != null) return type; + if (s==Sig.UNIV) type=AlloyType.UNIV; + else if (s==Sig.SIGINT) type=AlloyType.INT; + else if (s==Sig.SEQIDX) type=AlloyType.SEQINT; + else if (s==Sig.STRING) type=AlloyType.STRING; + else type = makeType(s.label, s.isOne!=null, s.isAbstract!=null, false, s.isPrivate!=null, s.isMeta!=null, s.isEnum!=null); + sig2type.put(s, type); + AlloyAtom atom = new AlloyAtom(type, (type==AlloyType.SEQINT ? Integer.MIN_VALUE : Integer.MAX_VALUE), s.label); + atom2sets.put(atom, new LinkedHashSet()); + sig2atom.put(s, atom); + if (s.parent!=Sig.UNIV && s.parent!=null) + ts.put(type, sigMETA(s.parent)); + if (s.parent!=null) + exts.add(new AlloyTuple(atom, sig2atom.get(s.parent))); + Iterable children = (s==Sig.UNIV ? toplevels : s.children()); + for(PrimSig sub:children) sigMETA(sub); + return type; + } + + /** Returns the AlloyType corresponding to the given sig; create an AlloyType for it if none existed before. */ + private void sigMETA(SubsetSig s) throws Err { + AlloyAtom atom; + AlloyType type = sig2type.get(s); + if (type != null) return; + type = makeType(s.label, s.isOne!=null, s.isAbstract!=null, false, s.isPrivate!=null, s.isMeta!=null, s.isEnum!=null); + atom = new AlloyAtom(type, Integer.MAX_VALUE, s.label); + atom2sets.put(atom, new LinkedHashSet()); + sig2atom.put(s, atom); + sig2type.put(s, type); + ts.put(type, AlloyType.SET); + for(Sig p: ((SubsetSig)s).parents) { + if (p instanceof SubsetSig) sigMETA((SubsetSig)p); else sigMETA((PrimSig)p); + ins.add(new AlloyTuple(atom, sig2atom.get(p))); + } + } + + /** Constructs the atoms corresponding to the given sig. */ + private void atoms(A4Solution sol, PrimSig s) throws Err { + Expr sum=Sig.NONE; + for(PrimSig c:s.children()) { sum=sum.plus(c); atoms(sol, c); } + A4TupleSet ts = (A4TupleSet) (sol.eval(s.minus(sum))); // This ensures that atoms will be associated with the most specific sig + for(A4Tuple z: ts) { + String atom = z.atom(0); + int i, dollar = atom.lastIndexOf('$'); + try { i = Integer.parseInt(dollar>=0 ? atom.substring(dollar+1) : atom); } catch(NumberFormatException ex) { i = Integer.MAX_VALUE; } + AlloyAtom at = new AlloyAtom(sig(s), ts.size()==1 ? Integer.MAX_VALUE : i, atom); + atom2sets.put(at, new LinkedHashSet()); + string2atom.put(atom, at); + } + } + + /** Construct an AlloySet or AlloyRelation corresponding to the given expression. */ + private void setOrRel(A4Solution sol, String label, Expr expr, boolean isPrivate, boolean isMeta) throws Err { + for(List ps:expr.type().fold()) { + if (ps.size()==1) { + PrimSig t = ps.get(0); + AlloySet set = makeSet(label, isPrivate, isMeta, sig(t)); + sets.add(set); + for(A4Tuple tp: (A4TupleSet)(sol.eval(expr.intersect(t)))) { + atom2sets.get(string2atom.get(tp.atom(0))).add(set); + } + } else { + Expr mask = null; + List types = new ArrayList(ps.size()); + for(int i=0; i ts = new LinkedHashSet(); + for(A4Tuple tp: (A4TupleSet)(sol.eval(expr.intersect(mask)))) { + AlloyAtom[] atoms = new AlloyAtom[tp.arity()]; + for(int i=0; i element."); + boolean isMeta = "yes".equals(inst.getAttribute("metamodel")); + A4Solution sol = A4SolutionReader.read(new ArrayList(), root); + for (Sig s:sol.getAllReachableSigs()) if (s instanceof PrimSig && ((PrimSig)s).parent==Sig.UNIV) toplevels.add((PrimSig)s); + if (!isMeta) { + sig2type.put(Sig.UNIV, AlloyType.UNIV); + sig2type.put(Sig.SIGINT, AlloyType.INT); + sig2type.put(Sig.SEQIDX, AlloyType.SEQINT); + sig2type.put(Sig.STRING, AlloyType.STRING); + ts.put(AlloyType.SEQINT, AlloyType.INT); + for(int i=sol.min(), max=sol.max(), maxseq=sol.getMaxSeq(); i<=max; i++) { + AlloyAtom at = new AlloyAtom(i>=0 && i()); + string2atom.put(""+i, at); + } + for(Sig s:sol.getAllReachableSigs()) if (!s.builtin && s instanceof PrimSig) sig((PrimSig)s); + for(Sig s:toplevels) if (!s.builtin || s==Sig.STRING) atoms(sol, (PrimSig)s); + for(Sig s:sol.getAllReachableSigs()) if (s instanceof SubsetSig) setOrRel(sol, s.label, s, s.isPrivate!=null, s.isMeta!=null); + for(Sig s:sol.getAllReachableSigs()) for(Field f:s.getFields()) setOrRel(sol, f.label, f, f.isPrivate!=null, f.isMeta!=null); + for(ExprVar s:sol.getAllSkolems()) setOrRel(sol, s.label, s, false, false); + } + if (isMeta) { + sigMETA(Sig.UNIV); + for(Sig s:sol.getAllReachableSigs()) if (s instanceof SubsetSig) sigMETA((SubsetSig)s); + for(Sig s:sol.getAllReachableSigs()) for(Field f:s.getFields()) { + for(List ps:f.type().fold()) { + List types = new ArrayList(ps.size()); + AlloyAtom[] tuple = new AlloyAtom[ps.size()]; + for(int i=0; i0) { sig2type.put(null, AlloyType.SET); rels.put(AlloyRelation.IN, ins); } + AlloyAtom univAtom = sig2atom.get(Sig.UNIV); + AlloyAtom intAtom = sig2atom.get(Sig.SIGINT); + AlloyAtom seqAtom = sig2atom.get(Sig.SEQIDX); + AlloyAtom strAtom = sig2atom.get(Sig.STRING); + for(Set t: rels.values()) for(AlloyTuple at: t) if (at.getAtoms().contains(univAtom)) { univAtom=null; break; } + for(Set t: rels.values()) for(AlloyTuple at: t) if (at.getAtoms().contains(intAtom)) { intAtom=null; break; } + for(Set t: rels.values()) for(AlloyTuple at: t) if (at.getAtoms().contains(seqAtom)) { seqAtom=null; break; } + for(Set t: rels.values()) for(AlloyTuple at: t) if (at.getAtoms().contains(strAtom)) { strAtom=null; break; } + if (univAtom!=null) { + for(Iterator it=exts.iterator(); it.hasNext();) { + AlloyTuple at=it.next(); + if (at.getStart()==univAtom || at.getEnd()==univAtom) it.remove(); + } + atom2sets.remove(univAtom); + } + if (strAtom!=null) { + for(Iterator it=exts.iterator(); it.hasNext();) { + AlloyTuple at=it.next(); + if (at.getStart()==strAtom || at.getEnd()==strAtom) it.remove(); + } + atom2sets.remove(strAtom); + } + if (intAtom!=null && seqAtom!=null) { + for(Iterator it=exts.iterator(); it.hasNext();) { + AlloyTuple at=it.next(); + if (at.getStart()==intAtom || at.getEnd()==intAtom || at.getStart()==seqAtom || at.getEnd()==seqAtom) it.remove(); + } + atom2sets.remove(intAtom); + atom2sets.remove(seqAtom); + } + if (exts.size()>0) { rels.put(AlloyRelation.EXTENDS, exts); } + } + AlloyModel am = new AlloyModel(sig2type.values(), sets, rels.keySet(), ts); + ans=new AlloyInstance(sol, sol.getOriginalFilename(), sol.getOriginalCommand(), am, atom2sets, rels, isMeta); + } + + /** Parse the file into an AlloyInstance if possible. */ + public static AlloyInstance parseInstance(File file) throws Err { + try { + return (new StaticInstanceReader(new XMLNode(file))).ans; + } catch(IOException ex) { + throw new ErrorFatal("Error reading the XML file: " + ex, ex); + } + } + + /** Parse the file into an AlloyInstance if possible, then close the Reader afterwards. */ + public static AlloyInstance parseInstance(Reader reader) throws Err { + try { + return (new StaticInstanceReader(new XMLNode(reader))).ans; + } catch(IOException ex) { + throw new ErrorFatal("Error reading the XML file: " + ex, ex); + } + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4viz/StaticProjector.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4viz/StaticProjector.java new file mode 100644 index 00000000..de320816 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4viz/StaticProjector.java @@ -0,0 +1,190 @@ +/* + * Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4viz; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * This utility class performs projection of AlloyModel and AlloyInstance. + * + *

+ * Thread Safety: Can be called only by the AWT event thread. + */ + +public final class StaticProjector { + + /** Constructor is private, since this utility class never needs to be instantiated. */ + private StaticProjector() {} + + /** + * Given an unprojected model, project it over the given collection of AlloyType(s). + * + * @param unprojectedModel - the original unprojected model + * @param typesToBeProjected - the collection of types to project over + */ + public static AlloyModel project(AlloyModel unprojectedModel, + Collection typesToBeProjected) { + return project(unprojectedModel, typesToBeProjected, null); + } + + /** + * Given an unprojected model, project it over the given collection of AlloyType(s). + * + * @param unprojectedModel - the original unprojected model + * @param typesToBeProjected - the collection of types to project over + * @param data - if nonnull, this method will record into this map how the relations are changed + * by the projection + *

+ * For every relation R that gets altered to become a new AlloySet or a new AlloyRelation, + * data.get(R) will give us a list of integers indicating the columns deleted from R due to + * the projection. (For example, if an original relation A->B->C->D becomes B->D, then the + * list of integers will be (0,2) indicating the first and third columns were removed). + *

+ * If a relation R remains unchanged during the projection, then data.get(R) will return an + * empty list. + *

+ * If a relation R is totally deleted, due to the projection, then R won't be in + * data.keySet(). + */ + private static AlloyModel project(AlloyModel unprojectedModel, + Collection typesToBeProjected, Map> data) { + Set types = new LinkedHashSet(unprojectedModel.getTypes()); + List sets = new ArrayList(unprojectedModel.getSets()); + List relations = new ArrayList(); + // Get rid of all projected types, as well as their subtypes. + for (AlloyType type : typesToBeProjected) { + types.remove(type); + types.removeAll(unprojectedModel.getSubTypes(type)); + } + types.add(AlloyType.UNIV); // "univ" has to be a type + // Now go over the relations... + for (AlloyRelation rel : unprojectedModel.getRelations()) { + List relTypes = new ArrayList(rel.getTypes()); + List indices = new ArrayList(); + int currentIndex = 0; + // For each type in a relation, if it is a removed type, remove it and keep track of its + // index. + for (Iterator relTypesIter = relTypes.iterator(); relTypesIter.hasNext();) { + if (!types.contains(relTypesIter.next())) { + relTypesIter.remove(); + indices.add(currentIndex); + } + currentIndex++; + } + // If the relation still contains at least two types, it becomes a new relation + if (relTypes.size() > 1) { + relations.add(new AlloyRelation(rel.getName(), rel.isPrivate, rel.isMeta, relTypes)); + if (data != null) + data.put(rel, indices); + } + // If it contains only one type, it becomes a new set. + else if (relTypes.size() == 1) { + sets.add(new AlloySet(rel.getName(), rel.isPrivate, rel.isMeta, relTypes.get(0))); + if (data != null) + data.put(rel, indices); + } + } + // Finally, go through the sets and remove any whose type was removed. + for (Iterator setsIter = sets.iterator(); setsIter.hasNext();) { + AlloySet set = setsIter.next(); + if (!types.contains(set.getType())) + setsIter.remove(); + } + return new AlloyModel(types, sets, relations, unprojectedModel); + } + + /** + * Project an instance over the given list of types (and their associated chosen atom). + * + * @param oldInstance - the original unprojected instance + * @param projection - the list of types to be projected and their associated chosen atoms + * + *

+ * For each type t in projection.getProjectedTypes: + * + *

+ * (1) If t doesn't exist in the instance, then we will simply ignore t. + * + *

+ * (2) Otherwise, if t has one or more atoms in the original instance,
+ * then projection.getProjectedAtom(t) must be one of the atoms (indicating the chosen atom + * for that type)
+ * else projection.getProjectedAtom(t) must be null.
+ * If rule (2) is violated, then some tuples may not show up in the return value. + */ + public static AlloyInstance project(AlloyInstance oldInstance, AlloyProjection projection) { + Map> data = new LinkedHashMap>(); + Map> atom2sets = new LinkedHashMap>(); + Map> rel2tuples = + new LinkedHashMap>(); + AlloyModel newModel = project(oldInstance.model, projection.getProjectedTypes(), data); + // First put all the atoms from the old instance into the new one + for (AlloyAtom atom : oldInstance.getAllAtoms()) { + atom2sets.put(atom, new LinkedHashSet(oldInstance.atom2sets(atom))); + } + // Now, decide what tuples to generate + for (AlloyRelation r : oldInstance.model.getRelations()) { + List list = data.get(r); + if (list == null) + continue; // This means that relation was deleted entirely + tupleLabel: for (AlloyTuple oldTuple : oldInstance.relation2tuples(r)) { + for (Integer i : list) { + // If an atom in the original tuple should be projected, but it doesn't match the + // chosen atom for that type, then this tuple must not be included in the new instance + AlloyAtom a = oldTuple.getAtoms().get(i); + AlloyType bt = r.getTypes().get(i); + bt = oldInstance.model.getTopmostSuperType(bt); + if (!a.equals(projection.getProjectedAtom(bt))) + continue tupleLabel; + } + List newTuple = oldTuple.project(list); + List newObj = r.project(list); + if (newObj.size() > 1 && newTuple.size() > 1) { + AlloyRelation r2 = new AlloyRelation(r.getName(), r.isPrivate, r.isMeta, newObj); + Set answer = rel2tuples.get(r2); + if (answer == null) + rel2tuples.put(r2, answer = new LinkedHashSet()); + AlloyTuple newT = new AlloyTuple(newTuple); + newT.isDashed = oldTuple.isDashed; + answer.add(newT); + } else if (newObj.size() == 1 && newTuple.size() == 1) { + AlloyAtom a = newTuple.get(0); + Set answer = atom2sets.get(a); + if (answer == null) + atom2sets.put(a, answer = new LinkedHashSet()); + answer.add(new AlloySet(r.getName(), r.isPrivate, r.isMeta, newObj.get(0))); + } + } + } + // Here, we don't have to explicitly filter out "illegal" atoms/tuples/... + // (that is, atoms that belong to types that no longer exist, etc). + // That's because AlloyInstance's constructor must do the check too, so there's no point in + // doing that twice. + return new AlloyInstance(oldInstance.originalA4, oldInstance.filename, oldInstance.commandname, + newModel, atom2sets, rel2tuples, oldInstance.isMetamodel); + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4viz/StaticThemeReaderWriter.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4viz/StaticThemeReaderWriter.java new file mode 100644 index 00000000..151c257d --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4viz/StaticThemeReaderWriter.java @@ -0,0 +1,465 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4viz; + +import java.io.File; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +import java.util.TreeSet; +import edu.mit.csail.sdg.alloy4.Util; +import edu.mit.csail.sdg.alloy4.XMLNode; +import edu.mit.csail.sdg.alloy4graph.DotColor; +import edu.mit.csail.sdg.alloy4graph.DotPalette; +import edu.mit.csail.sdg.alloy4graph.DotShape; +import edu.mit.csail.sdg.alloy4graph.DotStyle; + +/** This utility class contains methods to read and write VizState customizations. + * + *

Thread Safety: Can be called only by the AWT event thread. + */ + +public final class StaticThemeReaderWriter { + + /** Constructor is private, since this utility class never needs to be instantiated. */ + private StaticThemeReaderWriter() { } + + /** Read the XML file and merge its settings into an existing VizState object. */ + public static void readAlloy(String filename, VizState theme) throws IOException { + File file = new File(filename); + try { + XMLNode elem = new XMLNode(file); + for(XMLNode sub: elem.getChildren("view")) parseView(sub,theme); + } catch(Throwable e) { + throw new IOException("The file \""+file.getPath()+"\" is not a valid XML file, or an error occurred in reading."); + } + } + + /** Write the VizState's customizations into a new file (which will be overwritten if it exists). */ + public static void writeAlloy(String filename, VizState theme) throws IOException { + PrintWriter bw = new PrintWriter(filename,"UTF-8"); + bw.write("\n\n\n"); + if (theme!=null) { + try { + writeView(bw, theme); + } catch(IOException ex) { + Util.close(bw); + throw new IOException("Error writing to the file \""+filename+"\""); + } + } + bw.write("\n\n"); + if (!Util.close(bw)) throw new IOException("Error writing to the file \""+filename+"\""); + } + + /*============================================================================================*/ + + /** Does nothing if the element is malformed. */ + private static void parseView(final XMLNode x, VizState now) { + /* + * + * .. + * + * + * 0 or more NODE or EDGE + * + */ + if (!x.is("view")) return; + for(XMLNode xml:x) { + if (xml.is("projection")) { + now.deprojectAll(); + for(AlloyType t:parseProjectionList(now,xml)) now.project(t); + } + } + if (has(x,"useOriginalAtomNames")) now.useOriginalName(getbool(x,"useOriginalAtomNames")); + if (has(x,"hidePrivate")) now.hidePrivate(getbool(x,"hidePrivate")); + if (has(x,"hideMeta")) now.hideMeta(getbool(x,"hideMeta")); + if (has(x,"fontsize")) now.setFontSize(getint(x,"fontsize")); + if (has(x,"nodetheme")) now.setNodePalette(parseDotPalette(x,"nodetheme")); + if (has(x,"edgetheme")) now.setEdgePalette(parseDotPalette(x,"edgetheme")); + for(XMLNode xml:x) { + if (xml.is("defaultnode")) parseNodeViz(xml, now, null); + else if (xml.is("defaultedge")) parseEdgeViz(xml, now, null); + else if (xml.is("node")) { + for(XMLNode sub:xml.getChildren("type")) { + AlloyType t=parseAlloyType(now,sub); if (t!=null) parseNodeViz(xml, now, t); + } + for(XMLNode sub:xml.getChildren("set")) { + AlloySet s=parseAlloySet(now,sub); if (s!=null) parseNodeViz(xml, now, s); + } + } + else if (xml.is("edge")) { + for(XMLNode sub:xml.getChildren("relation")) { + AlloyRelation r=parseAlloyRelation(now,sub); if (r!=null) parseEdgeViz(xml, now, r); + } + } + } + } + + /*============================================================================================*/ + + /** Writes nothing if the argument is null. */ + private static void writeView(PrintWriter out, VizState view) throws IOException { + if (view==null) return; + VizState defaultView=new VizState(view.getOriginalInstance()); + out.write("\n"); + if (view.getProjectedTypes().size()>0) writeProjectionList(out, view.getProjectedTypes()); + out.write("\n\n\n\n"); + // === nodes === + Set types = new TreeSet(); + types.addAll(view.getOriginalModel().getTypes()); + types.addAll(view.getCurrentModel().getTypes()); + types.addAll(view.getOriginalModel().getSets()); + types.addAll(view.getCurrentModel().getSets()); + Map> viz2node=new TreeMap>(); + for(AlloyNodeElement t:types) { + String str=writeNodeViz(view,defaultView,t); + Set nodes=viz2node.get(str); + if (nodes==null) viz2node.put(str, nodes=new TreeSet()); + nodes.add(t); + } + for(Map.Entry> e:viz2node.entrySet()) { + out.write("\n\n"); + for(AlloyNodeElement ts:e.getValue()) { + if (ts instanceof AlloyType) writeAlloyType(out,(AlloyType)ts); + else if (ts instanceof AlloySet) writeAlloySet(out,(AlloySet)ts); + } + out.write("\n"); + } + // === edges === + Set rels = new TreeSet(); + rels.addAll(view.getOriginalModel().getRelations()); + rels.addAll(view.getCurrentModel().getRelations()); + Map> viz2edge=new TreeMap>(); + for(AlloyRelation r:rels) { + String str=writeEdgeViz(view,defaultView,r); + if (str.length()==0) continue; + Set edges=viz2edge.get(str); + if (edges==null) viz2edge.put(str, edges=new TreeSet()); + edges.add(r); + } + for(Map.Entry> e:viz2edge.entrySet()) { + out.write("\n\n"); + for(AlloyRelation r:e.getValue()) writeAlloyRelation(out,r); + out.write("\n"); + } + // === done === + out.write("\n\n"); + } + + /*============================================================================================*/ + + /** Return null if the element is malformed. */ + private static AlloyType parseAlloyType(VizState now, XMLNode x) { + /* class AlloyType implements AlloyNodeElement { + * String name; + * } + * + */ + if (!x.is("type")) return null; + String name=x.getAttribute("name"); + if (name.length()==0) return null; else return now.getCurrentModel().hasType(name); + } + + /** Writes nothing if the argument is null. */ + private static void writeAlloyType(PrintWriter out, AlloyType x) throws IOException { + if (x!=null) Util.encodeXMLs(out, " \n"); + } + + /*============================================================================================*/ + + /** Return null if the element is malformed. */ + private static AlloySet parseAlloySet(VizState now, XMLNode x) { + /* class AlloySet implements AlloyNodeElement { + * String name; + * AlloyType type; + * } + * + */ + if (!x.is("set")) return null; + String name=x.getAttribute("name"), type=x.getAttribute("type"); + if (name.length()==0 || type.length()==0) return null; + AlloyType t=now.getCurrentModel().hasType(type); + if (t==null) return null; else return now.getCurrentModel().hasSet(name, t); + } + + /** Writes nothing if the argument is null. */ + private static void writeAlloySet(PrintWriter out, AlloySet x) throws IOException { + if (x!=null) Util.encodeXMLs(out," \n"); + } + + /*============================================================================================*/ + + /** Return null if the element is malformed. */ + private static AlloyRelation parseAlloyRelation(VizState now, XMLNode x) { + /* + * + * 2 or more + * + */ + List ans=new ArrayList(); + if (!x.is("relation")) return null; + String name=x.getAttribute("name"); + if (name.length()==0) return null; + for(XMLNode sub:x.getChildren("type")) { + String typename=sub.getAttribute("name"); + if (typename.length()==0) return null; + AlloyType t = now.getCurrentModel().hasType(typename); + if (t==null) return null; + ans.add(t); + } + if (ans.size()<2) return null; else return now.getCurrentModel().hasRelation(name, ans); + } + + /** Writes nothing if the argument is null. */ + private static void writeAlloyRelation(PrintWriter out, AlloyRelation x) throws IOException { + if (x==null) return; + Util.encodeXMLs(out, " "); + for(AlloyType t:x.getTypes()) Util.encodeXMLs(out, " "); + out.write(" \n"); + } + + /*============================================================================================*/ + + /** Always returns a nonnull (though possibly empty) set of AlloyType. */ + private static Set parseProjectionList(VizState now, XMLNode x) { + /* + * + * 0 or more + * + */ + Set ans=new TreeSet(); + if (x.is("projection")) for(XMLNode sub:x.getChildren("type")) { + String name=sub.getAttribute("name"); + if (name.length()==0) continue; + AlloyType t = now.getOriginalModel().hasType(name); + if (t!=null) ans.add(t); + } + return ans; + } + + /** Writes an empty Projection tag if the argument is null or empty */ + private static void writeProjectionList(PrintWriter out, Set types) throws IOException { + if (types==null || types.size()==0) { out.write("\n\n"); return; } + out.write("\n"); + for(AlloyType t:types) Util.encodeXMLs(out, " "); + out.write(" \n"); + } + + /*============================================================================================*/ + + /** Do nothing if the element is malformed; note: x can be null. */ + private static void parseNodeViz(XMLNode xml, VizState view, AlloyNodeElement x) { + /* + * + * zero or more SET or TYPE + * + * + * Each attribute, if omitted, means "no change". + * Note: BOOLEAN is tristate. + */ + if (has(xml,"visible")) view.nodeVisible.put (x, getbool(xml, "visible")); + if (has(xml,"hideunconnected")) view.hideUnconnected.put (x, getbool(xml, "hideunconnected")); + if (x==null || x instanceof AlloySet) { + AlloySet s=(AlloySet)x; + if (has(xml,"showlabel")) view.showAsLabel.put (s, getbool(xml, "showlabel")); + if (has(xml,"showinattr")) view.showAsAttr.put (s, getbool(xml, "showinattr")); + } + if (x==null || x instanceof AlloyType) { + AlloyType t=(AlloyType)x; + if (has(xml,"numberatoms")) view.number.put (t, getbool(xml, "numberatoms")); + } + if (has(xml,"style")) view.nodeStyle.put(x, parseDotStyle(xml)); + if (has(xml,"color")) view.nodeColor.put(x, parseDotColor(xml)); + if (has(xml,"shape")) view.shape .put(x, parseDotShape(xml)); + if (has(xml,"label")) view.label .put(x, xml.getAttribute("label")); + } + + /** Returns the String representation of an AlloyNodeElement's settings. */ + private static String writeNodeViz(VizState view, VizState defaultView, AlloyNodeElement x) throws IOException { + StringWriter sw=new StringWriter(); + PrintWriter out=new PrintWriter(sw); + writeBool(out, "visible", view.nodeVisible.get(x), defaultView.nodeVisible.get(x)); + writeBool(out, "hideunconnected", view.hideUnconnected.get(x), defaultView.hideUnconnected.get(x)); + if (x==null || x instanceof AlloySet) { + AlloySet s=(AlloySet)x; + writeBool(out, "showlabel", view.showAsLabel.get(s), defaultView.showAsLabel.get(s)); + writeBool(out, "showinattr", view.showAsAttr.get(s), defaultView.showAsAttr.get(s)); + } + if (x==null || x instanceof AlloyType) { + AlloyType t=(AlloyType)x; + writeBool(out, "numberatoms", view.number.get(t), defaultView.number.get(t)); + } + writeDotStyle(out, view.nodeStyle.get(x), defaultView.nodeStyle.get(x)); + writeDotShape(out, view.shape.get(x), defaultView.shape.get(x)); + writeDotColor(out, view.nodeColor.get(x), defaultView.nodeColor.get(x)); + if (x!=null && !view.label.get(x).equals(defaultView.label.get(x))) + Util.encodeXMLs(out, " label=\"", view.label.get(x), "\""); + if (out.checkError()) throw new IOException("PrintWriter IO Exception!"); + return sw.toString(); + } + + /*============================================================================================*/ + + /** Do nothing if the element is malformed; note: x can be null. */ + private static void parseEdgeViz(XMLNode xml, VizState view, AlloyRelation x) { + /* + * + * zero or more RELATION + * + * + * Each attribute, if omitted, means "no change". + * Note: BOOLEAN is tristate. + */ + if (has(xml,"visible")) view.edgeVisible.put (x, getbool(xml,"visible")); + if (has(xml,"attribute")) view.attribute .put (x, getbool(xml,"attribute")); + if (has(xml,"merge")) view.mergeArrows.put (x, getbool(xml,"merge")); + if (has(xml,"layout")) view.layoutBack .put (x, getbool(xml,"layout")); + if (has(xml,"constraint")) view.constraint .put (x, getbool(xml,"constraint")); + if (has(xml,"style")) view.edgeStyle .put (x, parseDotStyle(xml)); + if (has(xml,"color")) view.edgeColor .put (x, parseDotColor(xml)); + if (has(xml,"weight")) view.weight .put (x, getint (xml,"weight")); + if (has(xml,"label")) view.label .put (x, xml.getAttribute("label")); + } + + /** Returns the String representation of an AlloyRelation's settings. */ + private static String writeEdgeViz(VizState view, VizState defaultView, AlloyRelation x) throws IOException { + StringWriter sw=new StringWriter(); + PrintWriter out=new PrintWriter(sw); + writeDotColor(out, view.edgeColor.get(x), defaultView.edgeColor.get(x)); + writeDotStyle(out, view.edgeStyle.get(x), defaultView.edgeStyle.get(x)); + writeBool(out, "visible", view.edgeVisible.get(x), defaultView.edgeVisible.get(x)); + writeBool(out, "merge", view.mergeArrows.get(x), defaultView.mergeArrows.get(x)); + writeBool(out, "layout", view.layoutBack.get(x), defaultView.layoutBack.get(x)); + writeBool(out, "attribute", view.attribute.get(x), defaultView.attribute.get(x)); + writeBool(out, "constraint",view.constraint.get(x), defaultView.constraint.get(x)); + if (view.weight.get(x) != defaultView.weight.get(x)) out.write(" weight=\"" + view.weight.get(x) + "\""); + if (x!=null && !view.label.get(x).equals(defaultView.label.get(x))) + Util.encodeXMLs(out, " label=\"", view.label.get(x), "\""); + if (out.checkError()) throw new IOException("PrintWriter IO Exception!"); + return sw.toString(); + } + + /*============================================================================================*/ + + /** Returns null if the attribute doesn't exist, or is malformed. */ + private static DotPalette parseDotPalette(XMLNode x, String key) { + return DotPalette.parse(x.getAttribute(key)); + } + + /** Writes nothing if value==defaultValue. */ + private static void writeDotPalette(PrintWriter out, String key, DotPalette value, DotPalette defaultValue) throws IOException { + if (value!=defaultValue) Util.encodeXMLs(out, " "+key+"=\"", value==null?"inherit":value.toString(), "\""); + } + + /*============================================================================================*/ + + /** Returns null if the attribute doesn't exist, or is malformed. */ + private static DotColor parseDotColor(XMLNode x) { return DotColor.parse(x.getAttribute("color")); } + + /** Writes nothing if value==defaultValue. */ + private static void writeDotColor(PrintWriter out, DotColor value, DotColor defaultValue) throws IOException { + if (value!=defaultValue) Util.encodeXMLs(out, " color=\"", value==null?"inherit":value.toString(), "\""); + } + + /*============================================================================================*/ + + /** Returns null if the attribute doesn't exist, or is malformed. */ + private static DotShape parseDotShape(XMLNode x) { return DotShape.parse(x.getAttribute("shape")); } + + /** Writes nothing if value==defaultValue. */ + private static void writeDotShape(PrintWriter out, DotShape value, DotShape defaultValue) throws IOException { + if (value!=defaultValue) Util.encodeXMLs(out, " shape=\"", value==null?"inherit":value.toString(), "\""); + } + + /*============================================================================================*/ + + /** Returns null if the attribute doesn't exist, or is malformed. */ + private static DotStyle parseDotStyle(XMLNode x) { return DotStyle.parse(x.getAttribute("style")); } + + /** Writes nothing if value==defaultValue. */ + private static void writeDotStyle(PrintWriter out, DotStyle value, DotStyle defaultValue) throws IOException { + if (value!=defaultValue) Util.encodeXMLs(out, " style=\"", value==null?"inherit":value.toString(), "\""); + } + + /*============================================================================================*/ + + /** Returns null if the attribute doesn't exist, or is malformed. */ + private static Boolean getbool(XMLNode x, String attr) { + String value=x.getAttribute(attr); + if (value.equalsIgnoreCase("yes") || value.equalsIgnoreCase("true")) return Boolean.TRUE; + if (value.equalsIgnoreCase("no") || value.equalsIgnoreCase("false")) return Boolean.FALSE; + return null; + } + + /** Writes nothing if the value is equal to the default value. */ + private static void writeBool(PrintWriter out, String key, Boolean value, Boolean defaultValue) throws IOException { + if (value==null && defaultValue==null) return; + if (value!=null && defaultValue!=null && value.booleanValue()==defaultValue.booleanValue()) return; + out.write(' '); + out.write(key); + if (value==null) out.write("=\"inherit\""); else out.write(value ? "=\"yes\"":"=\"no\""); + } + + /*============================================================================================*/ + + /** Returns true if the XML element has the given attribute. */ + private static boolean has(XMLNode x, String attr) { + return x.getAttribute(attr,null)!=null; + } + + /** Returns 0 if the attribute doesn't exist, or is malformed. */ + private static int getint(XMLNode x, String attr) { + String value=x.getAttribute(attr); + int i; + try { + i=Integer.parseInt(value); + } catch(NumberFormatException ex) { + i=0; + } + return i; + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4viz/VizCustomizationPanel.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4viz/VizCustomizationPanel.java new file mode 100644 index 00000000..dd63e603 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4viz/VizCustomizationPanel.java @@ -0,0 +1,473 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4viz; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.FocusAdapter; +import java.awt.event.FocusEvent; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.ArrayList; +import java.util.List; +import javax.swing.BoxLayout; +import javax.swing.Icon; +import javax.swing.JComboBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JSpinner; +import javax.swing.JSplitPane; +import javax.swing.JTextField; +import javax.swing.SpinnerNumberModel; +import javax.swing.border.EmptyBorder; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import javax.swing.tree.TreePath; +import edu.mit.csail.sdg.alloy4.Listener; +import edu.mit.csail.sdg.alloy4.OurBorder; +import edu.mit.csail.sdg.alloy4.OurCombobox; +import edu.mit.csail.sdg.alloy4.OurCheckbox; +import edu.mit.csail.sdg.alloy4.OurTree; +import edu.mit.csail.sdg.alloy4.OurUtil; +import edu.mit.csail.sdg.alloy4.Util; +import edu.mit.csail.sdg.alloy4graph.DotColor; +import edu.mit.csail.sdg.alloy4graph.DotPalette; +import edu.mit.csail.sdg.alloy4graph.DotShape; +import edu.mit.csail.sdg.alloy4graph.DotStyle; + +/** GUI panel for making customization changes. + * + *

Thread Safety: Can be called only by the AWT event thread. + */ + +public final class VizCustomizationPanel extends JPanel { + + /** This ensures the class can be serialized reliably. */ + private static final long serialVersionUID = 0; + + /** The TREE NODE representing the root of the customization panel. */ + private static final Object ROOT = new Object(); + + /** The TREE NODE representing the GENERAL OPTIONS panel. */ + private static final Object GENERAL = new Object(); + + /** The TREE NODE representing the GENERAL NODES panel. */ + private static final Object NODES = new Object(); + + /** The TREE NODE representing the GENERAL EDGES panel. */ + private static final Object EDGES = new Object(); + + /** This is the VizState object that this customization panel will customize. */ + private final VizState vizState; + + /** This is the background color for the upper-half of the customization panel. */ + private static final Color wcolor = new Color(0.9f, 0.9f, 0.9f); + + /** This is the upper-half of the customization panel. */ + private final JPanel zoomPane; + + /** The JSplitPane separating this customization panel with the main graph panel. */ + private final JSplitPane divider; + + /** If it's an instance of AlloyElement, that means it's the latest selected type/set/relation. + * If it's GENERAL, NODES, or EDGES, that means the latest selected panel is + * the General Graph Settings, Default Type+Set, or Default Relation panels respectively. + * All else, that means the zoom panel is empty. + */ + private Object lastElement = null; + + //=============================================================================================================// + + /** Constructs a customization panel. + * @param divider - the JSplitPane separating the left-customization-half with the right-graph-half + * @param vizState - the VizState object that will be customized by this customization panel + */ + public VizCustomizationPanel(JSplitPane divider, VizState vizState) { + this.divider = divider; + this.vizState = vizState; + setBorder(null); + setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); + zoomPane = new JPanel(); + zoomPane.setBorder(new OurBorder(false,false,true,false)); + zoomPane.setLayout(new BoxLayout(zoomPane, BoxLayout.Y_AXIS)); + zoomPane.setAlignmentX(0f); + zoomPane.setBackground(wcolor); + remakeAll(); + } + + //=============================================================================================================// + + /** This method selects a particular treenode and shows the details of a particular Type/Set/Relation. + *

+ * If x is an instance of AlloyElement, that means it's the latest selected type/set/relation. + * If x is GENERAL, NODES, or EDGES, that means the latest selected panel is + * the General Graph Settings, Default Type+Set, or Default Relation panels respectively. + */ + private void zoom(Object x) { + lastElement = x; + zoomPane.removeAll(); + if (x instanceof AlloyNodeElement) makeNodeOptionsPanel(zoomPane, (AlloyNodeElement)x); + else if (x instanceof AlloyRelation) makeEdgeOptionsPanel(zoomPane, (AlloyRelation)x); + else if (x == GENERAL) createGeneralWidget(zoomPane); + else if (x == NODES) createDefaultNodeWidget(zoomPane); + else if (x == EDGES) createDefaultEdgeWidget(zoomPane); + else { + // The following 2 lines make sure the panel doesn't get too small on Mac + zoomPane.add(OurUtil.makeH(wcolor, new JLabel(" "), (Object)null)); + zoomPane.add(OurUtil.makeH(new Dimension(250, 200), wcolor, (Object)null)); + } + Dimension dim = zoomPane.getPreferredSize(); + if (divider!=null && divider.getDividerLocation()dim.width) dim.width=divider.getDividerLocation(); + dim.height = 150; + zoomPane.setPreferredSize(dim); + dim.width = 450; + zoomPane.setMinimumSize(dim); + zoomPane.repaint(); + validate(); + } + + //=============================================================================================================// + + /** Regenerate all the customization widgets based on the latest settings. */ + public void remakeAll() { + // Make the tree + final OurTree tree = new OurTree(12) { + private static final long serialVersionUID = 0; + private final AlloyModel old = vizState.getOriginalModel(), now = vizState.getCurrentModel(); + private final boolean hidePrivate = vizState.hidePrivate(), hideMeta = vizState.hideMeta(); + { + do_start(); + setRootVisible(false); + setShowsRootHandles(false); + listeners.add(new Listener() { + public Object do_action(Object sender, Event event) { return null; } + public Object do_action(Object sender, Event event, Object arg) { zoom(arg); return null; } + }); + } + @Override public String convertValueToText(Object value, boolean sel, boolean expand, boolean leaf, int i, boolean focus) { + if (value==GENERAL) return "general graph settings"; + if (value==NODES) return "types and sets"; + if (value==EDGES) return "relations"; + if (value instanceof AlloyType) { + AlloyType x = (AlloyType)value; + if (vizState.getCurrentModel().hasType(x)) return "sig "+typename(x)+""; + return "sig "+typename(x)+" (projected)"; + } + if (value instanceof AlloySet) return "set "+ ((AlloySet)value).getName() + ""; + if (value instanceof AlloyRelation) return value.toString(); else return ""; + } + @Override public List do_ask(Object parent) { + ArrayList ans = new ArrayList(); + if (parent==ROOT) { ans.add(GENERAL); ans.add(NODES); ans.add(EDGES); } + else if (parent==NODES) { ans.add(AlloyType.UNIV); } + else if (parent==EDGES) { + for (AlloyRelation rel: vizState.getCurrentModel().getRelations()) + if (!(hidePrivate && rel.isPrivate) && !(hideMeta && rel.isMeta)) ans.add(rel); + } else if (parent instanceof AlloyType) { + AlloyType type = (AlloyType)parent; + for (AlloySet s:now.getSets()) + if (!(hidePrivate && s.isPrivate) && !(hideMeta && s.isMeta) && s.getType().equals(type)) ans.add(s); + if (!type.isEnum) for(AlloyType t: old.getDirectSubTypes(type)) + if (!(hidePrivate && t.isPrivate) && !(hideMeta && t.isMeta)) + if (now.hasType(t) || vizState.canProject(t)) ans.add(t); + } + return ans; + } + @Override public boolean do_isDouble(Object object) { return object==NODES || object==EDGES; } + @Override public Object do_root() { return ROOT; } + }; + // Pre-expand the entire tree. + TreePath last = null; + for(int i=0; i " + Util.encode(rel.toString()) + "")); + parent.add(OurUtil.makeH(10, labelText, wcolor, 5, color, 5, style, 3, weightPanel, 2, null)); + parent.add(OurUtil.makeHT(wcolor, 10, panel1, 15, panel2, 2, null)); + } + + //=============================================================================================================// + + /** Generates the "general graph settings" widgets, and add them to "parent". */ + private void createGeneralWidget(JPanel parent) { + final List fontSizes = Util.asList(9,10,11,12,14,16,18,20,22,24,26,28,32,36,40,44,48,54,60,66,72); + JLabel nLabel = OurUtil.label("Node Color Palette:"); + JLabel eLabel = OurUtil.label("Edge Color Palette:"); + JLabel aLabel = OurUtil.label("Use original atom names:"); + JLabel pLabel = OurUtil.label("Hide private sigs/relations:"); + JLabel mLabel = OurUtil.label("Hide meta sigs/relations:"); + JLabel fLabel = OurUtil.label("Font Size:"); + JComboBox fontSize = new OurCombobox(false, fontSizes.toArray(), 60, 32, vizState.getFontSize()) { + private static final long serialVersionUID = 0; + @Override public void do_changed(Object value) { if (fontSizes.contains(value)) vizState.setFontSize((Integer)value); } + }; + JComboBox nodepal = new OurCombobox(false, DotPalette.values(), 100, 32, vizState.getNodePalette()) { + private static final long serialVersionUID = 0; + @Override public String do_getText(Object value) { return ((DotPalette)value).getDisplayedText(); } + @Override public void do_changed(Object value) { vizState.setNodePalette((DotPalette)value); } + }; + JComboBox edgepal = new OurCombobox(false, DotPalette.values(), 100, 32, vizState.getEdgePalette()) { + private static final long serialVersionUID = 0; + @Override public String do_getText(Object value) { return ((DotPalette)value).getDisplayedText(); } + @Override public void do_changed(Object value) { vizState.setEdgePalette((DotPalette)value); } + }; + JPanel name = new OurCheckbox("", "Whether the visualizer should use the original atom names as-is.", vizState.useOriginalName() ? OurCheckbox.ON : OurCheckbox.OFF) { + private static final long serialVersionUID = 0; + public Icon do_action() { boolean x = vizState.useOriginalName(); vizState.useOriginalName(!x); return (!x ? ON : OFF); } + }; + JPanel priv = new OurCheckbox("", "Whether the visualizer should hide private sigs, sets, and relations by default.", vizState.hidePrivate() ? OurCheckbox.ON : OurCheckbox.OFF) { + private static final long serialVersionUID = 0; + public Icon do_action() { boolean x = vizState.hidePrivate(); vizState.hidePrivate(!x); remakeAll(); return (!x ? ON : OFF); } + }; + JPanel meta = new OurCheckbox("", "Whether the visualizer should hide meta sigs, sets, and relations by default.", vizState.hideMeta() ? OurCheckbox.ON : OurCheckbox.OFF) { + private static final long serialVersionUID = 0; + public Icon do_action() { boolean x = vizState.hideMeta(); vizState.hideMeta(!x); remakeAll(); return (!x ? ON : OFF); } + }; + parent.add(makelabel(" General Graph Settings:")); + parent.add(OurUtil.makeH(wcolor, new Dimension(6, 6))); + parent.add(OurUtil.makeH(wcolor, 25, nLabel, 5, nodepal, 8, aLabel, 5, name, 2, null)); + parent.add(OurUtil.makeH(wcolor, 25, eLabel, 5, edgepal, 8, fLabel, 5, fontSize, 2, null)); + parent.add(OurUtil.makeH(wcolor, 25, pLabel, 5, priv, 2, null)); + parent.add(OurUtil.makeH(wcolor, 25, mLabel, 5, meta, 2, null)); + } + + //=============================================================================================================// + + /** Generates the "default type and set settings" widgets, and add them to "parent". */ + private void createDefaultNodeWidget(JPanel parent) { + JComboBox color = new OurCombobox(false, DotColor.valuesWithout(DotColor.MAGIC), 110, 35, vizState.nodeColor.get(null)) { + private static final long serialVersionUID = 0; + @Override public String do_getText(Object value) { return ((DotColor)value).getDisplayedText(); } + @Override public Icon do_getIcon(Object value) { return ((DotColor)value).getIcon(vizState.getNodePalette()); } + @Override public void do_changed(Object value) { vizState.nodeColor.put(null, (DotColor)value); } + }; + JComboBox shape = new OurCombobox(false, DotShape.values(), 135, 35, vizState.shape.get(null)) { + private static final long serialVersionUID = 0; + @Override public String do_getText(Object value) { return ((DotShape)value).getDisplayedText(); } + @Override public Icon do_getIcon(Object value) { return ((DotShape)value).getIcon(); } + @Override public void do_changed(Object value) { vizState.shape.put(null, (DotShape)value); } + }; + JComboBox style = new OurCombobox(false, DotStyle.values(), 110, 35, vizState.nodeStyle.get(null)) { + private static final long serialVersionUID = 0; + @Override public String do_getText(Object value) { return ((DotStyle)value).getDisplayedText(); } + @Override public Icon do_getIcon(Object value) { return ((DotStyle)value).getIcon(); } + @Override public void do_changed(Object value) { vizState.nodeStyle.put(null, (DotStyle)value); } + }; + JPanel vis = vizState.nodeVisible .pick("Show", "Show members of type as nodes"); + JPanel hide = vizState.hideUnconnected.pick("Hide unconnected nodes", "Hide nodes without arcs"); + JPanel num = vizState.number .pick("Number nodes", "Attach atom number to node label as suffix"); + JPanel label= vizState.showAsLabel .pick("Show as labels", "Show members as labels"); + JPanel attr = vizState.showAsAttr .pick("Show in relation attributes", "Show set membership of endpoints when relation attributes are enabled"); + parent.add(makelabel(" Default Type and Set Settings:")); + parent.add(OurUtil.makeH(wcolor, 10, color, 7, style, 7, shape, 2, null)); + JPanel a=OurUtil.makeVL(wcolor, vis, num, label), b=OurUtil.makeVL(wcolor, hide, attr); + parent.add(OurUtil.makeHT(wcolor, 10, a, 10, b, 2, null)); + } + + //=============================================================================================================// + + /** Generates the "default relation settings" widgets, and add them to "parent". */ + private void createDefaultEdgeWidget(JPanel parent) { + JComboBox colorComboE = new OurCombobox(false, DotColor.valuesWithout(DotColor.WHITE), 110, 35, vizState.edgeColor.get(null)) { + private static final long serialVersionUID = 0; + @Override public String do_getText(Object value) { return ((DotColor)value).getDisplayedText(); } + @Override public Icon do_getIcon(Object value) { return ((DotColor)value).getIcon(vizState.getEdgePalette()); } + @Override public void do_changed(Object value) { vizState.edgeColor.put(null, (DotColor)value); } + }; + JComboBox outlineComboE = new OurCombobox(false, DotStyle.values(), 110, 35, vizState.edgeStyle.get(null)) { + private static final long serialVersionUID = 0; + @Override public String do_getText(Object value) { return ((DotStyle)value).getDisplayedText(); } + @Override public Icon do_getIcon(Object value) { return ((DotStyle)value).getIcon(); } + @Override public void do_changed(Object value) { vizState.edgeStyle.put(null, (DotStyle)value); } + }; + JPanel dispCBE = vizState.edgeVisible.pick("Show as arcs", "Show relations as arcs"); + JPanel mergeCBE = vizState.mergeArrows.pick("Merge arrows", "Merge opposing arrows of the same relation"); + JPanel constraintCBE = vizState.constraint .pick("Influence layout", "Whether this edge influences the graph layout"); + JPanel attrCBE = vizState.attribute .pick("Show as attributes", "Show relations as attributes on nodes"); + JPanel laybackCBE = vizState.layoutBack .pick("Layout backwards", "Layout graph as if arcs were reversed"); + parent.add(makelabel(" Default Relation Settings:")); + parent.add(OurUtil.makeH(wcolor, 10, colorComboE, 8, outlineComboE, 2, null)); + JPanel a=OurUtil.makeVL(wcolor, dispCBE, attrCBE, constraintCBE, 10), b=OurUtil.makeVL(wcolor, laybackCBE, mergeCBE); + parent.add(OurUtil.makeHT(wcolor, 10, a, 10, b, 2, null)); + } + + //=============================================================================================================// + + /** Convenient helper method that returns a description of an AlloyType (and what it extends). */ + private String typename(AlloyType type) { + if (type.equals(AlloyType.UNIV)) return "univ"; + AlloyType sup=vizState.getOriginalModel().getSuperType(type); + if (sup!=null && !sup.equals(AlloyType.UNIV)) return type.getName()+" extends "+sup.getName(); + return type.getName(); + } + + /** Generates a black JLabel for the given String. */ + private static JLabel makelabel(String label) { return OurUtil.label(label, OurUtil.getVizFont().deriveFont(Font.BOLD)); } + + /** Project over the given type if we are allowed to. */ + private void projectAlloyType(AlloyType type) { vizState.project(type); remakeAll(); } + + /** Unproject over the given type if it is currently projected. */ + private void deprojectAlloyType(AlloyType type) { vizState.deproject(type); remakeAll(); } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4viz/VizGUI.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4viz/VizGUI.java new file mode 100644 index 00000000..62b00c67 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4viz/VizGUI.java @@ -0,0 +1,1025 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4viz; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.ComponentEvent; +import java.awt.event.ComponentListener; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.prefs.Preferences; +import javax.swing.Box; +import javax.swing.Icon; +import javax.swing.JMenu; +import javax.swing.JMenuBar; +import javax.swing.JMenuItem; +import javax.swing.JPopupMenu; +import javax.swing.JScrollPane; +import javax.swing.JTextArea; +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JFrame; +import javax.swing.JPanel; +import javax.swing.JSplitPane; +import javax.swing.JToolBar; +import javax.swing.plaf.basic.BasicSplitPaneUI; +import edu.mit.csail.sdg.alloy4.Computer; +import edu.mit.csail.sdg.alloy4.ConstList; +import edu.mit.csail.sdg.alloy4.OurBorder; +import edu.mit.csail.sdg.alloy4.OurCheckbox; +import edu.mit.csail.sdg.alloy4.OurConsole; +import edu.mit.csail.sdg.alloy4.OurDialog; +import edu.mit.csail.sdg.alloy4.Runner; +import edu.mit.csail.sdg.alloy4.Util; +import edu.mit.csail.sdg.alloy4.OurUtil; +import edu.mit.csail.sdg.alloy4.Version; +import edu.mit.csail.sdg.alloy4.Util.IntPref; +import edu.mit.csail.sdg.alloy4.Util.StringPref; +import edu.mit.csail.sdg.alloy4graph.GraphViewer; +import static edu.mit.csail.sdg.alloy4.OurUtil.menu; +import static edu.mit.csail.sdg.alloy4.OurUtil.menuItem; + +/** GUI main window for the visualizer. + * + *

Thread Safety: Can be called only by the AWT event thread. + */ + +public final class VizGUI implements ComponentListener { + + /** The background color for the toolbar. */ + private static final Color background = new Color(0.9f, 0.9f, 0.9f); + + /** The icon for a "checked" menu item. */ + private static final Icon iconYes = OurUtil.loadIcon("images/menu1.gif"); + + /** The icon for an "unchecked" menu item. */ + private static final Icon iconNo = OurUtil.loadIcon("images/menu0.gif"); + + /** Whether the JVM should shutdown after the last file is closed. */ + private final boolean standalone; + + /** The current display mode. */ + private VisualizerMode currentMode = VisualizerMode.get(); + + /** The JFrame for the main GUI window; or null if we intend to display the graph inside a user-given JPanel instead. */ + private final JFrame frame; + + /** The toolbar. */ + private final JToolBar toolbar; + + /** The projection popup menu. */ + private final JPopupMenu projectionPopup; + + /** The buttons on the toolbar. */ + private final JButton projectionButton, openSettingsButton, closeSettingsButton, + magicLayout, loadSettingsButton, saveSettingsButton, saveAsSettingsButton, + resetSettingsButton, updateSettingsButton, openEvaluatorButton, closeEvaluatorButton, enumerateButton, + vizButton, treeButton, txtButton/*, dotButton, xmlButton*/; + + /** This list must contain all the display mode buttons (that is, vizButton, xmlButton...) */ + private final List solutionButtons = new ArrayList(); + + /** The "theme" menu. */ + private final JMenu thememenu; + + /** The "window" menu. */ + private final JMenu windowmenu; + + /** The "show next" menu item. */ + private final JMenuItem enumerateMenu; + + /** Current font size. */ + private int fontSize=12; + + /** 0: theme and evaluator are both invisible; 1: theme is visible; 2: evaluator is visible. */ + private int settingsOpen=0; + + /** The current instance and visualization settings; null if none is loaded. */ + private VizState myState=null; + + /** Returns the current visualization settings (and you can call getOriginalInstance() on it to get the current instance). + * If you make changes to the state, you should call doApply() on the VizGUI object to refresh the screen. + */ + public VizState getVizState() { return myState; } + + /** The customization panel to the left; null if it is not yet loaded. */ + private VizCustomizationPanel myCustomPanel=null; + + /** The evaluator panel to the left; null if it is not yet loaded. */ + private OurConsole myEvaluatorPanel=null; + + /** The graphical panel to the right; null if it is not yet loaded. */ + private VizGraphPanel myGraphPanel=null; + + /** The splitpane between the customization panel and the graph panel. */ + private final JSplitPane splitpane; + + /** The tree or graph or text being displayed on the right hand side. */ + private JComponent content=null; + + /** Returns the JSplitPane containing the customization/evaluator panel in the left and the graph on the right. */ + public JSplitPane getPanel() { return splitpane; } + + /** The last known divider position between the customization panel and the graph panel. */ + private int lastDividerPosition=0; + + /** If nonnull, you can pass in an expression to be evaluated. + * If it throws an exception, that means an error has occurred. + */ + private final Computer evaluator; + + /** If nonnull, you can pass in an XML file to find the next solution. */ + private final Computer enumerator; + + //==============================================================================================// + + /** The current theme file; "" if there is no theme file loaded. */ + private String thmFileName=""; + + /** Returns the current THM filename; "" if no theme file is currently loaded. */ + public String getThemeFilename() { return thmFileName; } + + //==============================================================================================// + + /** The current XML file; "" if there is no XML file loaded. */ + private String xmlFileName=""; + + /** Returns the current XML filename; "" if no file is currently loaded. */ + public String getXMLfilename() { return xmlFileName; } + + //==============================================================================================// + + /** The list of XML files loaded in this session so far. */ + private final List xmlLoaded=new ArrayList(); + + /** Return the list of XML files loaded in this session so far. */ + public ConstList getInstances() { return ConstList.make(xmlLoaded); } + + //==============================================================================================// + + /** This maps each XML filename to a descriptive title. */ + private Map xml2title = new LinkedHashMap(); + + /** Returns a short descriptive title associated with an XML file. */ + public String getInstanceTitle(String xmlFileName) { + String answer = xml2title.get(Util.canon(xmlFileName)); + return (answer==null) ? "(unknown)" : answer; + } + + //==============================================================================================// + + /** Add a vertical divider to the toolbar. */ + private void addDivider() { + JPanel divider = OurUtil.makeH(new Dimension(1, 40), Color.LIGHT_GRAY); + divider.setAlignmentY(0.5f); + if (!Util.onMac()) toolbar.add(OurUtil.makeH(5,background)); else toolbar.add(OurUtil.makeH(5)); + toolbar.add(divider); + if (!Util.onMac()) toolbar.add(OurUtil.makeH(5,background)); else toolbar.add(OurUtil.makeH(5)); + } + + //======== The Preferences ======================================================================================// + //======== Note: you must make sure each preference has a unique key ============================================// + + /** This enum defines the set of possible visualizer modes. */ + private enum VisualizerMode { + /** Visualize using graphviz's dot. */ Viz("graphviz"), +// /** See the DOT content. */ DOT("dot"), +// /** See the XML content. */ XML("xml"), + /** See the instance as text. */ TEXT("txt"), + /** See the instance as a tree. */ Tree("tree"); + /** This is a unique String for this value; it should be kept consistent in future versions. */ + private final String id; + /** Constructs a new VisualizerMode value with the given id. */ + private VisualizerMode(String id) { this.id=id; } + /** Given an id, return the enum value corresponding to it (if there's no match, then return Viz). */ + private static VisualizerMode parse(String id) { + for(VisualizerMode vm: values()) if (vm.id.equals(id)) return vm; + return Viz; + } + /** Saves this value into the Java preference object. */ + public void set() { Preferences.userNodeForPackage(Util.class).put("VisualizerMode",id); } + /** Reads the current value of the Java preference object (if it's not set, then return Viz). */ + public static VisualizerMode get() { return parse(Preferences.userNodeForPackage(Util.class).get("VisualizerMode","")); } + }; + + /** The latest X corrdinate of the Alloy Visualizer window. */ + private static final IntPref VizX = new IntPref("VizX",0,-1,65535); + + /** The latest Y corrdinate of the Alloy Visualizer window. */ + private static final IntPref VizY = new IntPref("VizY",0,-1,65535); + + /** The latest width of the Alloy Visualizer window. */ + private static final IntPref VizWidth = new IntPref("VizWidth",0,-1,65535); + + /** The latest height of the Alloy Visualizer window. */ + private static final IntPref VizHeight = new IntPref("VizHeight",0,-1,65535); + + /** The first file in Alloy Visualizer's "open recent theme" list. */ + private static final StringPref Theme0 = new StringPref("Theme0"); + + /** The second file in Alloy Visualizer's "open recent theme" list. */ + private static final StringPref Theme1 = new StringPref("Theme1"); + + /** The third file in Alloy Visualizer's "open recent theme" list. */ + private static final StringPref Theme2 = new StringPref("Theme2"); + + /** The fourth file in Alloy Visualizer's "open recent theme" list. */ + private static final StringPref Theme3 = new StringPref("Theme3"); + + //==============================================================================================// + + /** If true, that means the event handlers should return a Runner encapsulating them, rather than perform the actual work. */ + private boolean wrap = false; + + /** Wraps the calling method into a Runnable whose run() will call the calling method with (false) as the only argument. */ + private Runner wrapMe() { + final String name; + try { throw new Exception(); } catch(Exception ex) { name = ex.getStackTrace()[1].getMethodName(); } + Method[] methods = getClass().getDeclaredMethods(); + Method m=null; + for(int i=0; i Note: if standalone==false and xmlFileName.length()==0, then we will initially hide the window. + */ + public VizGUI(boolean standalone, String xmlFileName, JMenu windowmenu) { + this(standalone, xmlFileName, windowmenu, null, null); + } + + /** Creates a new visualization GUI window; this method can only be called by the AWT event thread. + * @param standalone - whether the JVM should shutdown after the last file is closed + * @param xmlFileName - the filename of the incoming XML file; "" if there's no file to open + * @param windowmenu - if standalone==false and windowmenu!=null, then this will be added as a menu on the menubar + * @param enumerator - if it's not null, it provides solution enumeration ability + * @param evaluator - if it's not null, it provides solution evaluation ability + * + *

Note: if standalone==false and xmlFileName.length()==0, then we will initially hide the window. + */ + public VizGUI(boolean standalone, String xmlFileName, JMenu windowmenu, Computer enumerator, Computer evaluator) { + this(standalone, xmlFileName, windowmenu, enumerator, evaluator, true); + } + + /** Creates a new visualization GUI window; this method can only be called by the AWT event thread. + * @param standalone - whether the JVM should shutdown after the last file is closed + * @param xmlFileName - the filename of the incoming XML file; "" if there's no file to open + * @param windowmenu - if standalone==false and windowmenu!=null, then this will be added as a menu on the menubar + * @param enumerator - if it's not null, it provides solution enumeration ability + * @param evaluator - if it's not null, it provides solution evaluation ability + * @param makeWindow - if false, then we will only construct the JSplitPane, without making the window + * + *

Note: if standalone==false and xmlFileName.length()==0 and makeWindow==true, then we will initially hide the window. + */ + public VizGUI(boolean standalone, String xmlFileName, JMenu windowmenu, Computer enumerator, Computer evaluator, boolean makeWindow) { + + this.enumerator = enumerator; + this.standalone = standalone; + this.evaluator = evaluator; + this.frame = makeWindow ? new JFrame("Alloy Visualizer") : null; + + // Figure out the desired x, y, width, and height + int screenWidth=OurUtil.getScreenWidth(), screenHeight=OurUtil.getScreenHeight(); + int width=VizWidth.get(); + if (width<0) width=screenWidth-150; else if (width<100) width=100; + if (width>screenWidth) width=screenWidth; + int height=VizHeight.get(); + if (height<0) height=screenHeight-150; else if (height<100) height=100; + if (height>screenHeight) height=screenHeight; + int x=VizX.get(); if (x<0 || x>screenWidth-10) x=0; + int y=VizY.get(); if (y<0 || y>screenHeight-10) y=0; + + // Create the menubar + JMenuBar mb = new JMenuBar(); + try { + wrap = true; + JMenu fileMenu = menu(mb, "&File", null); + menuItem(fileMenu, "Open...", 'O', 'O', doLoad()); + JMenu exportMenu = menu(null, "&Export To", null); + menuItem(exportMenu, "Dot...", 'D', 'D', doExportDot()); + menuItem(exportMenu, "XML...", 'X', 'X', doExportXml()); + fileMenu.add(exportMenu); + menuItem(fileMenu, "Close", 'W', 'W', doClose()); + if (standalone) menuItem(fileMenu, "Quit", 'Q', 'Q', doCloseAll()); else menuItem(fileMenu, "Close All", 'A', doCloseAll()); + JMenu instanceMenu = menu(mb, "&Instance", null); + enumerateMenu = menuItem(instanceMenu, "Show Next Solution", 'N', 'N', doNext()); + thememenu = menu(mb, "&Theme", doRefreshTheme()); + if (standalone || windowmenu==null) windowmenu = menu(mb, "&Window", doRefreshWindow()); + this.windowmenu = windowmenu; + } finally { + wrap = false; + } + mb.add(windowmenu); + thememenu.setEnabled(false); + windowmenu.setEnabled(false); + if (frame!=null) frame.setJMenuBar(mb); + + // Create the toolbar + projectionPopup = new JPopupMenu(); + projectionButton = new JButton("Projection: none"); + projectionButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + repopulateProjectionPopup(); + if (projectionPopup.getComponentCount()>0) projectionPopup.show(projectionButton, 10, 10); + } + }); + repopulateProjectionPopup(); + toolbar = new JToolBar(); + toolbar.setVisible(false); + toolbar.setFloatable(false); + toolbar.setBorder(null); + if (!Util.onMac()) toolbar.setBackground(background); + try { + wrap = true; + vizButton=makeSolutionButton("Viz", "Show Visualization", "images/24_graph.gif", doShowViz()); +// dotButton=makeSolutionButton("Dot", "Show the Dot File for the Graph", "images/24_plaintext.gif", doShowDot()); +// xmlButton=makeSolutionButton("XML", "Show XML", "images/24_plaintext.gif", doShowXML()); + txtButton=makeSolutionButton("Txt", "Show the textual output for the Graph", "images/24_plaintext.gif", doShowTxt()); + treeButton=makeSolutionButton("Tree", "Show Tree", "images/24_texttree.gif", doShowTree()); + if (frame!=null) addDivider(); + toolbar.add(closeSettingsButton=OurUtil.button("Close", "Close the theme customization panel", "images/24_settings_close2.gif", doCloseThemePanel())); + toolbar.add(updateSettingsButton=OurUtil.button("Apply", "Apply the changes to the current theme", "images/24_settings_apply2.gif", doApply())); + toolbar.add(openSettingsButton=OurUtil.button("Theme", "Open the theme customization panel", "images/24_settings.gif", doOpenThemePanel())); + toolbar.add(magicLayout=OurUtil.button("Magic Layout", "Automatic theme customization (will reset current theme)", "images/24_settings_apply2.gif", doMagicLayout())); + toolbar.add(openEvaluatorButton=OurUtil.button("Evaluator", "Open the evaluator", "images/24_settings.gif", doOpenEvalPanel())); + toolbar.add(closeEvaluatorButton=OurUtil.button("Close Evaluator", "Close the evaluator", "images/24_settings_close2.gif", doCloseEvalPanel())); + toolbar.add(enumerateButton=OurUtil.button("Next", "Show the next solution", "images/24_history.gif", doNext())); + toolbar.add(projectionButton); + toolbar.add(loadSettingsButton=OurUtil.button("Load", "Load the theme customization from a theme file", "images/24_open.gif", doLoadTheme())); + toolbar.add(saveSettingsButton=OurUtil.button("Save", "Save the current theme customization", "images/24_save.gif", doSaveTheme())); + toolbar.add(saveAsSettingsButton=OurUtil.button("Save As", "Save the current theme customization as a new theme file", "images/24_save.gif", doSaveThemeAs())); + toolbar.add(resetSettingsButton=OurUtil.button("Reset", "Reset the theme customization", "images/24_settings_close2.gif", doResetTheme())); + } finally { + wrap = false; + } + settingsOpen=0; + + // Create the horizontal split pane + splitpane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT); + splitpane.setOneTouchExpandable(false); + splitpane.setResizeWeight(0.); + splitpane.setContinuousLayout(true); + splitpane.setBorder(null); + ((BasicSplitPaneUI)(splitpane.getUI())).getDivider().setBorder(new OurBorder(false,true,false,false)); + + // Display the window, then proceed to load the input file + if (frame!=null) { + frame.pack(); + if (!Util.onMac() && !Util.onWindows()) { + // many Window managers do not respect ICCCM2; this should help avoid the Title Bar being shifted "off screen" + if (x<30) { if (x<0) x=0; width=width-(30-x); x=30; } + if (y<30) { if (y<0) y=0; height=height-(30-y); y=30; } + if (width<100) width=100; + if (height<100) height=100; + } + frame.setSize(width, height); + frame.setLocation(x, y); + frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); + try { wrap=true; frame.addWindowListener(doClose()); } finally { wrap=false; } + frame.addComponentListener(this); + } + if (xmlFileName.length()>0) doLoadInstance(xmlFileName); + } + + /** Invoked when the Visualizationwindow is resized. */ + public void componentResized(ComponentEvent e) { + componentMoved(e); + } + + /** Invoked when the Visualizationwindow is moved. */ + public void componentMoved(ComponentEvent e) { + if (frame!=null) { + VizWidth.set(frame.getWidth()); + VizHeight.set(frame.getHeight()); + VizX.set(frame.getX()); + VizY.set(frame.getY()); + } + } + + /** Invoked when the Visualizationwindow is shown. */ + public void componentShown(ComponentEvent e) { } + + /** Invoked when the Visualizationwindow is hidden. */ + public void componentHidden(ComponentEvent e) { } + + /** Helper method that repopulates the Porjection popup menu. */ + private void repopulateProjectionPopup() { + int num=0; + String label="Projection: none"; + if (myState==null) { projectionButton.setEnabled(false); return; } + projectionButton.setEnabled(true); + projectionPopup.removeAll(); + final Set projected = myState.getProjectedTypes(); + for(final AlloyType t: myState.getOriginalModel().getTypes()) if (myState.canProject(t)) { + final boolean on = projected.contains(t); + final JMenuItem m = new JMenuItem(t.getName(), on ? OurCheckbox.ON : OurCheckbox.OFF); + m.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + if (on) myState.deproject(t); else myState.project(t); + updateDisplay(); + } + }); + projectionPopup.add(m); + if (on) { num++; if (num==1) label="Projected over "+t.getName(); } + } + projectionButton.setText(num>1 ? ("Projected over "+num+" sigs") : label); + } + + /** Helper method that refreshes the right-side visualization panel with the latest settings. */ + private void updateDisplay() { + if (myState==null) return; + // First, update the toolbar + currentMode.set(); + for(JButton button:solutionButtons) button.setEnabled(settingsOpen!=1); + switch (currentMode) { + case Tree: treeButton.setEnabled(false); break; + case TEXT: txtButton.setEnabled(false); break; +// case XML: xmlButton.setEnabled(false); break; +// case DOT: dotButton.setEnabled(false); break; + default: vizButton.setEnabled(false); + } + final boolean isMeta = myState.getOriginalInstance().isMetamodel; + vizButton.setVisible(frame!=null); + treeButton.setVisible(frame!=null); + txtButton.setVisible(frame!=null); +// dotButton.setVisible(frame!=null); +// xmlButton.setVisible(frame!=null); + magicLayout.setVisible((settingsOpen==0 || settingsOpen==1) && currentMode==VisualizerMode.Viz); + projectionButton.setVisible((settingsOpen==0 || settingsOpen==1) && currentMode==VisualizerMode.Viz); + openSettingsButton.setVisible( settingsOpen==0 && currentMode==VisualizerMode.Viz); + loadSettingsButton.setVisible(frame==null && settingsOpen==1 && currentMode==VisualizerMode.Viz); + saveSettingsButton.setVisible(frame==null && settingsOpen==1 && currentMode==VisualizerMode.Viz); + saveAsSettingsButton.setVisible(frame==null && settingsOpen==1 && currentMode==VisualizerMode.Viz); + resetSettingsButton.setVisible(frame==null && settingsOpen==1 && currentMode==VisualizerMode.Viz); + closeSettingsButton.setVisible(settingsOpen==1 && currentMode==VisualizerMode.Viz); + updateSettingsButton.setVisible(settingsOpen==1 && currentMode==VisualizerMode.Viz); + openEvaluatorButton.setVisible(!isMeta && settingsOpen==0 && evaluator!=null); + closeEvaluatorButton.setVisible(!isMeta && settingsOpen==2 && evaluator!=null); + enumerateMenu.setEnabled(!isMeta && settingsOpen==0 && enumerator!=null); + enumerateButton.setVisible(!isMeta && settingsOpen==0 && enumerator!=null); + toolbar.setVisible(true); + // Now, generate the graph or tree or textarea that we want to display on the right + if (frame!=null) frame.setTitle(makeVizTitle()); + switch (currentMode) { + case Tree: { + final VizTree t = new VizTree(myState.getOriginalInstance().originalA4, makeVizTitle(), fontSize); + final JScrollPane scroll = OurUtil.scrollpane(t, Color.BLACK, Color.WHITE, new OurBorder(true, false, true, false)); + scroll.addFocusListener(new FocusListener() { + public final void focusGained(FocusEvent e) { t.requestFocusInWindow(); } + public final void focusLost(FocusEvent e) { } + }); + content = scroll; + break; + } + case TEXT: { + String textualOutput = myState.getOriginalInstance().originalA4.toString(); + content = getTextComponent(textualOutput); + break; + } +// case XML: { +// content=getTextComponent(xmlFileName); +// break; +// } + default: { + if (myGraphPanel==null) { + myGraphPanel=new VizGraphPanel(myState, false); + } else { + myGraphPanel.seeDot(false); + myGraphPanel.remakeAll(); + } + } + content=myGraphPanel; + } + // Now that we've re-constructed "content", let's set its font size + if (currentMode != VisualizerMode.Tree) { + content.setFont(OurUtil.getVizFont().deriveFont((float)fontSize)); + content.invalidate(); + content.repaint(); + content.validate(); + } + // Now, display them! + final Box instanceTopBox = Box.createHorizontalBox(); + instanceTopBox.add(toolbar); + final JPanel instanceArea = new JPanel(new BorderLayout()); + instanceArea.add(instanceTopBox, BorderLayout.NORTH); + instanceArea.add(content, BorderLayout.CENTER); + instanceArea.setVisible(true); + if (!Util.onMac()) { instanceTopBox.setBackground(background); instanceArea.setBackground(background); } + JComponent left = null; + if (settingsOpen==1) { + if (myCustomPanel==null) myCustomPanel = new VizCustomizationPanel(splitpane,myState); else myCustomPanel.remakeAll(); + left = myCustomPanel; + } else if (settingsOpen>1) { + if (myEvaluatorPanel==null) + myEvaluatorPanel = new OurConsole(evaluator, true, + "The ", true, "Alloy Evaluator ", false, + "allows you to type\nin Alloy expressions and see their values.\nFor example, ", true, + "univ", false, " shows the list of all atoms.\n(You can press UP and DOWN to recall old inputs).\n"); + try { evaluator.compute(new File(xmlFileName)); } catch(Exception ex) { } // exception should not happen + left = myEvaluatorPanel; + left.setBorder(new OurBorder(false, false, false, false)); + } + if (frame!=null && frame.getContentPane()==splitpane) lastDividerPosition=splitpane.getDividerLocation(); + splitpane.setRightComponent(instanceArea); + splitpane.setLeftComponent(left); + if (left!=null) { + Dimension dim = left.getPreferredSize(); + if (lastDividerPosition<50 && frame!=null) lastDividerPosition = frame.getWidth()/2; + if (lastDividerPosition400) lastDividerPosition = 400; + splitpane.setDividerLocation(lastDividerPosition); + } + if (frame!=null) frame.setContentPane(splitpane); + if (settingsOpen!=2) content.requestFocusInWindow(); else myEvaluatorPanel.requestFocusInWindow(); + repopulateProjectionPopup(); + if (frame!=null) frame.validate(); else splitpane.validate(); + } + + /** Helper method that creates a button and add it to both the "SolutionButtons" list, as well as the toolbar. */ + private JButton makeSolutionButton(String label, String toolTip, String image, ActionListener mode) { + JButton button = OurUtil.button(label, toolTip, image, mode); + solutionButtons.add(button); + toolbar.add(button); + return button; + } + + /** Helper method that returns a concise description of the instance currently being displayed. */ + private String makeVizTitle() { + String filename = (myState!=null ? myState.getOriginalInstance().filename : ""); + String commandname = (myState!=null ? myState.getOriginalInstance().commandname : ""); + int i=filename.lastIndexOf('/'); + if (i>=0) filename=filename.substring(i+1); + i=filename.lastIndexOf('\\'); + if (i>=0) filename=filename.substring(i+1); + int n=filename.length(); + if (n>4 && filename.substring(n-4).equalsIgnoreCase(".als")) filename=filename.substring(0,n-4); + if (filename.length()>0) return "("+filename+") "+commandname; else return commandname; + } + + /** Helper method that inserts "filename" into the "recently opened THEME file list". */ + private void addThemeHistory(String filename) { + String name0=Theme0.get(), name1=Theme1.get(), name2=Theme2.get(); + if (name0.equals(filename)) return; else {Theme0.set(filename); Theme1.set(name0);} + if (name1.equals(filename)) return; else Theme2.set(name1); + if (name2.equals(filename)) return; else Theme3.set(name2); + } + + /** Helper method returns a JTextArea containing the given text. */ + private JComponent getTextComponent(String text) { + final JTextArea ta = OurUtil.textarea(text, 10, 10, false, true); + final JScrollPane ans = new JScrollPane(ta, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED) { + private static final long serialVersionUID = 0; + @Override public void setFont(Font font) { ta.setFont(font); } + }; + ans.setBorder(new OurBorder(true, false, true, false)); + return ans; + } + +// /** Helper method that reads a file and then return a JTextArea containing it. */ +// private JComponent getTextComponentFromFile(String filename) { +// String text = ""; +// try { text="\n"+Util.readAll(filename); } catch(IOException ex) { text="# Error reading from "+filename; } +// return getTextComponent(text); +// } + + /** Returns the GraphViewer that contains the graph; can be null if the graph hasn't been loaded yet. */ + public GraphViewer getViewer() { + if (null == myGraphPanel) return null; + return myGraphPanel.alloyGetViewer(); + } + + /** Load the XML instance. */ + public void loadXML(final String fileName, boolean forcefully) { + final String xmlFileName = Util.canon(fileName); + File f = new File(xmlFileName); + if (forcefully || !xmlFileName.equals(this.xmlFileName)) { + AlloyInstance myInstance; + try { + if (!f.exists()) throw new IOException("File " + xmlFileName + " does not exist."); + myInstance = StaticInstanceReader.parseInstance(f); + } catch (Throwable e) { + xmlLoaded.remove(fileName); + xmlLoaded.remove(xmlFileName); + OurDialog.alert("Cannot read or parse Alloy instance: "+xmlFileName+"\n\nError: "+e.getMessage()); + if (xmlLoaded.size()>0) { loadXML(xmlLoaded.get(xmlLoaded.size()-1), false); return; } + doCloseAll(); + return; + } + if (myState==null) myState=new VizState(myInstance); else myState.loadInstance(myInstance); + repopulateProjectionPopup(); + xml2title.put(xmlFileName, makeVizTitle()); + this.xmlFileName = xmlFileName; + } + if (!xmlLoaded.contains(xmlFileName)) xmlLoaded.add(xmlFileName); + toolbar.setEnabled(true); + settingsOpen=0; + thememenu.setEnabled(true); + windowmenu.setEnabled(true); + if (frame!=null) { + frame.setVisible(true); + frame.setTitle("Alloy Visualizer "+Version.version()+" loading... Please wait..."); + OurUtil.show(frame); + } + updateDisplay(); + } + + /** This method loads a specific theme file. */ + public boolean loadThemeFile(String filename) { + if (myState==null) return false; // Can only load if there is a VizState loaded + filename = Util.canon(filename); + try { + myState.loadPaletteXML(filename); + } catch (IOException ex) { + OurDialog.alert("Error: " + ex.getMessage()); + return false; + } + repopulateProjectionPopup(); + if (myCustomPanel!=null) myCustomPanel.remakeAll(); + if (myGraphPanel!=null) myGraphPanel.remakeAll(); + addThemeHistory(filename); + thmFileName=filename; + updateDisplay(); + return true; + } + + /** This method saves a specific current theme (if filename==null, it asks the user); returns true if it succeeded. */ + public boolean saveThemeFile(String filename) { + if (myState==null) return false; // Can only save if there is a VizState loaded + if (filename==null) { + File file=OurDialog.askFile(false, null, ".thm", ".thm theme files"); + if (file==null) return false; + if (file.exists()) if (!OurDialog.askOverwrite(Util.canon(file.getPath()))) return false; + Util.setCurrentDirectory(file.getParentFile()); + filename = file.getPath(); + } + filename = Util.canon(filename); + try { + myState.savePaletteXML(filename); + filename = Util.canon(filename); // Since the canon name may have changed + addThemeHistory(filename); + } catch (Throwable er) { + OurDialog.alert("Error saving the theme.\n\nError: " + er.getMessage()); + return false; + } + thmFileName = filename; + return true; + } + + //========================================= EVENTS ============================================================================================ + + /** This method changes the font size for everything (except the graph) */ + public void doSetFontSize(int fontSize) { + this.fontSize = fontSize; + if (!(content instanceof VizGraphPanel)) updateDisplay(); else content.setFont(OurUtil.getVizFont().deriveFont((float)fontSize)); + } + + /** This method asks the user for a new XML instance file to load. */ + private Runner doLoad() { + if (wrap) return wrapMe(); + File file=OurDialog.askFile(true, null, ".xml", ".xml instance files"); + if (file==null) return null; + Util.setCurrentDirectory(file.getParentFile()); + loadXML(file.getPath(), true); + return null; + } + + /** This method loads a new XML instance file if it's not the current file. */ + private Runner doLoadInstance(String fileName) { + if (!wrap) loadXML(fileName, false); + return wrapMe(fileName); + } + + /** This method closes the current instance; if there are previously loaded files, we will load one of them; + * otherwise, this window will set itself as invisible (if not in standalone mode), + * or it will terminate the entire application (if in standalone mode). + */ + private Runner doClose() { + if (wrap) return wrapMe(); + xmlLoaded.remove(xmlFileName); + if (xmlLoaded.size()>0) { doLoadInstance(xmlLoaded.get(xmlLoaded.size()-1)); return null; } + if (standalone) System.exit(0); else if (frame!=null) frame.setVisible(false); + return null; + } + + /** This method closes every XML file. + * If in standalone mode, the JVM will then shutdown, otherwise it will just set the window invisible. + */ + private Runner doCloseAll() { + if (wrap) return wrapMe(); + xmlLoaded.clear(); + xmlFileName=""; + if (standalone) System.exit(0); else if (frame!=null) frame.setVisible(false); + return null; + } + + /** This method refreshes the "theme" menu. */ + private Runner doRefreshTheme() { + if (wrap) return wrapMe(); + String defaultTheme = System.getProperty("alloy.theme0"); + thememenu.removeAll(); + try { + wrap=true; + menuItem(thememenu, "Load Theme...", 'L', doLoadTheme()); + if (defaultTheme!=null && defaultTheme.length()>0 && (new File(defaultTheme)).isDirectory()) + menuItem(thememenu, "Load Sample Theme...", 'B', doLoadSampleTheme()); + menuItem(thememenu, "Save Theme", 'S', doSaveTheme()); + menuItem(thememenu, "Save Theme As...", 'A', doSaveThemeAs()); + menuItem(thememenu, "Reset Theme", 'R', doResetTheme()); + } finally { + wrap=false; + } + return null; + } + + /** This method asks the user for a new theme file to load. */ + private Runner doLoadTheme() { + if (wrap) return wrapMe(); + String defaultTheme=System.getProperty("alloy.theme0"); + if (defaultTheme==null) defaultTheme=""; + if (myState==null) return null; // Can only load if there is a VizState loaded + if (myState.changedSinceLastSave()) { + char opt = OurDialog.askSaveDiscardCancel("The current theme"); + if (opt=='c') return null; + if (opt=='s' && !saveThemeFile(thmFileName.length()==0 ? null : thmFileName)) return null; + } + File file=OurDialog.askFile(true, null, ".thm", ".thm theme files"); + if (file!=null) { Util.setCurrentDirectory(file.getParentFile()); loadThemeFile(file.getPath()); } + return null; + } + + /** This method asks the user for a new theme file (from the default Alloy4 distribution) to load. */ + private Runner doLoadSampleTheme() { + if (wrap) return wrapMe(); + String defaultTheme=System.getProperty("alloy.theme0"); + if (defaultTheme==null) defaultTheme=""; + if (myState==null) return null; // Can only load if there is a VizState loaded + if (myState.changedSinceLastSave()) { + char opt = OurDialog.askSaveDiscardCancel("The current theme"); + if (opt=='c') return null; + if (opt=='s' && !saveThemeFile(thmFileName.length()==0 ? null : thmFileName)) return null; + } + File file=OurDialog.askFile(true, defaultTheme, ".thm", ".thm theme files"); + if (file!=null) loadThemeFile(file.getPath()); + return null; + } + + /** This method saves the current theme. */ + private Runner doSaveTheme() { + if (!wrap) saveThemeFile(thmFileName.length()==0 ? null : thmFileName); + return wrapMe(); + } + + /** This method saves the current theme to a new ".thm" file. */ + private Runner doSaveThemeAs() { + if (wrap) return wrapMe(); + File file=OurDialog.askFile(false, null, ".thm", ".thm theme files"); + if (file==null) return null; + if (file.exists()) if (!OurDialog.askOverwrite(Util.canon(file.getPath()))) return null; + Util.setCurrentDirectory(file.getParentFile()); + saveThemeFile(file.getPath()); + return null; + } + + private Runner doExportDot() { + if (wrap) return wrapMe(); + File file=OurDialog.askFile(false, null, ".dot", ".dot graph files"); + if (file==null) return null; + if (file.exists()) if (!OurDialog.askOverwrite(Util.canon(file.getPath()))) return null; + Util.setCurrentDirectory(file.getParentFile()); + String filename = Util.canon(file.getPath()); + try { + Util.writeAll(filename, myGraphPanel.toDot()); + } catch (Throwable er) { + OurDialog.alert("Error saving the theme.\n\nError: " + er.getMessage()); + } + return null; + } + + private Runner doExportXml() { + if (wrap) return wrapMe(); + File file=OurDialog.askFile(false, null, ".xml", ".xml XML files"); + if (file==null) return null; + if (file.exists()) if (!OurDialog.askOverwrite(Util.canon(file.getPath()))) return null; + Util.setCurrentDirectory(file.getParentFile()); + String filename = Util.canon(file.getPath()); + try { + Util.writeAll(filename, Util.readAll(xmlFileName)); + } catch (Throwable er) { + OurDialog.alert("Error saving XML instance.\n\nError: " + er.getMessage()); + } + return null; + } + + /** This method resets the current theme. */ + private Runner doResetTheme() { + if (wrap) return wrapMe(); + if (myState==null) return null; + if (!OurDialog.yesno("Are you sure you wish to clear all your customizations?", "Yes, clear them", "No, keep them")) return null; + myState.resetTheme(); + repopulateProjectionPopup(); + if (myCustomPanel!=null) myCustomPanel.remakeAll(); + if (myGraphPanel!=null) myGraphPanel.remakeAll(); + thmFileName=""; + updateDisplay(); + return null; + } + + /** This method modifies the theme using a set of heuristics. */ + private Runner doMagicLayout() { + if (wrap) return wrapMe(); + if (myState==null) return null; + if (!OurDialog.yesno("This will clear your original customizations. Are you sure?", "Yes, clear them", "No, keep them")) return null; + myState.resetTheme(); + try { MagicLayout.magic(myState); MagicColor.magic(myState); } catch(Throwable ex) { } + repopulateProjectionPopup(); + if (myCustomPanel!=null) myCustomPanel.remakeAll(); + if (myGraphPanel!=null) myGraphPanel.remakeAll(); + updateDisplay(); + return null; + } + + /** This method refreshes the "window" menu. */ + private Runner doRefreshWindow() { + if (wrap) return wrapMe(); + windowmenu.removeAll(); + try { + wrap=true; + for(final String f:getInstances()) { + JMenuItem it = new JMenuItem("Instance: "+getInstanceTitle(f), null); + it.setIcon(f.equals(getXMLfilename())?iconYes:iconNo); + it.addActionListener(doLoadInstance(f)); + windowmenu.add(it); + } + } finally { + wrap=false; + } + return null; + } + + /** This method inserts "Minimize" and "Maximize" entries into a JMenu. */ + public void addMinMaxActions(JMenu menu) { + try { + wrap=true; + menuItem(menu, "Minimize", 'M', doMinimize(), iconNo); + menuItem(menu, "Zoom", doZoom(), iconNo); + } finally { + wrap=false; + } + } + + /** This method minimizes the window. */ + private Runner doMinimize() { + if (!wrap && frame!=null) OurUtil.minimize(frame); + return wrapMe(); + } + + /** This method alternatingly maximizes or restores the window. */ + private Runner doZoom() { + if (!wrap && frame!=null) OurUtil.zoom(frame); + return wrapMe(); + } + + /** This method attempts to derive the next satisfying instance. */ + private Runner doNext() { + if (wrap) return wrapMe(); + if (settingsOpen!=0) return null; + if (xmlFileName.length()==0) { + OurDialog.alert("Cannot display the next solution since no instance is currently loaded."); + } else if (enumerator==null) { + OurDialog.alert("Cannot display the next solution since the analysis engine is not loaded with the visualizer."); + } else { + try { enumerator.compute(xmlFileName); } catch(Throwable ex) { OurDialog.alert(ex.getMessage()); } + } + return null; + } + + /** This method updates the graph with the current theme customization. */ + private Runner doApply() { + if (!wrap) updateDisplay(); + return wrapMe(); + } + + /** This method opens the theme customization panel if closed. */ + private Runner doOpenThemePanel() { + if (!wrap) { settingsOpen=1; updateDisplay(); } + return wrapMe(); + } + + /** This method closes the theme customization panel if open. */ + private Runner doCloseThemePanel() { + if (!wrap) { settingsOpen=0; updateDisplay(); } + return wrapMe(); + } + + /** This method opens the evaluator panel if closed. */ + private Runner doOpenEvalPanel() { + if (!wrap) { settingsOpen=2; updateDisplay(); } + return wrapMe(); + } + + /** This method closes the evaluator panel if open. */ + private Runner doCloseEvalPanel() { + if (!wrap) { settingsOpen=0; updateDisplay(); } + return wrapMe(); + } + + /** This method changes the display mode to show the instance as a graph (the return value is always null). */ + public Runner doShowViz() { + if (!wrap) { currentMode=VisualizerMode.Viz; updateDisplay(); return null; } + return wrapMe(); + } + + /** This method changes the display mode to show the instance as a tree (the return value is always null). */ + public Runner doShowTree() { + if (!wrap) { currentMode=VisualizerMode.Tree; updateDisplay(); return null; } + return wrapMe(); + } + + /** + * This method changes the display mode to show the equivalent dot text (the + * return value is always null). + */ + public Runner doShowTxt() { + if (!wrap) { currentMode = VisualizerMode.TEXT; updateDisplay(); return null; } + return wrapMe(); + } + +// /** This method changes the display mode to show the equivalent dot text (the return value is always null). */ +// public Runner doShowDot() { +// if (!wrap) { currentMode=VisualizerMode.DOT; updateDisplay(); return null; } +// return wrapMe(); +// } +// +// /** This method changes the display mode to show the instance as XML (the return value is always null). */ +// public Runner doShowXML() { +// if (!wrap) { currentMode=VisualizerMode.XML; updateDisplay(); return null; } +// return wrapMe(); +// } + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4viz/VizGraphPanel.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4viz/VizGraphPanel.java new file mode 100644 index 00000000..1d96dc91 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4viz/VizGraphPanel.java @@ -0,0 +1,366 @@ +/* + * Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4viz; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.Graphics; +import java.awt.GridLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.AdjustmentEvent; +import java.awt.event.AdjustmentListener; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JComboBox; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JSplitPane; +import javax.swing.JTextArea; +import javax.swing.SwingConstants; +import javax.swing.border.Border; +import javax.swing.border.EmptyBorder; + +import edu.mit.csail.sdg.alloy4.ConstList; +import edu.mit.csail.sdg.alloy4.OurBorder; +import edu.mit.csail.sdg.alloy4.OurCombobox; +import edu.mit.csail.sdg.alloy4.OurUtil; +import edu.mit.csail.sdg.alloy4.Util; +import edu.mit.csail.sdg.alloy4graph.GraphViewer; + +/** + * GUI panel that houses the actual graph, as well as any projection comboboxes. + * + *

+ * Thread Safety: Can be called only by the AWT event thread. + */ + +public final class VizGraphPanel extends JPanel { + + /** Inner class that displays a combo box of possible projection atom choices. */ + final class TypePanel extends JPanel { + /** This ensures the class can be serialized reliably. */ + private static final long serialVersionUID = 0; + /** The type being projected. */ + private final AlloyType type; + /** + * The list of atoms; can be an empty list if there are no atoms in this type to be projected. + */ + private final List atoms; + /** The list of atom names; atomnames.empty() iff atoms.isEmpty() */ + private final String[] atomnames; + /** The combo box showing the possible atoms to choose from. */ + private final JComboBox atomCombo; + + /** + * Constructs a new TypePanel. + * + * @param type - the type being projected + * @param atoms - the list of possible projection atom choices + */ + private TypePanel(final AlloyType type, List atoms, final AlloyAtom initialValue) { + super(); + final JButton left, right; + int initialIndex = 0; + this.type = type; + atoms = new ArrayList(atoms); + Collections.sort(atoms); + this.atoms = ConstList.make(atoms); + this.setLayout(new BoxLayout(this, BoxLayout.X_AXIS)); + this.setBorder(null); + this.atomnames = new String[this.atoms.size()]; + for (int i = 0; i < this.atoms.size(); i++) { + this.atomnames[i] = this.atoms.get(i).getVizName(VizGraphPanel.this.vizState, true); + if (this.atoms.get(i).equals(initialValue)) { + initialIndex = i; + } + } + this.add(left = new JButton("<<")); + this.add(Box.createRigidArea(new Dimension(2, 2))); + this.add(this.atomCombo = + new OurCombobox(this.atomnames.length < 1 ? new String[] {" "} : this.atomnames)); + this.add(Box.createRigidArea(new Dimension(2, 2))); + this.add(right = new JButton(">>")); + left.setVerticalAlignment(SwingConstants.CENTER); + right.setVerticalAlignment(SwingConstants.CENTER); + final Dimension dim = this.atomCombo.getPreferredSize(); + final int idealWidth = Util.onMac() ? 120 : 80; + if (dim.width < idealWidth) { + dim.width = idealWidth + 20; + this.atomCombo.setMinimumSize(dim); + this.atomCombo.setPreferredSize(dim); + } + left.setEnabled(initialIndex > 0); + right.setEnabled(initialIndex < this.atomnames.length - 1); + this.atomCombo.setSelectedIndex(initialIndex); + if (Util.onMac()) { + this.atomCombo.setBorder(BorderFactory.createEmptyBorder(4, 1, 0, 1)); + } + left.addActionListener(new ActionListener() { + @Override + public final void actionPerformed(final ActionEvent e) { + final int curIndex = TypePanel.this.atomCombo.getSelectedIndex(); + if (curIndex > 0) { + TypePanel.this.atomCombo.setSelectedIndex(curIndex - 1); + } + } + }); + right.addActionListener(new ActionListener() { + @Override + public final void actionPerformed(final ActionEvent e) { + final int curIndex = TypePanel.this.atomCombo.getSelectedIndex(); + if (curIndex < TypePanel.this.atomCombo.getItemCount() - 1) { + TypePanel.this.atomCombo.setSelectedIndex(curIndex + 1); + } + } + }); + this.atomCombo.addActionListener(new ActionListener() { + @Override + public final void actionPerformed(final ActionEvent e) { + left.setEnabled(TypePanel.this.atomCombo.getSelectedIndex() > 0); + right.setEnabled( + TypePanel.this.atomCombo.getSelectedIndex() < TypePanel.this.atomnames.length - 1); + VizGraphPanel.this.remakeAll(); + VizGraphPanel.this.getParent().invalidate(); + VizGraphPanel.this.getParent().repaint(); + } + }); + } + + /** Returns the currently-selected atom; returns null if the list is empty. */ + public AlloyAtom getAlloyAtom() { + final int i = this.atomCombo.getSelectedIndex(); + if (i >= 0 && i < this.atoms.size()) { + return this.atoms.get(i); + } else { + return null; + } + } + + /** Returns the entire list of atoms; could be an empty set. */ + public List getAlloyAtoms() { + return this.atoms; + } + + /** Returns the AlloyType associated with this TypePanel. */ + public AlloyType getAlloyType() { + return this.type; + } + + /** True if this TypePanel object does not need to be rebuilt. */ + private boolean upToDate(final AlloyType type, List atoms) { + if (!this.type.equals(type)) { + return false; + } + atoms = new ArrayList(atoms); + Collections.sort(atoms); + if (!this.atoms.equals(atoms)) { + return false; + } + for (int i = 0; i < this.atoms.size(); i++) { + final String n = this.atoms.get(i).getVizName(VizGraphPanel.this.vizState, true); + if (!this.atomnames[i].equals(n)) { + return false; + } + } + return true; + } + } + + /** This ensures the class can be serialized reliably. */ + private static final long serialVersionUID = 0; + + /** This is the current customization settings. */ + private final VizState vizState; + + /** Whether the user wants to see the DOT source code or not. */ + private boolean seeDot = false; + + /** The current GraphViewer (or null if we are not looking at a GraphViewer) */ + private GraphViewer viewer = null; + + /** The scrollpane containing the upperhalf of the panel (showing the graph) */ + private final JScrollPane diagramScrollPanel; + + /** The upperhalf of the panel (showing the graph). */ + private final JPanel graphPanel; + + /** The lowerhalf of the panel (showing the comboboxes for choosing the projected atoms). */ + private final JPanel navPanel; + + /** The splitpane separating the graphPanel and the navPanel. */ + private final JSplitPane split; + + /** The current projection choice; null if no projection is in effect. */ + private AlloyProjection currentProjection = null; + + /** This is the list of TypePanel(s) we've already constructed. */ + private final Map type2panel = new TreeMap(); + + /** + * Create a splitpane showing the graph on top, as well as projection comboboxes on the bottom. + * + * @param vizState - the current visualization settings + * @param seeDot - true if we want to see the DOT source code, false if we want it rendered as a + * graph + */ + public VizGraphPanel(final VizState vizState, final boolean seeDot) { + final Border b = new EmptyBorder(0, 0, 0, 0); + OurUtil.make(this, Color.BLACK, Color.WHITE, b); + this.seeDot = seeDot; + this.vizState = vizState; + this.setLayout(new GridLayout()); + this.setMaximumSize(new Dimension(Short.MAX_VALUE, Short.MAX_VALUE)); + this.navPanel = new JPanel(); + final JScrollPane navscroll = OurUtil.scrollpane(this.navPanel); + this.graphPanel = OurUtil.make(new JPanel(), Color.BLACK, Color.WHITE, b); + // graphPanel.addMouseListener(new MouseAdapter() { + // @Override public void mousePressed(MouseEvent ev) { + // // We let Ctrl+LeftClick bring up the popup menu, just like RightClick, + // // since many Mac mouses do not have a right button. + // if (viewer==null) return; + // else if (ev.getButton()==MouseEvent.BUTTON3) { } + // else if (ev.getButton()==MouseEvent.BUTTON1 && ev.isControlDown()) { } + // else return; + // viewer.alloyPopup(graphPanel, ev.getX(), ev.getY()); + // } + // }); + this.diagramScrollPanel = + OurUtil.scrollpane(this.graphPanel, new OurBorder(true, true, true, false)); + this.diagramScrollPanel.getVerticalScrollBar().addAdjustmentListener(new AdjustmentListener() { + @Override + public void adjustmentValueChanged(final AdjustmentEvent e) { + VizGraphPanel.this.diagramScrollPanel.invalidate(); + VizGraphPanel.this.diagramScrollPanel.repaint(); + VizGraphPanel.this.diagramScrollPanel.validate(); + } + }); + this.diagramScrollPanel.getHorizontalScrollBar() + .addAdjustmentListener(new AdjustmentListener() { + @Override + public void adjustmentValueChanged(final AdjustmentEvent e) { + VizGraphPanel.this.diagramScrollPanel.invalidate(); + VizGraphPanel.this.diagramScrollPanel.repaint(); + VizGraphPanel.this.diagramScrollPanel.validate(); + } + }); + this.split = + OurUtil.splitpane(JSplitPane.VERTICAL_SPLIT, this.diagramScrollPanel, navscroll, 0); + this.split.setResizeWeight(1.0); + this.split.setDividerSize(0); + this.add(this.split); + this.remakeAll(); + } + + /** + * Retrieves the actual GraphViewer object that contains the graph (or null if the graph hasn't + * loaded yet) + */ + public GraphViewer alloyGetViewer() { + return this.viewer; + } + + public JPanel getGraphPanel() { + return this.graphPanel; + } + + /** We override the paint method to auto-resize the divider. */ + @Override + public void paint(final Graphics g) { + super.paint(g); + this.split.setDividerLocation(this.split.getSize().height - this.split.getInsets().bottom + - this.split.getDividerSize() - this.split.getRightComponent().getPreferredSize().height); + } + + /** Regenerate the comboboxes and the graph. */ + public void remakeAll() { + final Map map = new LinkedHashMap(); + this.navPanel.removeAll(); + for (final AlloyType type : this.vizState.getProjectedTypes()) { + final List atoms = this.vizState.getOriginalInstance().type2atoms(type); + TypePanel tp = this.type2panel.get(type); + if (tp != null && tp.getAlloyAtom() != null && !atoms.contains(tp.getAlloyAtom())) { + tp = null; + } + if (tp != null && tp.getAlloyAtom() == null && atoms.size() > 0) { + tp = null; + } + if (tp != null && !tp.upToDate(type, atoms)) { + tp = null; + } + if (tp == null) { + this.type2panel.put(type, tp = new TypePanel(type, atoms, null)); + } + this.navPanel.add(tp); + map.put(tp.getAlloyType(), tp.getAlloyAtom()); + } + this.currentProjection = new AlloyProjection(map); + final JPanel graph = this.vizState.getGraph(this.currentProjection); + if (this.seeDot && graph instanceof GraphViewer) { + this.viewer = null; + final JTextArea txt = OurUtil.textarea(graph.toString(), 10, 10, false, true, this.getFont()); + this.diagramScrollPanel.setViewportView(txt); + } else { + if (graph instanceof GraphViewer) { + this.viewer = (GraphViewer) graph; + } else { + this.viewer = null; + } + this.graphPanel.removeAll(); + this.graphPanel.add(graph); + this.diagramScrollPanel.setViewportView(this.graphPanel); + this.diagramScrollPanel.invalidate(); + this.diagramScrollPanel.repaint(); + this.diagramScrollPanel.validate(); + } + } + + /** Changes whether we are seeing the DOT source or not. */ + public void seeDot(final boolean yesOrNo) { + if (this.seeDot == yesOrNo) { + return; + } + this.seeDot = yesOrNo; + this.remakeAll(); + } + + /** Changes the font. */ + @Override + public void setFont(final Font font) { + super.setFont(font); + if (this.diagramScrollPanel != null) { + this.diagramScrollPanel.getViewport().getView().setFont(font); + } + } + + public String toDot() { + return this.vizState.getGraph(this.currentProjection).toString(); + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4viz/VizState.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4viz/VizState.java new file mode 100644 index 00000000..9569139f --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4viz/VizState.java @@ -0,0 +1,448 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4viz; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.io.IOException; +import java.util.LinkedHashMap; +import java.util.Set; +import java.util.TreeSet; +import javax.swing.Icon; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import edu.mit.csail.sdg.alloy4.ConstSet; +import edu.mit.csail.sdg.alloy4.MailBug; +import edu.mit.csail.sdg.alloy4.OurCheckbox; +import edu.mit.csail.sdg.alloy4.OurUtil; +import edu.mit.csail.sdg.alloy4graph.DotColor; +import edu.mit.csail.sdg.alloy4graph.DotPalette; +import edu.mit.csail.sdg.alloy4graph.DotShape; +import edu.mit.csail.sdg.alloy4graph.DotStyle; + +/** Mutable; this stores an unprojected model as well as the current theme customization. + * + *

Thread Safety: Can be called only by the AWT event thread. + */ + +public final class VizState { + + /** Construct a new VizState (with default theme settings) for the given instance; if world!=null, it is the root of the AST. + */ + public VizState(AlloyInstance originalInstance) { + this.originalInstance = originalInstance; + this.currentModel = originalInstance.model; + resetTheme(); + loadInstance(originalInstance); + } + + /** Make a copy of an existing VizState object. */ + public VizState(VizState old) { + originalInstance = old.originalInstance; + currentModel = old.currentModel; + projectedTypes = new TreeSet(old.projectedTypes); + useOriginalNames = old.useOriginalNames; + hidePrivate = old.hidePrivate; + hideMeta = old.hideMeta; + fontSize = old.fontSize; + nodePalette = old.nodePalette; + edgePalette = old.edgePalette; + nodeColor.putAll(old.nodeColor); + nodeStyle.putAll(old.nodeStyle); + nodeVisible.putAll(old.nodeVisible); + label.putAll(old.label); + number.putAll(old.number); + hideUnconnected.putAll(old.hideUnconnected); + showAsAttr.putAll(old.showAsAttr); + showAsLabel.putAll(old.showAsLabel); + shape.putAll(old.shape); + weight.putAll(old.weight); + attribute.putAll(old.attribute); + mergeArrows.putAll(old.mergeArrows); + constraint.putAll(old.constraint); + layoutBack.putAll(old.layoutBack); + edgeColor.putAll(old.edgeColor); + edgeStyle.putAll(old.edgeStyle); + edgeVisible.putAll(old.edgeVisible); + changedSinceLastSave = false; + } + + /** Clears the current theme. */ + public void resetTheme() { + currentModel = originalInstance.model; + projectedTypes.clear(); + useOriginalNames = false; + hidePrivate = true; + hideMeta = true; + fontSize = 12; + nodePalette = DotPalette.CLASSIC; + edgePalette = DotPalette.CLASSIC; + nodeColor.clear(); nodeColor.put(null, DotColor.WHITE); + nodeStyle.clear(); nodeStyle.put(null, DotStyle.SOLID); + nodeVisible.clear(); nodeVisible.put(null, true); + label.clear(); label.put(null, ""); + number.clear(); number.put(null, true); + hideUnconnected.clear(); hideUnconnected.put(null, false); + showAsAttr.clear(); showAsAttr.put(null, false); + showAsLabel.clear(); showAsLabel.put(null, true); + shape.clear(); shape.put(null, DotShape.ELLIPSE); + weight.clear(); weight.put(null, 0); + attribute.clear(); attribute.put(null, false); + mergeArrows.clear(); mergeArrows.put(null, true); + constraint.clear(); constraint.put(null, true); + layoutBack.clear(); layoutBack.put(null, false); + edgeColor.clear(); edgeColor.put(null, DotColor.MAGIC); + edgeStyle.clear(); edgeStyle.put(null, DotStyle.SOLID); + edgeVisible.clear(); edgeVisible.put(null, true); + // Provide some nice defaults for "Int" and "seq/Int" type + AlloyType sigint=AlloyType.INT; + label.put(sigint,""); + number.put(sigint,true); + hideUnconnected.put(sigint,true); + AlloyType seqidx=AlloyType.SEQINT; + label.put(seqidx,""); + number.put(seqidx,true); + hideUnconnected.put(seqidx,true); + // Provide some nice defaults for meta model stuff + AlloyType set=AlloyType.SET; + AlloyRelation ext=AlloyRelation.EXTENDS, in=AlloyRelation.IN; + shape.put(null,DotShape.BOX); nodeColor.put(null,DotColor.YELLOW); nodeStyle.put(null,DotStyle.SOLID); + shape.put(set,DotShape.ELLIPSE); nodeColor.put(set,DotColor.BLUE); label.put(set,""); + edgeColor.put(ext,DotColor.BLACK); weight.put(ext,100); layoutBack.put(ext,true); + edgeColor.put(in,DotColor.BLACK); weight.put(in,100); layoutBack.put(in,true); + // Done + cache.clear(); + changedSinceLastSave=false; + } + + /** Load a new instance into this VizState object (the input argument is treated as a new unprojected instance); + * if world!=null, it is the root of the AST + */ + public void loadInstance(AlloyInstance unprojectedInstance) { + this.originalInstance=unprojectedInstance; + for (AlloyType t:getProjectedTypes()) if (!unprojectedInstance.model.hasType(t)) projectedTypes.remove(t); + currentModel = StaticProjector.project(unprojectedInstance.model, projectedTypes); + cache.clear(); + } + + /** Erase the current theme customizations and then load it from a file. + * @throws IOException - if an error occurred + */ + public void loadPaletteXML(String filename) throws IOException { + resetTheme(); + StaticThemeReaderWriter.readAlloy(filename,this); + cache.clear(); + changedSinceLastSave=false; + } + + /** Saves the current theme to a file (which will be overwritten if it exists already). + * @throws IOException - if an error occurred + */ + public void savePaletteXML(String filename) throws IOException { + StaticThemeReaderWriter.writeAlloy(filename,this); + changedSinceLastSave=false; + } + + /** Caches previously generated graphs. */ + private LinkedHashMap cache=new LinkedHashMap(); + + /** Generate a VizGraphPanel for a given projection choice, using the current settings. */ + public JPanel getGraph(AlloyProjection projectionChoice) { + JPanel ans = cache.get(projectionChoice); + if (ans!=null) return ans; + AlloyInstance inst = originalInstance; + try { + ans = StaticGraphMaker.produceGraph(inst, this, projectionChoice); + cache.put(projectionChoice, ans); + } catch(Throwable ex) { + String msg = "An error has occurred: " + ex + "\n\nStackTrace:\n" + MailBug.dump(ex) + "\n"; + JScrollPane scroll = OurUtil.scrollpane(OurUtil.textarea(msg, 0, 0, false, false)); + ans = new JPanel(); + ans.setLayout(new BorderLayout()); + ans.add(scroll, BorderLayout.CENTER); + ans.setBackground(Color.WHITE); + } + ans.setBorder(null); + return ans; + } + + /** True if the theme has been modified since last save. */ + private boolean changedSinceLastSave=false; + + /** True if the theme has been modified since last save. */ + public boolean changedSinceLastSave() { return changedSinceLastSave; } + + /** Sets the "changed since last save" flag, then flush any cached generated graphs. */ + private void change() { changedSinceLastSave=true; cache.clear(); } + + /** If oldValue is different from newValue, then sets the "changed since last save" flag and flush the cache. */ + private void changeIf(Object oldValue, Object newValue) { + if (oldValue==null) { if (newValue==null) return; } else { if (oldValue.equals(newValue)) return; } + change(); + } + + /*============================================================================================*/ + + /** If x is an AlloyType, x is not univ, then return its parent (which could be univ); + * If x is an AlloySet, then return x's type; + * All else, return null. + */ + private AlloyType parent(AlloyElement x, AlloyModel model) { + if (x instanceof AlloySet) return ((AlloySet)x).getType(); + if (x instanceof AlloyType) return model.getSuperType((AlloyType)x); + return null; + } + + /*============================================================================================*/ + + /** The original unprojected instance. */ + private AlloyInstance originalInstance; + + /** Returns the original unprojected model. */ + public AlloyInstance getOriginalInstance() { return originalInstance; } + + /** Returns the original unprojected model. */ + public AlloyModel getOriginalModel() { return originalInstance.model; } + + /*============================================================================================*/ + + /** The current (possibly projected) model. */ + private AlloyModel currentModel; + + /** Returns the current (possibly projected) model. */ + public AlloyModel getCurrentModel() { return currentModel; } + + /*============================================================================================*/ + + /** The set of types we are currently projecting over. */ + private Set projectedTypes = new TreeSet(); + + /** Gets an unmodifiable copy of the set of types we are currently projecting over. */ + public ConstSet getProjectedTypes() { return ConstSet.make(projectedTypes); } + + /** Returns true iff the type is not univ, and it is a toplevel type. */ + public boolean canProject(final AlloyType type) { return isTopLevel(type); } + + /** Returns true iff the type is not univ, and it is a toplevel type. */ + public boolean isTopLevel(final AlloyType type) { + return AlloyType.UNIV.equals(originalInstance.model.getSuperType(type)); + } + + /** Adds type to the list of projected types if it's a toplevel type. */ + public void project(AlloyType type) { + if (canProject(type)) if (projectedTypes.add(type)) { + currentModel = StaticProjector.project(originalInstance.model, projectedTypes); + change(); + } + } + + /** Removes type from the list of projected types if it is currently projected. */ + public void deproject(AlloyType type) { + if (projectedTypes.remove(type)) { + currentModel = StaticProjector.project(originalInstance.model, projectedTypes); + change(); + } + } + + /** Removes every entry from the list of projected types. */ + public void deprojectAll() { + if (projectedTypes.size()>0) { + projectedTypes.clear(); + currentModel = StaticProjector.project(originalInstance.model, projectedTypes); + change(); + } + } + + /*============================================================================================*/ + + /** Whether to use the original atom names. */ + private boolean useOriginalNames = false; + + /** Returns whether we will use original atom names. */ + public boolean useOriginalName() { return useOriginalNames; } + + /** Sets whether we will use original atom names or not. */ + public void useOriginalName(Boolean newValue) { + if (newValue!=null && useOriginalNames!=newValue) { change(); useOriginalNames=newValue; } + } + + /*============================================================================================*/ + + /** Whether to hide private sigs/fields/relations. */ + private boolean hidePrivate = false; + + /** Returns whether we will hide private sigs/fields/relations. */ + public boolean hidePrivate() { return hidePrivate; } + + /** Sets whether we will hide private sigs/fields/relations. */ + public void hidePrivate(Boolean newValue) { + if (newValue!=null && hidePrivate!=newValue) { change(); hidePrivate=newValue; } + } + + /*============================================================================================*/ + + /** Whether to hide meta sigs/fields/relations. */ + private boolean hideMeta = true; + + /** Returns whether we will hide meta sigs/fields/relations. */ + public boolean hideMeta() { return hideMeta; } + + /** Sets whether we will hide meta sigs/fields/relations. */ + public void hideMeta(Boolean newValue) { + if (newValue!=null && hideMeta!=newValue) { change(); hideMeta=newValue; } + } + + /*============================================================================================*/ + + /** The graph's font size. */ + private int fontSize = 12; + + /** Returns the font size. */ + public int getFontSize() { return fontSize; } + + /** Sets the font size. */ + public void setFontSize(int n) { if (fontSize!=n && fontSize>0) { change(); fontSize=n; } } + + /*============================================================================================*/ + + /** The default node palette. */ + private DotPalette nodePalette; + + /** Gets the default node palette. */ + public DotPalette getNodePalette() { return nodePalette; } + + /** Sets the default node palette. */ + public void setNodePalette(DotPalette x) { + if (nodePalette!=x && x!=null) {change(); nodePalette=x;} + } + + /*============================================================================================*/ + + /** The default edge palette. */ + private DotPalette edgePalette; + + /** Gets the default edge palette. */ + public DotPalette getEdgePalette() { return edgePalette; } + + /** Sets the default edge palette. */ + public void setEdgePalette(DotPalette x) { + if (edgePalette!=x && x!=null) {change(); edgePalette=x;} + } + + /*============================================================================================*/ + + + // An important invariant to maintain: every map here must map null to a nonnull value. + public final MInt weight = new MInt(); + public final MString label = new MString(); + public final MMap nodeColor = new MMap(); + public final MMap edgeColor = new MMap(); + public final MMap nodeStyle = new MMap(); + public final MMap edgeStyle = new MMap(); + public final MMap shape = new MMap(); + public final MMap attribute = new MMap(true, false); + public final MMap mergeArrows = new MMap(true, false); + public final MMap constraint = new MMap(true, false); + public final MMap layoutBack = new MMap(true, false); + public final MMap edgeVisible = new MMap(true, false); + public final MMap nodeVisible = new MMap(true, false); + public final MMap number = new MMap(true, false); + public final MMap hideUnconnected = new MMap(true, false); + public final MMap showAsAttr = new MMap(true, false); + public final MMap showAsLabel = new MMap(true, false); + + public final class MInt { + private final LinkedHashMap map = new LinkedHashMap(); + private MInt() { } + private void clear() { map.clear(); change(); } + private void putAll(MInt x) { map.putAll(x.map); change(); } + public int get(AlloyElement x) { Integer ans=map.get(x); if (ans==null) return 0; else return ans; } + public void put(AlloyElement x, Integer v) { if (v==null || v<0) v=0; changeIf(map.put(x,v), v); } + } + + public final class MString { + private final LinkedHashMap map = new LinkedHashMap(); + private MString() { } + private void clear() { map.clear(); change(); } + private void putAll(MString x) { map.putAll(x.map); change(); } + public String get(AlloyElement x) { String ans=map.get(x); if (ans==null) ans=x.getName().trim(); return ans; } + public void put(AlloyElement x, String v) { if (x==null && v==null) v=""; if (x!=null && x.getName().equals(v)) v=null; changeIf(map.put(x,v), v); } + } + + public final class MMap { + private final LinkedHashMap map = new LinkedHashMap(); + private final T onValue; + private final T offValue; + private MMap() { onValue=null; offValue=null; } + private MMap(T on, T off) { this.onValue=on; this.offValue=off; } + private void clear() { map.clear(); change(); } + private void putAll(MMap x) { map.putAll(x.map); change(); } + public T get(AlloyElement obj) { return map.get(obj); } + public T resolve(AlloyElement obj) { + AlloyModel m = currentModel; + for(AlloyElement x=obj; ;x=parent(x,m)) { T v=map.get(x); if (v!=null) return v; } + } + /** Set the value for the given object; can be "null" to mean "inherit" */ + public void put(AlloyElement obj, T value) { + if (obj==null && value==null) return; + Object old = map.put(obj, value); + if ((old==null && value!=null) || (old!=null && !old.equals(value))) change(); + } + OurCheckbox pick(String label, String tooltip) { + return new OurCheckbox(label, tooltip, (Boolean.TRUE.equals(get(null)) ? OurCheckbox.ON : OurCheckbox.OFF)) { + private static final long serialVersionUID = 0; + public Icon do_action() { + T old = get(null); + boolean ans = (old!=null && old.equals(onValue)); + MMap.this.put(null, ans ? offValue : onValue); + return ans ? OFF : ON; + } + }; + } + OurCheckbox pick(final AlloyElement obj, final String label, final String tooltip) { + T a = get(obj), b = resolve(obj); + Icon icon = a==null ? (Boolean.TRUE.equals(b) ? OurCheckbox.INH_ON : OurCheckbox.INH_OFF) : (Boolean.TRUE.equals(a) ? OurCheckbox.ALL_ON : OurCheckbox.ALL_OFF); + return new OurCheckbox(label, tooltip, icon) { + private static final long serialVersionUID = 0; + public Icon do_action() { + T a = get(obj); + if (a==null) a=onValue; else if (a.equals(onValue)) a=offValue; else a=null; + MMap.this.put(obj, a); + return a==null ? (Boolean.TRUE.equals(resolve(obj)) ? INH_ON : INH_OFF) : (Boolean.TRUE.equals(a) ? ALL_ON : ALL_OFF); + } + }; + } + } + + // Reads the value for that type/set/relation. + // If x==null, then we guarantee the return value is nonnull + // If x!=null, then it may return null (which means "inherited") + // (Note: "label" and "weight" will never return null) + + // Reads the value for that atom based on an existing AlloyInstance; return value is never null. + public DotColor nodeColor (AlloyAtom a, AlloyInstance i) { for(AlloySet s:i.atom2sets(a)) {DotColor v=nodeColor.get(s); if (v!=null) return v;} return nodeColor.resolve (a.getType()); } + public DotStyle nodeStyle (AlloyAtom a, AlloyInstance i) { for(AlloySet s:i.atom2sets(a)) {DotStyle v=nodeStyle.get(s); if (v!=null) return v;} return nodeStyle.resolve (a.getType()); } + public DotShape shape (AlloyAtom a, AlloyInstance i) { for(AlloySet s:i.atom2sets(a)) {DotShape v=shape.get(s); if (v!=null) return v;} return shape.resolve (a.getType()); } + public boolean nodeVisible (AlloyAtom a, AlloyInstance i) { + // If it's in 1 or more set, then TRUE if at least one of them is TRUE. + // If it's in 0 set, then travel up the chain of AlloyType and return the first non-null value. + if (i.atom2sets(a).size()>0) { + for(AlloySet s:i.atom2sets(a)) if (nodeVisible.resolve(s)) return true; + return false; + } + return nodeVisible.resolve(a.getType()); + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4viz/VizTree.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4viz/VizTree.java new file mode 100644 index 00000000..9d3dd202 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4viz/VizTree.java @@ -0,0 +1,151 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4viz; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import edu.mit.csail.sdg.alloy4compiler.ast.Expr; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprHasName; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprVar; +import edu.mit.csail.sdg.alloy4compiler.ast.Sig; +import edu.mit.csail.sdg.alloy4compiler.ast.Sig.Field; +import edu.mit.csail.sdg.alloy4compiler.translator.A4Solution; +import edu.mit.csail.sdg.alloy4compiler.translator.A4Tuple; +import edu.mit.csail.sdg.alloy4compiler.translator.A4TupleSet; +import edu.mit.csail.sdg.alloy4.Err; +import edu.mit.csail.sdg.alloy4.OurTree; +import edu.mit.csail.sdg.alloy4.Pair; +import edu.mit.csail.sdg.alloy4.Util; +import static edu.mit.csail.sdg.alloy4.Util.encode; + +/** GUI tree that displays an instance as a tree. + * + *

Thread Safety: Can be called only by the AWT event thread. + */ + +public final class VizTree extends OurTree { + + /** {@inheritDoc} */ + @Override public String convertValueToText(Object val, boolean selected, boolean expanded, boolean leaf, int row, boolean focus) { + String c = ">"; + if (onWindows) c = selected ? " style=\"color:#ffffff;\">" : " style=\"color:#000000;\">"; + if (val instanceof A4Solution) return " "; + if (val instanceof Sig) { + String label = ((Sig)val).label; + if (label.startsWith("this/")) label = label.substring(5); + return " "; + } + if (val instanceof ExprVar) return " "; + if (val instanceof String) return " "; + if (val instanceof Pair) return " )val).b)).label) + ""; + if (val instanceof A4Tuple) { + StringBuilder sb = new StringBuilder(" 1) sb.append(" -> "); + sb.append(encode(tp.atom(i))); + } + sb.append(""); + return sb.toString(); + } + return ""; + } + + /** {@inheritDoc} */ + @Override public Object do_root() { return instance; } + + /** {@inheritDoc} */ + @Override public List do_ask(Object parent) { + List ans = new ArrayList(); + try { + if (parent instanceof A4Solution) { + return toplevel; + } else if (parent instanceof Sig || parent instanceof ExprVar) { + A4TupleSet ts = (A4TupleSet) (instance.eval((Expr)parent)); + for(A4Tuple t: ts) ans.add(t.atom(0)); + } else if (parent instanceof String) { + String atom = (String)parent; + for(Sig s: instance.getAllReachableSigs()) for(Field f: s.getFields()) for(A4Tuple t: instance.eval(f)) { + if (t.atom(0).equals(atom)) { ans.add(new Pair(atom, f)); break; } + } + for(ExprVar f: instance.getAllSkolems()) if (f.type().arity()>1) for(A4Tuple t: (A4TupleSet)(instance.eval(f))) { + if (t.atom(0).equals(atom)) { ans.add(new Pair(atom, f)); break; } + } + } else if (parent instanceof Pair) { + Pair p = (Pair)parent; + ExprHasName rel = (ExprHasName) (p.b); + String atom = (String) (p.a); + for(A4Tuple tuple: (A4TupleSet) (instance.eval(rel))) if (tuple.atom(0).equals(atom)) { + if (tuple.arity()==2) ans.add(tuple.atom(1)); else ans.add(tuple); + } + } else if (parent instanceof A4Tuple) { + A4Tuple tp = (A4Tuple)parent; + for(int i=1; i() { + public int compare(Object a, Object b) { + String t1, t2; + if (a instanceof Pair) { t1=((ExprHasName)(((Pair)a).b)).label; t2=((ExprHasName)(((Pair)b).b)).label; } + else { t1=a.toString(); t2=b.toString(); } + int i = t1.compareToIgnoreCase(t2); + if (i!=0) return i; else return t1.compareTo(t2); + } + }); + return ans; + } catch(Err er) { + return ans; + } + } + + /** This ensures the class can be serialized reliably. */ + private static final long serialVersionUID = 0; + + /** Caches whether we're on Windows or not. */ + private final boolean onWindows; + + /** The title of this tree. */ + private final String title; + + /** The instance being displayed. */ + private final A4Solution instance; + + /** The list of toplevel nodes to show. */ + private final List toplevel; + + /** Constructs a tree to display the given instance. */ + public VizTree(A4Solution instance, String title, int fontSize) { + super(fontSize); + this.instance = instance; + this.title = title; + this.onWindows = Util.onWindows(); + ArrayList toplevel = new ArrayList(); + for(Sig s: instance.getAllReachableSigs()) if (s!=Sig.UNIV && s!=Sig.SEQIDX && s!=Sig.NONE) toplevel.add(s); + for(ExprVar v: instance.getAllSkolems()) if (v.type().arity()==1 && v.label.startsWith("$")) toplevel.add(v); + Collections.sort(toplevel, new Comparator() { + public int compare(Object a, Object b) { + String t1, t2; + if (a instanceof Sig) { t1=((Sig)a).label; if (b instanceof ExprVar) return -1; else t2=((Sig)b).label; } + else { t1=((ExprVar)a).label; if (b instanceof Sig) return 1; else t2=((ExprVar)b).label; } + return Util.slashComparator.compare(t1, t2); + } + }); + this.toplevel = Collections.unmodifiableList(toplevel); + do_start(); + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4viz/package.html b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4viz/package.html new file mode 100644 index 00000000..dd708ff9 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4viz/package.html @@ -0,0 +1,5 @@ + + +This package displays Alloy4 instances. + + diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4whole/DemoFileSystem.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4whole/DemoFileSystem.java new file mode 100644 index 00000000..acedc665 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4whole/DemoFileSystem.java @@ -0,0 +1,157 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4whole; + +import static edu.mit.csail.sdg.alloy4.A4Reporter.NOP; +import java.util.LinkedHashSet; +import java.util.Set; +import edu.mit.csail.sdg.alloy4.Err; +import edu.mit.csail.sdg.alloy4compiler.ast.Attr; +import edu.mit.csail.sdg.alloy4compiler.ast.Command; +import edu.mit.csail.sdg.alloy4compiler.ast.Decl; +import edu.mit.csail.sdg.alloy4compiler.ast.Expr; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprConstant; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprHasName; +import edu.mit.csail.sdg.alloy4compiler.ast.Sig; +import edu.mit.csail.sdg.alloy4compiler.ast.Sig.Field; +import edu.mit.csail.sdg.alloy4compiler.ast.Sig.PrimSig; +import edu.mit.csail.sdg.alloy4compiler.translator.A4Options; +import edu.mit.csail.sdg.alloy4compiler.translator.A4Solution; +import edu.mit.csail.sdg.alloy4compiler.translator.TranslateAlloyToKodkod; + +/** This class demonstrates how to access Alloy4 via the API, by modeling a simple file system. */ + +public class DemoFileSystem { + + /* Helper methods to simplify using the API for this example. */ + + Set sigs = new LinkedHashSet(); + + PrimSig makeSig(String name, boolean isAbstract, boolean isOne) throws Err { + PrimSig ans = new PrimSig(name, (isAbstract ? Attr.ABSTRACT : null), (isOne ? Attr.ONE : null)); + sigs.add(ans); + return ans; + } + + PrimSig makeSig(PrimSig parent, String name, boolean isAbstract, boolean isOne) throws Err { + PrimSig ans = new PrimSig(name, parent, (isAbstract ? Attr.ABSTRACT : null), (isOne ? Attr.ONE : null)); + sigs.add(ans); + return ans; + } + + void runFor3(Expr expr) throws Err { + A4Options opt = new A4Options(); + Command cmd = new Command(false, 3, 3, 3, expr.and(fact)); + A4Solution sol = TranslateAlloyToKodkod.execute_command(NOP, sigs, cmd, opt); + System.out.println(sol.toString().trim()); + if (sol.satisfiable()) { + System.out.println("In particular, File = " + sol.eval(file)); + System.out.println("In particular, Dir = " + sol.eval(dir)); + System.out.println("In particular, contains = " + sol.eval(contains)); + System.out.println("In particular, parent = " + sol.eval(parent)); + } + System.out.println(); + } + + /* These corresponds to the helper predicates/functions provided in util/*.als */ + + static Expr acyclic(Expr r) throws Err { + Decl d = r.join(Sig.UNIV).oneOf("x"); // x is a variable over the domain of r + ExprHasName x = d.get(); + return x.in(x.join(r.closure())).not().forAll(d); // (x !in x.^r) for all x + } + + /* Here are definitions common to all instances. */ + + PrimSig obj=null, dir=null, file=null, root=null; + + Field parent=null, contains=null; + + Expr fact=ExprConstant.TRUE; + + void makeDomain() throws Err { + // abstract sig Obj { parent: lone Dir } + // abstract sig Dir extends Obj { contains: set Obj } + // abstract sig File extends Obj { } + // one sig Root extends Dir { } + obj = makeSig("Obj", true, false); + dir = makeSig(obj, "Dir", true, false); + file = makeSig(obj, "File", true, false); + root = makeSig(dir, "Root", false, true); + parent = obj.addField("parent", dir.loneOf()); + contains = dir.addField("contains", obj.setOf()); + // fact { all x:Obj-Root | one x.parent } + Decl x = obj.minus(root).oneOf("x"); + fact = x.get().join(parent).one().forAll(x).and(fact); + // fact { contains = ~ parent } + fact = contains.equal(parent.transpose()).and(fact); + // fact { acyclic[contains] } + fact = acyclic(contains).and(fact); + } + + /* Here is instance number 1. */ + + void makeInstance1() throws Err { + // file = F1, F2, F3 + // dir = Root, D1, D2 + // F1.parent = D1 + // F2.parent = D2 + // F3.parent = D2 + // D2.parent = D1 + // D1.parent = Root + PrimSig file1 = makeSig(file, "F1", false, true); + PrimSig file2 = makeSig(file, "F2", false, true); + PrimSig file3 = makeSig(file, "F3", false, true); + PrimSig dir1 = makeSig(dir, "D1", false, true); + PrimSig dir2 = makeSig(dir, "D2", false, true); + fact = file1.join(parent).equal(dir1).and(fact); + fact = file2.join(parent).equal(dir2).and(fact); + fact = file3.join(parent).equal(dir2).and(fact); + fact = dir2.join(parent).equal(dir1).and(fact); + fact = dir1.join(parent).equal(root).and(fact); + } + + /* Here is instance number 2. */ + + void makeInstance2() throws Err { + // dir = Root, D1, D2 + // D2.parent = D1 + // D1.parent = D2 + PrimSig dir1 = makeSig(dir, "D1", false, true); + PrimSig dir2 = makeSig(dir, "D2", false, true); + fact = dir2.join(parent).equal(dir1).and(fact); + fact = dir1.join(parent).equal(dir2).and(fact); + } + + private DemoFileSystem() { } + + /* The main driver. */ + + public static void main(String[] args) throws Err { + + DemoFileSystem x; + + x = new DemoFileSystem(); + x.makeDomain(); + x.makeInstance1(); + x.runFor3(x.file.some()); // run { some file } + + x = new DemoFileSystem(); + x.makeDomain(); + x.makeInstance1(); + x.runFor3(acyclic(x.contains).not()); // run { !acyclic[contains] } + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4whole/ExampleUsingTheAPI.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4whole/ExampleUsingTheAPI.java new file mode 100644 index 00000000..99418b74 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4whole/ExampleUsingTheAPI.java @@ -0,0 +1,94 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4whole; + +import java.util.Arrays; +import java.util.List; +import edu.mit.csail.sdg.alloy4.Err; +import edu.mit.csail.sdg.alloy4.Util; +import edu.mit.csail.sdg.alloy4compiler.ast.Attr; +import edu.mit.csail.sdg.alloy4compiler.ast.Decl; +import edu.mit.csail.sdg.alloy4compiler.ast.Expr; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprConstant; +import edu.mit.csail.sdg.alloy4compiler.ast.Command; +import edu.mit.csail.sdg.alloy4compiler.ast.Func; +import edu.mit.csail.sdg.alloy4compiler.ast.Sig.PrimSig; +import edu.mit.csail.sdg.alloy4compiler.ast.Sig; +import edu.mit.csail.sdg.alloy4compiler.translator.A4Options; +import edu.mit.csail.sdg.alloy4compiler.translator.A4Solution; +import edu.mit.csail.sdg.alloy4compiler.translator.TranslateAlloyToKodkod; +import static edu.mit.csail.sdg.alloy4compiler.ast.Sig.UNIV; +import static edu.mit.csail.sdg.alloy4.A4Reporter.NOP; + +/** This class demonstrates how to access Alloy4 via the API. */ + +public final class ExampleUsingTheAPI { + + public static void main(String[] args) throws Err { + + // Chooses the Alloy4 options + A4Options opt = new A4Options(); + opt.solver = A4Options.SatSolver.SAT4J; + + // abstract sig A {} + PrimSig A = new PrimSig("A", Attr.ABSTRACT); + + // sig B {} + PrimSig B = new PrimSig("B"); + + // one sig A1 extends A {} + PrimSig A1 = new PrimSig("A1", A, Attr.ONE); + + // one sig A2 extends A {} + PrimSig A2 = new PrimSig("A2", A, Attr.ONE); + + // A { f: B lone->lone B } + Expr f = A.addField("f", B.lone_arrow_lone(B)); + // Since (B lone->lone B) is not unary, the default is "setOf", meaning "f:set (B lone->lone B)" + + // A { g: B } + Expr g = A.addField("g", B); + // The line above is the same as: A.addField(null, "g", B.oneOf()) since B is unary. + // If you want "setOf", you need: A.addField(null, "g", B.setOf()) + + // pred someG { some g } + Func someG = new Func(null, "SomeG", null, null, g.some()); + + // pred atMostThree[x:univ, y:univ] { #(x+y) >= 3 } + Decl x = UNIV.oneOf("x"); + Decl y = UNIV.oneOf("y"); + Expr body = x.get().plus(y.get()).cardinality().lte(ExprConstant.makeNUMBER(3)); + Func atMost3 = new Func(null, "atMost3", Util.asList(x,y), null, body); + + List sigs = Arrays.asList(new Sig[]{A, B, A1, A2}); + + // run { some A && atMostThree[B,B] } for 3 but 3 int, 3 seq + Expr expr1 = A.some().and(atMost3.call(B,B)); + Command cmd1 = new Command(false, 3, 3, 3, expr1); + A4Solution sol1 = TranslateAlloyToKodkod.execute_command(NOP, sigs, cmd1, opt); + System.out.println("[Solution1]:"); + System.out.println(sol1.toString()); + + // run { some f && SomeG[] } for 3 but 2 int, 1 seq, 5 A, exactly 6 B + Expr expr2 = f.some().and(someG.call()); + Command cmd2 = new Command(false, 3, 2, 1, expr2); + cmd2 = cmd2.change(A, false, 5); + cmd2 = cmd2.change(B, true, 6); + A4Solution sol2 = TranslateAlloyToKodkod.execute_command(NOP, sigs, cmd2, opt); + System.out.println("[Solution2]:"); + System.out.println(sol2.toString()); + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4whole/ExampleUsingTheCompiler.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4whole/ExampleUsingTheCompiler.java new file mode 100644 index 00000000..aa9f2570 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4whole/ExampleUsingTheCompiler.java @@ -0,0 +1,92 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4whole; + +import edu.mit.csail.sdg.alloy4.A4Reporter; +import edu.mit.csail.sdg.alloy4.Err; +import edu.mit.csail.sdg.alloy4.ErrorWarning; +import edu.mit.csail.sdg.alloy4compiler.ast.Command; +import edu.mit.csail.sdg.alloy4compiler.ast.Module; +import edu.mit.csail.sdg.alloy4compiler.parser.CompUtil; +import edu.mit.csail.sdg.alloy4compiler.translator.A4Options; +import edu.mit.csail.sdg.alloy4compiler.translator.A4Solution; +import edu.mit.csail.sdg.alloy4compiler.translator.TranslateAlloyToKodkod; +import edu.mit.csail.sdg.alloy4viz.VizGUI; + +/** This class demonstrates how to access Alloy4 via the compiler methods. */ + +public final class ExampleUsingTheCompiler { + + /* + * Execute every command in every file. + * + * This method parses every file, then execute every command. + * + * If there are syntax or type errors, it may throw + * a ErrorSyntax or ErrorType or ErrorAPI or ErrorFatal exception. + * You should catch them and display them, + * and they may contain filename/line/column information. + */ + public static void main(String[] args) throws Err { + + // The visualizer (We will initialize it to nonnull when we visualize an Alloy solution) + VizGUI viz = null; + + // Alloy4 sends diagnostic messages and progress reports to the A4Reporter. + // By default, the A4Reporter ignores all these events (but you can extend the A4Reporter to display the event for the user) + A4Reporter rep = new A4Reporter() { + // For example, here we choose to display each "warning" by printing it to System.out + @Override public void warning(ErrorWarning msg) { + System.out.print("Relevance Warning:\n"+(msg.toString().trim())+"\n\n"); + System.out.flush(); + } + }; + + for(String filename:args) { + + // Parse+typecheck the model + System.out.println("=========== Parsing+Typechecking "+filename+" ============="); + Module world = CompUtil.parseEverything_fromFile(rep, null, filename); + + // Choose some default options for how you want to execute the commands + A4Options options = new A4Options(); + options.solver = A4Options.SatSolver.SAT4J; + + for (Command command: world.getAllCommands()) { + // Execute the command + System.out.println("============ Command "+command+": ============"); + A4Solution ans = TranslateAlloyToKodkod.execute_command(rep, world.getAllReachableSigs(), command, options); + // Print the outcome + System.out.println(ans); + // If satisfiable... + if (ans.satisfiable()) { + // You can query "ans" to find out the values of each set or type. + // This can be useful for debugging. + // + // You can also write the outcome to an XML file + ans.writeXML("alloy_example_output.xml"); + // + // You can then visualize the XML file by calling this: + if (viz==null) { + viz = new VizGUI(false, "alloy_example_output.xml", null); + } else { + viz.loadXML("alloy_example_output.xml", true); + } + } + } + } + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4whole/Helper.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4whole/Helper.java new file mode 100644 index 00000000..f9bb3d49 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4whole/Helper.java @@ -0,0 +1,55 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4whole; + +import java.util.Map; +import java.util.HashMap; +import edu.mit.csail.sdg.alloy4.Err; +import edu.mit.csail.sdg.alloy4compiler.ast.Sig; +import edu.mit.csail.sdg.alloy4compiler.ast.Sig.PrimSig; +import edu.mit.csail.sdg.alloy4compiler.translator.A4Solution; +import edu.mit.csail.sdg.alloy4compiler.translator.A4Tuple; +import edu.mit.csail.sdg.alloy4compiler.translator.A4TupleSet; + +/** This class contains convenient methods for people using Alloy4 API. + * These methods are provided for your convenience, and any discovered bug will be fixed, + * but these methods are not part of the official Alloy4 API and individual methods may be removed in the future if necessary. + */ + +public final class Helper { + + /** + * Given an A4Solution, return a map that maps every atom to its most specific signature. + * + *

For example, suppose we have "sig Animal { }" and "sig Dog, Cat extends Animal { }". + * Suppose the solution says Animal={A$1, A$2, A$3, A$4} and Dog={A$1} and Cat={A$2, A$3}. + * This method will return a map that maps A$1 to Dog, A$2 to Cat, A$3 to Cat, and A$4 to Animal. + * (A$1 is both an Animal and a Dog, but Dog is a subtype of Animal, so Dog is A$1's most specific signature) + */ + public static Map atom2sig(A4Solution solution) throws Err { + Map map = new HashMap(); + for(Sig s: solution.getAllReachableSigs()) if (s instanceof PrimSig) for(A4Tuple t: (A4TupleSet) solution.eval(s)) { + // We skip over SubsetSig and only care about PrimSig + String atom = t.atom(0); + PrimSig old = map.get(atom); + if (old==null || ((PrimSig)s).isSameOrDescendentOf(old)) { map.put(atom, (PrimSig)s); } + } + return map; + } + + /** Constructor is private, since this utility class never needs to be instantiated. */ + private Helper() { } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4whole/InternalTest.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4whole/InternalTest.java new file mode 100644 index 00000000..f989cb2e --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4whole/InternalTest.java @@ -0,0 +1,175 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4whole; + +import java.io.StringReader; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Arrays; +import edu.mit.csail.sdg.alloy4.A4Reporter; +import edu.mit.csail.sdg.alloy4.MailBug; +import edu.mit.csail.sdg.alloy4.SafeList; +import edu.mit.csail.sdg.alloy4.XMLNode; +import edu.mit.csail.sdg.alloy4compiler.ast.Command; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprVar; +import edu.mit.csail.sdg.alloy4compiler.ast.Sig; +import edu.mit.csail.sdg.alloy4compiler.ast.Sig.Field; +import edu.mit.csail.sdg.alloy4compiler.parser.CompUtil; +import edu.mit.csail.sdg.alloy4compiler.ast.Module; +import edu.mit.csail.sdg.alloy4compiler.translator.A4Options; +import edu.mit.csail.sdg.alloy4compiler.translator.A4Solution; +import edu.mit.csail.sdg.alloy4compiler.translator.A4SolutionReader; +import edu.mit.csail.sdg.alloy4compiler.translator.TranslateAlloyToKodkod; + +/** API-specific regression test suite; the larger collection of models that test both the compiler and translator are in models/tests/*.als */ + +final class InternalTest { + + private static void check(boolean condition) throws Exception { + if (!condition) throw new RuntimeException("Assertion failure: condition should be TRUE"); + } + + private static void check(Object a, Object b) { + if (a==null) { + if (b!=null) throw new AssertionError("a is null, but b is "+b); + } else if (b==null) { + throw new AssertionError("b is null, but a is "+a); + } else if (!a.equals(b)) { + throw new AssertionError("a!=b\n" + "a=" + a + "\nb=" + b + "\n"); + } + } + + static void test1() throws Exception { + XMLNode xml = new XMLNode(new StringReader( + "" + + "" + + " " + + " " + + " " + + " " + + "" + + " " + + " " + + " " + + "" + + "" + + " " + + " " + + "" + + "" + + "")); + Sig state = new Sig.PrimSig("State"); + A4Solution sol = A4SolutionReader.read(Arrays.asList(state), xml); + SafeList skolems = new SafeList(sol.getAllSkolems()); + check(skolems.size()==1); + check(skolems.get(0).label, "$Deadlock_s"); + check(skolems.get(0).type(), state.type()); + // + Sig state2 = new Sig.PrimSig("State"); + Field field2 = state2.addField("len", Sig.SIGINT); + sol = A4SolutionReader.read(Arrays.asList(state2), xml); + SafeList skolems2 = new SafeList(sol.getAllSkolems()); + check(skolems2.size()==1); + check(skolems2.get(0).label, "$Deadlock_s"); + check(skolems2.get(0).type(), state2.type()); + check(""+sol.eval(field2.cardinality()), "-2"); + } + + static void test2() throws Exception { + test1(); + XMLNode xml = new XMLNode(new StringReader( + "" + + "" + + " " + + " " + + " " + + " " + + "" + + " " + + " " + + "" + + "" + + "")); + Sig activity = new Sig.PrimSig("Act"); + A4Solution sol = A4SolutionReader.read(Arrays.asList(activity), xml); + SafeList skolems = new SafeList(sol.getAllSkolems()); + check(skolems.size()==1); + check(skolems.get(0).label, "$x"); + check(skolems.get(0).type(), Sig.SEQIDX.type().product(activity.type())); + } + + static void test3() throws Exception { + XMLNode xml = new XMLNode(new StringReader( + "" + + "" + + " " + + " " + + " " + + " " + + " " + + "" + + "")); + String err = ""; + try { A4SolutionReader.read(null, xml); } catch(Throwable ex) { err=ex.toString(); } + check(err.contains("cyclic inheritance")); + } + + /** Displays the amount of memory taken per solution enumeration. */ + public static void main2(String[] args) throws Exception { + String filename = "models/examples/algorithms/dijkstra.als"; + Module world = CompUtil.parseEverything_fromFile(A4Reporter.NOP, null, filename); + A4Options options = new A4Options(); + for (Command command: world.getAllCommands()) { + A4Solution ans = TranslateAlloyToKodkod.execute_command(A4Reporter.NOP, world.getAllReachableSigs(), command, options); + while(ans.satisfiable()) { + String hc = "Answer: " + ans.toString().hashCode(); + System.gc(); + System.gc(); + System.gc(); + Thread.sleep(500); + System.gc(); + System.gc(); + System.gc(); + Thread.sleep(500); + long t = Runtime.getRuntime().totalMemory(), f = Runtime.getRuntime().freeMemory(), m = Runtime.getRuntime().maxMemory(); + System.out.println(hc + " total=" + t + " free=" + f + " max="+m); System.out.flush(); + ans=ans.next(); + } + return; + } + } + + /** Runs every test case. */ + public static void main(String[] args) throws Exception { + try { + for(Method m: InternalTest.class.getDeclaredMethods()) { + String name = m.getName(); + if (name.startsWith("test")) { + System.out.print("Running "+name+"..."); System.out.flush(); + m.invoke(null, new Object[0]); + System.out.print(" Done.\n"); System.out.flush(); + } + } + } catch(Throwable ex) { + while(ex instanceof InvocationTargetException) ex=((InvocationTargetException)ex).getCause(); + System.out.println(); + System.out.flush(); + System.err.println("Error:\n"+MailBug.dump(ex).trim()+"\n"); + System.err.flush(); + System.exit(1); + } + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4whole/SimpleCLI.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4whole/SimpleCLI.java new file mode 100644 index 00000000..25d8fb54 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4whole/SimpleCLI.java @@ -0,0 +1,230 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4whole; + +import java.io.IOException; +import java.io.PrintWriter; +import java.io.RandomAccessFile; +import java.io.StringReader; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.List; +import edu.mit.csail.sdg.alloy4.A4Reporter; +import edu.mit.csail.sdg.alloy4.ErrorWarning; +import edu.mit.csail.sdg.alloy4.Pair; +import edu.mit.csail.sdg.alloy4.Util; +import edu.mit.csail.sdg.alloy4.Version; +import edu.mit.csail.sdg.alloy4.XMLNode; +import edu.mit.csail.sdg.alloy4compiler.ast.Command; +import edu.mit.csail.sdg.alloy4compiler.ast.Decl; +import edu.mit.csail.sdg.alloy4compiler.ast.Expr; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprHasName; +import edu.mit.csail.sdg.alloy4compiler.ast.Func; +import edu.mit.csail.sdg.alloy4compiler.ast.Sig; +import edu.mit.csail.sdg.alloy4compiler.ast.Module; +import edu.mit.csail.sdg.alloy4compiler.parser.CompUtil; +import edu.mit.csail.sdg.alloy4compiler.translator.A4Options; +import edu.mit.csail.sdg.alloy4compiler.translator.A4Solution; +import edu.mit.csail.sdg.alloy4compiler.translator.A4SolutionReader; +import edu.mit.csail.sdg.alloy4compiler.translator.A4SolutionWriter; +import edu.mit.csail.sdg.alloy4compiler.translator.TranslateAlloyToKodkod; +import edu.mit.csail.sdg.alloy4compiler.translator.A4Options.SatSolver; +import edu.mit.csail.sdg.alloy4viz.StaticInstanceReader; + +/** This class is used by the Alloy developers to drive the regression test suite. + * For a more detailed guide on how to use Alloy API, please see "ExampleUsingTheCompiler.java" + */ + +public final class SimpleCLI { + + private static final class SimpleReporter extends A4Reporter { + + private final StringBuilder sb = new StringBuilder(); + + private final List warnings = new ArrayList(); + + private final RandomAccessFile os; + + public SimpleReporter() throws IOException { + os = new RandomAccessFile(".alloy.tmp","rw"); + os.setLength(0); + } + + public void flush() throws IOException { + if (sb.length()>65536) { + os.write(sb.toString().getBytes("UTF-8")); + sb.delete(0, sb.length()); + } + } + + public void close() throws IOException { + if (sb.length()>0) { + os.write(sb.toString().getBytes("UTF-8")); + sb.delete(0, sb.length()); + } + os.close(); + } + + @Override public void debug(String msg) { sb.append(msg); } + + @Override public void parse(String msg) { sb.append(msg); } + + @Override public void typecheck(String msg) { sb.append(msg); } + + @Override public void warning(ErrorWarning msg) { warnings.add(msg); } + + @Override public void scope(String msg) { sb.append(" "); sb.append(msg); } + + @Override public void bound(String msg) { sb.append(" "); sb.append(msg); } + + @Override public void translate(String solver, int bitwidth, int maxseq, int skolemDepth, int symmetry) { + sb.append(" Solver="+solver+" Bitwidth="+bitwidth+" MaxSeq="+maxseq+" Symmetry="+(symmetry>0 ? (""+symmetry) : "OFF")+"\n"); + } + + @Override public void solve(int primaryVars, int totalVars, int clauses) { + if (db) db(" "+totalVars+" vars. "+primaryVars+" primary vars. "+clauses+" clauses.\n"); + sb.append(" "+totalVars+" vars. "+primaryVars+" primary vars. "+clauses+" clauses. 12345ms.\n"); + } + + @Override public void resultCNF(String filename) {} + + @Override public void resultSAT(Object command, long solvingTime, Object solution) { + if (db) db(" SAT!\n"); + if (!(command instanceof Command)) return; + Command cmd = (Command)command; + sb.append(cmd.check ? " Counterexample found. " : " Instance found. "); + if (cmd.check) sb.append("Assertion is invalid"); else sb.append("Predicate is consistent"); + if (cmd.expects==0) sb.append(", contrary to expectation"); else if (cmd.expects==1) sb.append(", as expected"); + sb.append(". "+solvingTime+"ms.\n\n"); + } + + @Override public void resultUNSAT(Object command, long solvingTime, Object solution) { + if (db) db(" UNSAT!\n"); + if (!(command instanceof Command)) return; + Command cmd = (Command)command; + sb.append(cmd.check ? " No counterexample found." : " No instance found."); + if (cmd.check) sb.append(" Assertion may be valid"); else sb.append(" Predicate may be inconsistent"); + if (cmd.expects==1) sb.append(", contrary to expectation"); else if (cmd.expects==0) sb.append(", as expected"); + sb.append(". "+solvingTime+"ms.\n\n"); + } + } + + private static boolean db=true; + + private static void db(String msg) { System.out.print(msg); System.out.flush(); } + + private SimpleCLI() { } + + private static void validate(A4Solution sol) throws Exception { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + sol.writeXML(pw, null, null); + pw.flush(); + sw.flush(); + String txt = sw.toString(); + A4SolutionReader.read(new ArrayList(), new XMLNode(new StringReader(txt))).toString(); + StaticInstanceReader.parseInstance(new StringReader(txt)); + } + + public static void main(String[] args) throws Exception { + final boolean sat4j = "yes".equals(System.getProperty("sat4j")); + final boolean minisat = "yes".equals(System.getProperty("minisat")); + SatSolver solver = A4Options.SatSolver.make("mem", "mem", "/zweb/sat/mem"); + final SimpleReporter rep = new SimpleReporter(); + final StringBuilder sb = rep.sb; + for(String filename:args) { + try { + // Parse+Typecheck + rep.sb.append("\n\nMain file = "+filename+"\n"); + if (db) db("Parsing+Typechecking..."); + Module world = CompUtil.parseEverything_fromFile(rep, null, filename); + if (db) db(" ok\n"); + List cmds=world.getAllCommands(); + for(ErrorWarning msg: rep.warnings) rep.sb.append("Relevance Warning:\n" + (msg.toString().trim()) + "\n\n"); + rep.warnings.clear(); + // Do a detailed dump if we will not be executing the commands + if (args.length!=1) { + for(Module m:world.getAllReachableModules()) { + for(Sig x:m.getAllSigs()) { + sb.append("\nSig ").append(x.label).append(" at position ").append(x.pos).append("\n"); + for(Decl d:x.getFieldDecls()) for(ExprHasName f:d.names) { + sb.append("\nField ").append(f.label).append(" with type ").append(f.type()).append("\n"); + d.expr.toString(sb, 2); + } + rep.flush(); + } + for(Func x:m.getAllFunc()) { + sb.append("\nFun/pred ").append(x.label).append(" at position ").append(x.pos).append("\n"); + for(Decl d:x.decls) for(ExprHasName v:d.names) { v.toString(sb, 2); d.expr.toString(sb, 4); } + x.returnDecl.toString(sb, 2); + x.getBody().toString(sb, 4); + rep.flush(); + } + for(Pair x:m.getAllFacts()) { + sb.append("\nFact ").append(x.a).append("\n"); x.b.toString(sb, 4); + rep.flush(); + } + for(Pair x:m.getAllAssertions()) { + sb.append("\nAssertion ").append(x.a).append("\n"); x.b.toString(sb, 4); + rep.flush(); + } + if (m==world) for(Command x:m.getAllCommands()) { + sb.append("\nCommand ").append(x.label).append("\n"); + x.formula.toString(sb, 4); + rep.flush(); + } + } + continue; + } + // Validate the metamodel generation code + StringWriter metasb = new StringWriter(); + PrintWriter meta = new PrintWriter(metasb); + Util.encodeXMLs(meta, "\n\n\n"); + A4SolutionWriter.writeMetamodel(world.getAllReachableSigs(), filename, meta); + Util.encodeXMLs(meta, "\n"); + meta.flush(); + metasb.flush(); + String metaxml = metasb.toString(); + A4SolutionReader.read(new ArrayList(), new XMLNode(new StringReader(metaxml))); + StaticInstanceReader.parseInstance(new StringReader(metaxml)); + // Okay, now solve the commands + A4Options options = new A4Options(); + options.originalFilename = filename; + options.solverDirectory = "/zweb/zweb/tmp/alloy4/x86-freebsd"; + options.solver = sat4j ? A4Options.SatSolver.SAT4J : (minisat ? A4Options.SatSolver.MiniSatJNI : solver); + for (int i=0; i60) cc=cc.substring(0,55); + db("Executing "+cc+"...\n"); + } + rep.sb.append("Executing \""+c+"\"\n"); + options.skolemDepth=0; + A4Solution s = TranslateAlloyToKodkod.execute_commandFromBook(rep, world.getAllReachableSigs(), c, options); + if (s.satisfiable()) { validate(s); if (s.isIncremental()) { s=s.next(); if (s.satisfiable()) validate(s); } } + options.skolemDepth=2; + s = TranslateAlloyToKodkod.execute_commandFromBook(rep, world.getAllReachableSigs(), c, options); + if (s.satisfiable()) { validate(s); if (s.isIncremental()) { s=s.next(); if (s.satisfiable()) validate(s); } } + } + } catch(Throwable ex) { + rep.sb.append("\n\nException: "+ex); + } + if (db) { if (args.length!=1) db(" ERROR!\n"); else db("\n\n"); } + } + rep.close(); + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4whole/SimpleGUI.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4whole/SimpleGUI.java new file mode 100644 index 00000000..6e65a622 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4whole/SimpleGUI.java @@ -0,0 +1,2067 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4whole; + +import static edu.mit.csail.sdg.alloy4.OurUtil.menu; +import static edu.mit.csail.sdg.alloy4.OurUtil.menuItem; +import static java.awt.event.KeyEvent.VK_A; +import static java.awt.event.KeyEvent.VK_ALT; +import static java.awt.event.KeyEvent.VK_E; +import static java.awt.event.KeyEvent.VK_PAGE_DOWN; +import static java.awt.event.KeyEvent.VK_PAGE_UP; +import static java.awt.event.KeyEvent.VK_SHIFT; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Container; +import java.awt.Font; +import java.awt.GraphicsEnvironment; +import java.awt.Toolkit; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.ComponentEvent; +import java.awt.event.ComponentListener; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Random; +import java.util.Scanner; +import java.util.Set; +import java.util.prefs.Preferences; + +import javax.swing.Box; +import javax.swing.Icon; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JComboBox; +import javax.swing.JEditorPane; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JMenu; +import javax.swing.JMenuBar; +import javax.swing.JMenuItem; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JSeparator; +import javax.swing.JSplitPane; +import javax.swing.JTextArea; +import javax.swing.JTextField; +import javax.swing.JToolBar; +import javax.swing.KeyStroke; +import javax.swing.SwingUtilities; +import javax.swing.Timer; +import javax.swing.UIManager; +import javax.swing.WindowConstants; +import javax.swing.border.EmptyBorder; +import javax.swing.border.LineBorder; +import javax.swing.event.HyperlinkEvent; +import javax.swing.event.HyperlinkListener; +import javax.swing.text.html.HTMLDocument; + +import kodkod.engine.fol2sat.HigherOrderDeclException; +import edu.mit.csail.sdg.alloy4.A4Reporter; +import edu.mit.csail.sdg.alloy4.Computer; +import edu.mit.csail.sdg.alloy4.Err; +import edu.mit.csail.sdg.alloy4.ErrorFatal; +import edu.mit.csail.sdg.alloy4.ErrorType; +import edu.mit.csail.sdg.alloy4.Listener; +import edu.mit.csail.sdg.alloy4.MacUtil; +import edu.mit.csail.sdg.alloy4.MailBug; +import edu.mit.csail.sdg.alloy4.OurAntiAlias; +import edu.mit.csail.sdg.alloy4.OurBorder; +import edu.mit.csail.sdg.alloy4.OurCombobox; +import edu.mit.csail.sdg.alloy4.OurDialog; +import edu.mit.csail.sdg.alloy4.OurSyntaxWidget; +import edu.mit.csail.sdg.alloy4.OurTabbedSyntaxWidget; +import edu.mit.csail.sdg.alloy4.OurTree; +import edu.mit.csail.sdg.alloy4.OurUtil; +import edu.mit.csail.sdg.alloy4.Pair; +import edu.mit.csail.sdg.alloy4.Pos; +import edu.mit.csail.sdg.alloy4.Runner; +import edu.mit.csail.sdg.alloy4.Subprocess; +import edu.mit.csail.sdg.alloy4.Util; +import edu.mit.csail.sdg.alloy4.Util.BooleanPref; +import edu.mit.csail.sdg.alloy4.Util.IntPref; +import edu.mit.csail.sdg.alloy4.Util.StringPref; +import edu.mit.csail.sdg.alloy4.Version; +import edu.mit.csail.sdg.alloy4.WorkerEngine; +import edu.mit.csail.sdg.alloy4.WorkerEngine.WorkerCallback; +import edu.mit.csail.sdg.alloy4.XMLNode; +import edu.mit.csail.sdg.alloy4compiler.ast.Browsable; +import edu.mit.csail.sdg.alloy4compiler.ast.Command; +import edu.mit.csail.sdg.alloy4compiler.ast.Expr; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprVar; +import edu.mit.csail.sdg.alloy4compiler.ast.Module; +import edu.mit.csail.sdg.alloy4compiler.ast.Sig; +import edu.mit.csail.sdg.alloy4compiler.ast.Sig.Field; +import edu.mit.csail.sdg.alloy4compiler.parser.CompUtil; +import edu.mit.csail.sdg.alloy4compiler.sim.SimInstance; +import edu.mit.csail.sdg.alloy4compiler.sim.SimTuple; +import edu.mit.csail.sdg.alloy4compiler.sim.SimTupleset; +import edu.mit.csail.sdg.alloy4compiler.translator.A4Options; +import edu.mit.csail.sdg.alloy4compiler.translator.A4Options.SatSolver; +import edu.mit.csail.sdg.alloy4compiler.translator.A4Solution; +import edu.mit.csail.sdg.alloy4compiler.translator.A4SolutionReader; +import edu.mit.csail.sdg.alloy4compiler.translator.A4Tuple; +import edu.mit.csail.sdg.alloy4compiler.translator.A4TupleSet; +import edu.mit.csail.sdg.alloy4viz.VizGUI; +import edu.mit.csail.sdg.alloy4whole.SimpleReporter.SimpleCallback1; +import edu.mit.csail.sdg.alloy4whole.SimpleReporter.SimpleTask1; +import edu.mit.csail.sdg.alloy4whole.SimpleReporter.SimpleTask2; + +/** Simple graphical interface for accessing various features of the analyzer. + * + *

Except noted below, methods in this class can only be called by the AWT event thread. + * + *

The methods that might get called from other threads are: + *
(1) the run() method in SatRunner is launched from a fresh thread + *
(2) the run() method in the instance watcher (in constructor) is launched from a fresh thread + */ + +public final class SimpleGUI implements ComponentListener, Listener { + + /** The latest welcome screen; each time we update the welcome screen, we increment this number. */ + private static final int welcomeLevel = 2; + + // Verify that the graphics environment is set up + static { + try { + GraphicsEnvironment.getLocalGraphicsEnvironment(); + } catch(Throwable ex) { + System.err.println("Unable to start the graphical environment."); + System.err.println("If you're on Mac OS X:"); + System.err.println(" Please make sure you are running as the current local user."); + System.err.println("If you're on Linux or FreeBSD:"); + System.err.println(" Please make sure your X Windows is configured."); + System.err.println(" You can verify this by typing \"xhost\"; it should not give an error message."); + System.err.flush(); + System.exit(1); + } + } + + //======== The Preferences ======================================================================================// + //======== Note: you must make sure each preference has a unique key ============================================// + + /** The list of allowable memory sizes. */ + private List allowedMemorySizes; + + /** True if Alloy Analyzer should let warning be nonfatal. */ + private static final BooleanPref WarningNonfatal = new BooleanPref("WarningNonfatal"); + + /** True if Alloy Analyzer should automatically visualize the latest instance. */ + private static final BooleanPref AutoVisualize = new BooleanPref("AutoVisualize"); + + /** True if Alloy Analyzer should insist on antialias. */ + private static final BooleanPref AntiAlias = new BooleanPref("AntiAlias"); + + /** True if Alloy Analyzer should record the raw Kodkod input and output. */ + private static final BooleanPref RecordKodkod = new BooleanPref("RecordKodkod"); + + /** True if Alloy Analyzer should enable the new Implicit This name resolution. */ + private static final BooleanPref ImplicitThis = new BooleanPref("ImplicitThis"); + + /** True if Alloy Analyzer should not report models that overflow. */ + private static final BooleanPref NoOverflow = new BooleanPref("NoOverflow"); + + /** The latest X corrdinate of the Alloy Analyzer's main window. */ + private static final IntPref AnalyzerX = new IntPref("AnalyzerX",0,-1,65535); + + /** The latest Y corrdinate of the Alloy Analyzer's main window. */ + private static final IntPref AnalyzerY = new IntPref("AnalyzerY",0,-1,65535); + + /** The latest width of the Alloy Analyzer's main window. */ + private static final IntPref AnalyzerWidth = new IntPref("AnalyzerWidth",0,-1,65535); + + /** The latest height of the Alloy Analyzer's main window. */ + private static final IntPref AnalyzerHeight = new IntPref("AnalyzerHeight",0,-1,65535); + + /** The latest font size of the Alloy Analyzer. */ + private static final IntPref FontSize = new IntPref("FontSize",9,12,72); + + /** The latest font name of the Alloy Analyzer. */ + private static final StringPref FontName = new StringPref("FontName","Lucida Grande"); + + /** The latest tab distance of the Alloy Analyzer. */ + private static final IntPref TabSize = new IntPref("TabSize",1,2,16); + + /** The latest welcome screen that the user has seen. */ + private static final IntPref Welcome = new IntPref("Welcome",0,0,1000); + + /** Whether syntax highlighting should be disabled or not. */ + private static final BooleanPref SyntaxDisabled = new BooleanPref("SyntaxHighlightingDisabled"); + + /** The number of recursion unrolls. */ + private static final IntPref Unrolls = new IntPref("Unrolls", -1, -1, 3); + + /** The skolem depth. */ + private static final IntPref SkolemDepth = new IntPref("SkolemDepth3", 0, 1, 4); + + /** The unsat core minimization strategy. */ + private static final IntPref CoreMinimization = new IntPref("CoreMinimization",0,2,2); + + /** The unsat core granularity. */ + private static final IntPref CoreGranularity = new IntPref("CoreGranularity",0,0,3); + + /** The amount of memory (in M) to allocate for Kodkod and the SAT solvers. */ + private static final IntPref SubMemory = new IntPref("SubMemory",16,768,65535); + + /** The amount of stack (in K) to allocate for Kodkod and the SAT solvers. */ + private static final IntPref SubStack = new IntPref("SubStack",16,8192,65536); + + /** The first file in Alloy Analyzer's "open recent" list. */ + private static final StringPref Model0 = new StringPref("Model0"); + + /** The second file in Alloy Analyzer's "open recent" list. */ + private static final StringPref Model1 = new StringPref("Model1"); + + /** The third file in Alloy Analyzer's "open recent" list. */ + private static final StringPref Model2 = new StringPref("Model2"); + + /** The fourth file in Alloy Analyzer's "open recent" list. */ + private static final StringPref Model3 = new StringPref("Model3"); + + /** This enum defines the set of possible message verbosity levels. */ + private enum Verbosity { + /** Level 0. */ DEFAULT("0", "low"), + /** Level 1. */ VERBOSE("1", "medium"), + /** Level 2. */ DEBUG("2", "high"), + /** Level 3. */ FULLDEBUG("3", "debug only"); + /** Returns true if it is greater than or equal to "other". */ + public boolean geq(Verbosity other) { return ordinal() >= other.ordinal(); } + /** This is a unique String for this value; it should be kept consistent in future versions. */ + private final String id; + /** This is the label that the toString() method will return. */ + private final String label; + /** Constructs a new Verbosity value with the given id and label. */ + private Verbosity(String id, String label) { this.id=id; this.label=label; } + /** Given an id, return the enum value corresponding to it (if there's no match, then return DEFAULT). */ + private static Verbosity parse(String id) { + for(Verbosity vb: values()) if (vb.id.equals(id)) return vb; + return DEFAULT; + } + /** Returns the human-readable label for this enum value. */ + @Override public final String toString() { return label; } + /** Saves this value into the Java preference object. */ + private void set() { Preferences.userNodeForPackage(Util.class).put("Verbosity",id); } + /** Reads the current value of the Java preference object (if it's not set, then return DEFAULT). */ + private static Verbosity get() { return parse(Preferences.userNodeForPackage(Util.class).get("Verbosity","")); } + }; + + //===================================================================================================// + + /** The JFrame for the main window. */ + private JFrame frame; + + /** The JFrame for the visualizer window. */ + private VizGUI viz; + + /** The "File", "Edit", "Run", "Option", "Window", and "Help" menus. */ + private JMenu filemenu, editmenu, runmenu, optmenu, windowmenu, windowmenu2, helpmenu; + + /** The toolbar. */ + private JToolBar toolbar; + + /** The various toolbar buttons. */ + private JButton runbutton, stopbutton, showbutton; + + /** The Splitpane. */ + private JSplitPane splitpane; + + /** The JLabel that displays the current line/column position, etc. */ + private JLabel status; + + /** Whether the editor has the focus, or the log window has the focus. */ + private boolean lastFocusIsOnEditor = true; + + /** The text editor. */ + private OurTabbedSyntaxWidget text; + + /** The "message panel" on the right. */ + private SwingLogPanel log; + + /** The scrollpane containing the "message panel". */ + private JScrollPane logpane; + + /** The last "find" that the user issued. */ + private String lastFind = ""; + + /** The last find is case-sensitive or not. */ + private boolean lastFindCaseSensitive = true; + + /** The last find is forward or not. */ + private boolean lastFindForward = true; + + /** The icon for a "checked" menu item. */ + private static final Icon iconYes = OurUtil.loadIcon("images/menu1.gif"); + + /** The icon for an "unchecked" menu item. */ + private static final Icon iconNo = OurUtil.loadIcon("images/menu0.gif"); + + /** The system-specific file separator (forward-slash on UNIX, back-slash on Windows, etc.) */ + private static final String fs = System.getProperty("file.separator"); + + /** The darker background color (for the MessageLog window and the Toolbar and the Status Bar, etc.) */ + private static final Color background = new Color(0.9f, 0.9f, 0.9f); + + /** If subrunning==true: 0 means SAT solving; 1 means metamodel; 2 means enumeration. */ + private int subrunningTask = 0; + + /** The amount of memory (in MB) currently allocated for this.subprocess */ + private int subMemoryNow = 0; + + /** The amount of stack (in KB) currently allocated for this.subprocess */ + private int subStackNow = 0; + + /** The list of commands (this field will be cleared to null when the text buffer is edited). */ + private List commands = null; + + /** The latest executed command. */ + private int latestCommand = 0; + + /** The current choices of SAT solver. */ + private List satChoices; + + /** The most recent Alloy version (as queried from alloy.mit.edu); -1 if alloy.mit.edu has not replied yet. */ + private int latestAlloyVersion = (-1); + + /** The most recent Alloy version name (as queried from alloy.mit.edu); "unknown" if alloy.mit.edu has not replied yet. */ + private String latestAlloyVersionName = "unknown"; + + /** If it's not "", then it is the XML filename for the latest satisfying instance or the latest metamodel. */ + private String latestInstance = ""; + + /** If it's not "", then it is the latest instance or metamodel during the most recent click of "Execute". */ + private String latestAutoInstance = ""; + + /** If true, that means the event handlers should return a Runner encapsulating them, rather than perform the actual work. */ + private boolean wrap = false; + + //====== helper methods =================================================// + + /** Inserts "filename" into the "recently opened file list". */ + private void addHistory(String filename) { + String name0=Model0.get(), name1=Model1.get(), name2=Model2.get(); + if (name0.equals(filename)) return; else {Model0.set(filename); Model1.set(name0);} + if (name1.equals(filename)) return; else Model2.set(name1); + if (name2.equals(filename)) return; else Model3.set(name2); + } + + /** Sets the flag "lastFocusIsOnEditor" to be true. */ + private Runner notifyFocusGained() { + if (wrap) return wrapMe(); + lastFocusIsOnEditor=true; + return null; + } + + /** Sets the flag "lastFocusIsOnEditor" to be false. */ + void notifyFocusLost() { lastFocusIsOnEditor=false; } + + /** Updates the status bar at the bottom of the screen. */ + private Runner notifyChange() { + if (wrap) return wrapMe(); + commands=null; + if (text==null) return null; // If this was called prior to the "text" being fully initialized + OurSyntaxWidget t = text.get(); + if (Util.onMac()) frame.getRootPane().putClientProperty("windowModified", Boolean.valueOf(t.modified())); + if (t.isFile()) frame.setTitle(t.getFilename()); else frame.setTitle("Alloy Analyzer "+Version.version()); + toolbar.setBorder(new OurBorder(false, false, text.count()<=1, false)); + int c = t.getCaret(); + int y = t.getLineOfOffset(c)+1; + int x = c - t.getLineStartOffset(y-1)+1; + status.setText("  Line "+y+", Column "+x + +(t.modified()?" [modified]":"")); + return null; + } + + /** Helper method that returns a hopefully very short name for a file name. */ + public static String slightlyShorterFilename(String name) { + if (name.toLowerCase(Locale.US).endsWith(".als")) { + int i=name.lastIndexOf('/'); + if (i>=0) name=name.substring(i+1); + i=name.lastIndexOf('\\'); + if (i>=0) name=name.substring(i+1); + return name.substring(0, name.length()-4); + } else if (name.toLowerCase(Locale.US).endsWith(".xml")) { + int i=name.lastIndexOf('/'); + if (i>0) i=name.lastIndexOf('/', i-1); + if (i>=0) name=name.substring(i+1); + i=name.lastIndexOf('\\'); + if (i>0) i=name.lastIndexOf('\\', i-1); + if (i>=0) name=name.substring(i+1); + return name.substring(0, name.length()-4); + } + return name; + } + + /** Copy the required files from the JAR into a temporary directory. */ + private void copyFromJAR() { + // Compute the appropriate platform + String os = System.getProperty("os.name").toLowerCase(Locale.US).replace(' ','-'); + if (os.startsWith("mac-")) os="mac"; else if (os.startsWith("windows-")) os="windows"; + String arch = System.getProperty("os.arch").toLowerCase(Locale.US).replace(' ','-'); + if (arch.equals("powerpc")) arch="ppc-"+os; else arch=arch.replaceAll("\\Ai[3456]86\\z","x86")+"-"+os; + if (os.equals("mac")) arch="x86-mac"; // our pre-compiled binaries are all universal binaries + // Find out the appropriate Alloy directory + final String platformBinary = alloyHome() + fs + "binary"; + // Write a few test files + try { + (new File(platformBinary)).mkdirs(); + Util.writeAll(platformBinary + fs + "tmp.cnf", "p cnf 3 1\n1 0\n"); + } catch(Err er) { + // The error will be caught later by the "berkmin" or "spear" test + } + // Copy the platform-dependent binaries + Util.copy(true, false, platformBinary, + arch+"/libminisat.so", arch+"/libminisatx1.so", arch+"/libminisat.jnilib", + arch+"/libminisatprover.so", arch+"/libminisatproverx1.so", arch+"/libminisatprover.jnilib", + arch+"/libzchaff.so", arch+"/libzchaffx1.so", arch+"/libzchaff.jnilib", + arch+"/berkmin", arch+"/spear"); + Util.copy(false, false, platformBinary, + arch+"/minisat.dll", arch+"/minisatprover.dll", arch+"/zchaff.dll", + arch+"/berkmin.exe", arch+"/spear.exe"); + // Copy the model files + Util.copy(false, true, alloyHome(), + "models/book/appendixA/addressBook1.als", "models/book/appendixA/addressBook2.als", "models/book/appendixA/barbers.als", + "models/book/appendixA/closure.als", "models/book/appendixA/distribution.als", "models/book/appendixA/phones.als", + "models/book/appendixA/prison.als", "models/book/appendixA/properties.als", "models/book/appendixA/ring.als", + "models/book/appendixA/spanning.als", "models/book/appendixA/tree.als", "models/book/appendixA/tube.als", "models/book/appendixA/undirected.als", + "models/book/appendixE/hotel.thm", "models/book/appendixE/p300-hotel.als", "models/book/appendixE/p303-hotel.als", "models/book/appendixE/p306-hotel.als", + "models/book/chapter2/addressBook1a.als", "models/book/chapter2/addressBook1b.als", "models/book/chapter2/addressBook1c.als", + "models/book/chapter2/addressBook1d.als", "models/book/chapter2/addressBook1e.als", "models/book/chapter2/addressBook1f.als", + "models/book/chapter2/addressBook1g.als", "models/book/chapter2/addressBook1h.als", "models/book/chapter2/addressBook2a.als", + "models/book/chapter2/addressBook2b.als", "models/book/chapter2/addressBook2c.als", "models/book/chapter2/addressBook2d.als", + "models/book/chapter2/addressBook2e.als", "models/book/chapter2/addressBook3a.als", "models/book/chapter2/addressBook3b.als", + "models/book/chapter2/addressBook3c.als", "models/book/chapter2/addressBook3d.als", "models/book/chapter2/theme.thm", + "models/book/chapter4/filesystem.als", "models/book/chapter4/grandpa1.als", + "models/book/chapter4/grandpa2.als", "models/book/chapter4/grandpa3.als", "models/book/chapter4/lights.als", + "models/book/chapter5/addressBook.als", "models/book/chapter5/lists.als", "models/book/chapter5/sets1.als", "models/book/chapter5/sets2.als", + "models/book/chapter6/hotel.thm", "models/book/chapter6/hotel1.als", "models/book/chapter6/hotel2.als", + "models/book/chapter6/hotel3.als", "models/book/chapter6/hotel4.als", "models/book/chapter6/mediaAssets.als", + "models/book/chapter6/memory/abstractMemory.als", "models/book/chapter6/memory/cacheMemory.als", + "models/book/chapter6/memory/checkCache.als", "models/book/chapter6/memory/checkFixedSize.als", + "models/book/chapter6/memory/fixedSizeMemory.als", "models/book/chapter6/memory/fixedSizeMemory_H.als", + "models/book/chapter6/ringElection.thm", "models/book/chapter6/ringElection1.als", "models/book/chapter6/ringElection2.als", + "models/examples/algorithms/dijkstra.als", "models/examples/algorithms/dijkstra.thm", + "models/examples/algorithms/messaging.als", "models/examples/algorithms/messaging.thm", + "models/examples/algorithms/opt_spantree.als", "models/examples/algorithms/opt_spantree.thm", + "models/examples/algorithms/peterson.als", + "models/examples/algorithms/ringlead.als", "models/examples/algorithms/ringlead.thm", + "models/examples/algorithms/s_ringlead.als", + "models/examples/algorithms/stable_mutex_ring.als", "models/examples/algorithms/stable_mutex_ring.thm", + "models/examples/algorithms/stable_orient_ring.als", "models/examples/algorithms/stable_orient_ring.thm", + "models/examples/algorithms/stable_ringlead.als", "models/examples/algorithms/stable_ringlead.thm", + "models/examples/case_studies/INSLabel.als", "models/examples/case_studies/chord.als", + "models/examples/case_studies/chord2.als", "models/examples/case_studies/chordbugmodel.als", + "models/examples/case_studies/com.als", "models/examples/case_studies/firewire.als", "models/examples/case_studies/firewire.thm", + "models/examples/case_studies/ins.als", "models/examples/case_studies/iolus.als", + "models/examples/case_studies/sync.als", "models/examples/case_studies/syncimpl.als", + "models/examples/puzzles/farmer.als", "models/examples/puzzles/farmer.thm", + "models/examples/puzzles/handshake.als", "models/examples/puzzles/handshake.thm", + "models/examples/puzzles/hanoi.als", "models/examples/puzzles/hanoi.thm", + "models/examples/systems/file_system.als", "models/examples/systems/file_system.thm", + "models/examples/systems/javatypes_soundness.als", + "models/examples/systems/lists.als", "models/examples/systems/lists.thm", + "models/examples/systems/marksweepgc.als", "models/examples/systems/views.als", + "models/examples/toys/birthday.als", "models/examples/toys/birthday.thm", + "models/examples/toys/ceilingsAndFloors.als", "models/examples/toys/ceilingsAndFloors.thm", + "models/examples/toys/genealogy.als", "models/examples/toys/genealogy.thm", + "models/examples/toys/grandpa.als", "models/examples/toys/grandpa.thm", + "models/examples/toys/javatypes.als", "models/examples/toys/life.als", "models/examples/toys/life.thm", + "models/examples/toys/numbering.als", "models/examples/toys/railway.als", "models/examples/toys/railway.thm", + "models/examples/toys/trivial.als", + "models/examples/tutorial/farmer.als", + "models/util/boolean.als", "models/util/graph.als", "models/util/integer.als", "models/util/natural.als", + "models/util/ordering.als", "models/util/relation.als", "models/util/seqrel.als", "models/util/sequence.als", + "models/util/sequniv.als", "models/util/ternary.als", "models/util/time.als" + ); + // Record the locations + System.setProperty("alloy.theme0", alloyHome() + fs + "models"); + System.setProperty("alloy.home", alloyHome()); + } + + /** Called when this window is resized. */ + public void componentResized(ComponentEvent e) { + componentMoved(e); + } + + /** Called when this window is moved. */ + public void componentMoved(ComponentEvent e) { + AnalyzerWidth.set(frame.getWidth()); + AnalyzerHeight.set(frame.getHeight()); + AnalyzerX.set(frame.getX()); + AnalyzerY.set(frame.getY()); + } + + /** Called when this window is shown. */ + public void componentShown(ComponentEvent e) {} + + /** Called when this window is hidden. */ + public void componentHidden(ComponentEvent e) {} + + /** Wraps the calling method into a Runnable whose run() will call the calling method with (false) as the only argument. */ + private Runner wrapMe() { + final String name; + try { throw new Exception(); } catch(Exception ex) { name = ex.getStackTrace()[1].getMethodName(); } + Method[] methods = getClass().getDeclaredMethods(); + Method m=null; + for(int i=0; i=0) ans=ans+tmp; + } + } + else if (x.isFile()) { + long tmp=x.length(); + if (ans>=0) ans=ans+tmp; + } + if (delete) x.delete(); + return ans; + } + + //===============================================================================================================// + + /** This method refreshes the "file" menu. */ + private Runner doRefreshFile() { + if (wrap) return wrapMe(); + try { + wrap = true; + filemenu.removeAll(); + menuItem(filemenu, "New", 'N', 'N', doNew()); + menuItem(filemenu, "Open...", 'O', 'O', doOpen()); + if (!Util.onMac()) + menuItem(filemenu, "Open Sample Models...", VK_ALT, 'O', doBuiltin()); + else + menuItem(filemenu, "Open Sample Models...", doBuiltin()); + JMenu recentmenu; + filemenu.add(recentmenu = new JMenu("Open Recent")); + menuItem(filemenu, "Reload all", 'R', 'R', doReloadAll()); + menuItem(filemenu, "Save", 'S', 'S', doSave()); + if (Util.onMac()) + menuItem(filemenu, "Save As...", VK_SHIFT, 'S', doSaveAs()); + else + menuItem(filemenu, "Save As...", 'A', doSaveAs()); + menuItem(filemenu, "Close", 'W', 'W', doClose()); + menuItem(filemenu, "Clear Temporary Directory", doClearTemp()); + menuItem(filemenu, "Quit", 'Q', (Util.onMac() ? -1 : 'Q'), doQuit()); + boolean found = false; + for(Util.StringPref p: new Util.StringPref[]{ Model0, Model1, Model2, Model3 }) { + String name = p.get(); + if (name.length()>0) { found = true; menuItem(recentmenu, name, doOpenFile(name)); } + } + recentmenu.addSeparator(); + menuItem(recentmenu, "Clear Menu", doClearRecent()); + recentmenu.setEnabled(found); + } finally { + wrap = false; + } + return null; + } + + /** This method performs File->New. */ + private Runner doNew() { + if (!wrap) { text.newtab(null); notifyChange(); doShow(); } + return wrapMe(); + } + + /** This method performs File->Open. */ + private Runner doOpen() { + if (wrap) return wrapMe(); + File file=OurDialog.askFile(true, null, ".als", ".als files"); + if (file!=null) { + Util.setCurrentDirectory(file.getParentFile()); + doOpenFile(file.getPath()); + } + return null; + } + + /** This method performs File->OpenBuiltinModels. */ + private Runner doBuiltin() { + if (wrap) return wrapMe(); + File file=OurDialog.askFile(true, alloyHome() + fs + "models", ".als", ".als files"); + if (file!=null) { + doOpenFile(file.getPath()); + } + return null; + } + + /** This method performs File->ReloadAll. */ + private Runner doReloadAll() { + if (!wrap) text.reloadAll(); + return wrapMe(); + } + + /** This method performs File->ClearRecentFiles. */ + private Runner doClearRecent() { + if (!wrap) { Model0.set(""); Model1.set(""); Model2.set(""); Model3.set(""); } + return wrapMe(); + } + + /** This method performs File->Save. */ + private Runner doSave() { + if (!wrap) { + String ans = text.save(false); + if (ans==null) return null; + notifyChange(); + addHistory(ans); + log.clearError(); + } + return wrapMe(); + } + + /** This method performs File->SaveAs. */ + private Runner doSaveAs() { + if (!wrap) { + String ans = text.save(true); + if (ans==null) return null; + notifyChange(); + addHistory(ans); + log.clearError(); + } + return wrapMe(); + } + + /** This method clears the temporary files and then reinitialize the temporary directory. */ + private Runner doClearTemp() { + if (!wrap) { + clearTemporarySpace(); + copyFromJAR(); + log.logBold("Temporary directory has been cleared.\n\n"); + log.logDivider(); + log.flush(); + } + return wrapMe(); + } + + /** This method performs File->Close. */ + private Runner doClose() { + if (!wrap) text.close(); + return wrapMe(); + } + + /** This method performs File->Quit. */ + private Runner doQuit() { + if (!wrap) if (text.closeAll()) { + try { WorkerEngine.stop(); } finally { System.exit(0); } + } + return wrapMe(); + } + + //===============================================================================================================// + + /** This method refreshes the "edit" menu. */ + private Runner doRefreshEdit() { + if (wrap) return wrapMe(); + try { + wrap = true; + boolean canUndo = text.get().canUndo(); + boolean canRedo = text.get().canRedo(); + editmenu.removeAll(); + menuItem(editmenu, "Undo", 'Z', 'Z', doUndo(), canUndo); + if (Util.onMac()) + menuItem(editmenu, "Redo", VK_SHIFT, 'Z', doRedo(), canRedo); + else + menuItem(editmenu, "Redo", 'Y', 'Y', doRedo(), canRedo); + editmenu.addSeparator(); + menuItem(editmenu, "Cut", 'X', 'X', doCut()); + menuItem(editmenu, "Copy", 'C', 'C', doCopy()); + menuItem(editmenu, "Paste", 'V', 'V', doPaste()); + editmenu.addSeparator(); + menuItem(editmenu, "Go To..." , 'T', 'T', doGoto()); + menuItem(editmenu, "Previous File" , VK_PAGE_UP, VK_PAGE_UP, doGotoPrevFile(), text.count()>1); + menuItem(editmenu, "Next File" , VK_PAGE_DOWN, VK_PAGE_DOWN, doGotoNextFile(), text.count()>1); + editmenu.addSeparator(); + menuItem(editmenu, "Find...", 'F', 'F', doFind()); + menuItem(editmenu, "Find Next", 'G', 'G', doFindNext()); + } finally { + wrap = false; + } + return null; + } + + /** This method performs Edit->Undo. */ + private Runner doUndo() { + if (!wrap) text.get().undo(); + return wrapMe(); + } + + /** This method performs Edit->Redo. */ + private Runner doRedo() { + if (!wrap) text.get().redo(); + return wrapMe(); + } + + /** This method performs Edit->Copy. */ + private Runner doCopy() { + if (!wrap) { if (lastFocusIsOnEditor) text.get().copy(); else log.copy(); } + return wrapMe(); + } + + /** This method performs Edit->Cut. */ + private Runner doCut() { + if (!wrap && lastFocusIsOnEditor) text.get().cut(); + return wrapMe(); + } + + /** This method performs Edit->Paste. */ + private Runner doPaste() { + if (!wrap && lastFocusIsOnEditor) text.get().paste(); + return wrapMe(); + } + + /** This method performs Edit->Find. */ + private Runner doFind() { + if (wrap) return wrapMe(); + JTextField x = OurUtil.textfield(lastFind,30); + x.selectAll(); + JCheckBox c = new JCheckBox("Case Sensitive?",lastFindCaseSensitive); + c.setMnemonic('c'); + JCheckBox b = new JCheckBox("Search Backward?",!lastFindForward); + b.setMnemonic('b'); + if (!OurDialog.getInput("Find", "Text:", x, " ", c, b)) return null; + if (x.getText().length() == 0) return null; + lastFind = x.getText(); + lastFindCaseSensitive = c.getModel().isSelected(); + lastFindForward = !b.getModel().isSelected(); + doFindNext(); + return null; + } + + /** This method performs Edit->FindNext. */ + private Runner doFindNext() { + if (wrap) return wrapMe(); + if (lastFind.length()==0) return null; + OurSyntaxWidget t = text.get(); + String all = t.getText(); + int i = Util.indexOf(all, lastFind, t.getCaret()+(lastFindForward?0:-1),lastFindForward,lastFindCaseSensitive); + if (i<0) { + i=Util.indexOf(all, lastFind, lastFindForward?0:(all.length()-1), lastFindForward, lastFindCaseSensitive); + if (i<0) { log.logRed("The specified search string cannot be found."); return null; } + log.logRed("Search wrapped."); + } else { + log.clearError(); + } + if (lastFindForward) t.moveCaret(i, i+lastFind.length()); else t.moveCaret(i+lastFind.length(), i); + t.requestFocusInWindow(); + return null; + } + + /** This method performs Edit->Goto. */ + private Runner doGoto() { + if (wrap) return wrapMe(); + JTextField y = OurUtil.textfield("", 10); + JTextField x = OurUtil.textfield("", 10); + if (!OurDialog.getInput("Go To", "Line Number:", y, "Column Number (optional):", x)) return null; + try { + OurSyntaxWidget t = text.get(); + int xx = 1, yy = Integer.parseInt(y.getText()), lineCount = t.getLineCount(); + if (yy<1) return null; + if (yy>lineCount) {log.logRed("This file only has "+lineCount+" line(s)."); return null;} + if (x.getText().length()!=0) xx=Integer.parseInt(x.getText()); + if (xx<1) {log.logRed("If the column number is specified, it must be 1 or greater."); return null;} + int caret = t.getLineStartOffset(yy-1); + int len = (yy==lineCount ? t.getText().length()+1 : t.getLineStartOffset(yy)) - caret; + if (xx>len) xx=len; + if (xx<1) xx=1; + t.moveCaret(caret+xx-1, caret+xx-1); + t.requestFocusInWindow(); + } catch(NumberFormatException ex) { + log.logRed("The number must be 1 or greater."); + } catch(Throwable ex) { + // This error is not important + } + return null; + } + + /** This method performs Edit->GotoPrevFile. */ + private Runner doGotoPrevFile() { + if (wrap) return wrapMe(); else {text.prev(); return null;} + } + + /** This method performs Edit->GotoNextFile. */ + private Runner doGotoNextFile() { + if (wrap) return wrapMe(); else {text.next(); return null;} + } + + //===============================================================================================================// + + /** This method refreshes the "run" menu. */ + private Runner doRefreshRun() { + if (wrap) return wrapMe(); + KeyStroke ac = KeyStroke.getKeyStroke(VK_E, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()); + try { + wrap = true; + runmenu.removeAll(); + menuItem(runmenu, "Execute Latest Command", 'E', 'E', doExecuteLatest()); + runmenu.add(new JSeparator()); + menuItem(runmenu, "Show Latest Instance", 'L', 'L', doShowLatest(), latestInstance.length()>0); + menuItem(runmenu, "Show Metamodel", 'M', 'M', doShowMetaModel()); + if (Version.experimental) menuItem(runmenu, "Show Parse Tree", 'P', doShowParseTree()); + menuItem(runmenu, "Open Evaluator", 'V', doLoadEvaluator()); + } finally { + wrap = false; + } + List cp = commands; + if (cp==null) { + try { + cp=CompUtil.parseOneModule_fromString(text.get().getText()); + } + catch(Err e) { + commands = null; + runmenu.getItem(0).setEnabled(false); + runmenu.getItem(3).setEnabled(false); + text.shade(new Pos(text.get().getFilename(), e.pos.x, e.pos.y, e.pos.x2, e.pos.y2)); + if ("yes".equals(System.getProperty("debug")) && Verbosity.get()==Verbosity.FULLDEBUG) + log.logRed("Fatal Exception!" + e.dump() + "\n\n"); + else + log.logRed(e.toString()+"\n\n"); + return null; + } + catch(Throwable e) { + commands = null; + runmenu.getItem(0).setEnabled(false); + runmenu.getItem(3).setEnabled(false); + log.logRed("Cannot parse the model.\n"+e.toString()+"\n\n"); + return null; + } + commands=cp; + } + text.clearShade(); + log.clearError(); // To clear any residual error message + if (cp==null) { runmenu.getItem(0).setEnabled(false); runmenu.getItem(3).setEnabled(false); return null; } + if (cp.size()==0) { runmenu.getItem(0).setEnabled(false); return null; } + if (latestCommand>=cp.size()) latestCommand=cp.size()-1; + runmenu.remove(0); + try { + wrap = true; + for(int i=0; i=2) { + JMenuItem y = new JMenuItem("Execute All", null); + y.setMnemonic(VK_A); + y.addActionListener(doRun(-1)); + runmenu.add(y,0); + runmenu.add(new JSeparator(),1); + } + } finally { + wrap = false; + } + return null; + } + + /** This method executes a particular RUN or CHECK command. */ + private Runner doRun(Integer commandIndex) { + if (wrap) return wrapMe(commandIndex); + final int index = commandIndex; + if (WorkerEngine.isBusy()) return null; + if (index==(-2)) subrunningTask=1; else subrunningTask=0; + latestAutoInstance=""; + if (index>=0) latestCommand=index; + if (index==-1 && commands!=null) { + latestCommand=commands.size()-1; + if (latestCommand<0) latestCommand=0; + } + // To update the accelerator to point to the command actually chosen + doRefreshRun(); + OurUtil.enableAll(runmenu); + if (commands==null) return null; + if (commands.size()==0 && index!=-2 && index!=-3) { log.logRed("There are no commands to execute.\n\n"); return null; } + int i=index; + if (i>=commands.size()) i=commands.size()-1; + SimpleCallback1 cb = new SimpleCallback1(this, null, log, Verbosity.get().ordinal(), latestAlloyVersionName, latestAlloyVersion); + SimpleTask1 task = new SimpleTask1(); + A4Options opt = new A4Options(); + opt.tempDirectory = alloyHome() + fs + "tmp"; + opt.solverDirectory = alloyHome() + fs + "binary"; + opt.recordKodkod = RecordKodkod.get(); + opt.noOverflow = NoOverflow.get(); + opt.unrolls = Version.experimental ? Unrolls.get() : (-1); + opt.skolemDepth = SkolemDepth.get(); + opt.coreMinimization = CoreMinimization.get(); + opt.coreGranularity = CoreGranularity.get(); + opt.originalFilename = Util.canon(text.get().getFilename()); + opt.solver = SatSolver.get(); + task.bundleIndex = i; + task.bundleWarningNonFatal = WarningNonfatal.get(); + task.map = text.takeSnapshot(); + task.options = opt.dup(); + task.resolutionMode = (Version.experimental && ImplicitThis.get()) ? 2 : 1; + task.tempdir = maketemp(); + try { + runmenu.setEnabled(false); + runbutton.setVisible(false); + showbutton.setEnabled(false); + stopbutton.setVisible(true); + int newmem = SubMemory.get(), newstack = SubStack.get(); + if (newmem != subMemoryNow || newstack != subStackNow) WorkerEngine.stop(); + if ("yes".equals(System.getProperty("debug")) && Verbosity.get()==Verbosity.FULLDEBUG) + WorkerEngine.runLocally(task, cb); + else + WorkerEngine.run(task, newmem, newstack, alloyHome() + fs + "binary", "", cb); + subMemoryNow = newmem; + subStackNow = newstack; + } catch(Throwable ex) { + WorkerEngine.stop(); + log.logBold("Fatal Error: Solver failed due to unknown reason.\n" + + "One possible cause is that, in the Options menu, your specified\n" + + "memory size is larger than the amount allowed by your OS.\n" + + "Also, please make sure \"java\" is in your program path.\n"); + log.logDivider(); + log.flush(); + doStop(2); + } + return null; + } + + /** This method stops the current run or check (how==0 means DONE, how==1 means FAIL, how==2 means STOP). */ + Runner doStop(Integer how) { + if (wrap) return wrapMe(how); + int h = how; + if (h!=0) { + if (h==2 && WorkerEngine.isBusy()) { WorkerEngine.stop(); log.logBold("\nSolving Stopped.\n"); log.logDivider(); } + WorkerEngine.stop(); + } + runmenu.setEnabled(true); + runbutton.setVisible(true); + showbutton.setEnabled(true); + stopbutton.setVisible(false); + if (latestAutoInstance.length()>0) { + String f=latestAutoInstance; + latestAutoInstance=""; + if (subrunningTask==2) viz.loadXML(f, true); else if (AutoVisualize.get() || subrunningTask==1) doVisualize("XML: "+f); + } + return null; + } + + /** This method executes the latest command. */ + private Runner doExecuteLatest() { + if (wrap) return wrapMe(); + doRefreshRun(); + OurUtil.enableAll(runmenu); + if (commands==null) return null; + int n=commands.size(); + if (n<=0) { log.logRed("There are no commands to execute.\n\n"); return null; } + if (latestCommand>=n) latestCommand=n-1; + if (latestCommand<0) latestCommand=0; + return doRun(latestCommand); + } + + /** This method displays the parse tree. */ + private Runner doShowParseTree() { + if (wrap) return wrapMe(); + doRefreshRun(); + OurUtil.enableAll(runmenu); + if (commands!=null) { + Module world = null; + try { + int resolutionMode = (Version.experimental && ImplicitThis.get()) ? 2 : 1; + A4Options opt = new A4Options(); + opt.tempDirectory = alloyHome() + fs + "tmp"; + opt.solverDirectory = alloyHome() + fs + "binary"; + opt.originalFilename = Util.canon(text.get().getFilename()); + world = CompUtil.parseEverything_fromFile(A4Reporter.NOP, text.takeSnapshot(), opt.originalFilename, resolutionMode); + } catch(Err er) { + text.shade(er.pos); + log.logRed(er.toString()+"\n\n"); + return null; + } + world.showAsTree(this); + } + return null; + } + + /** This method displays the meta model. */ + private Runner doShowMetaModel() { + if (wrap) return wrapMe(); + doRefreshRun(); + OurUtil.enableAll(runmenu); + if (commands!=null) doRun(-2); + return null; + } + + /** This method displays the latest instance. */ + private Runner doShowLatest() { + if (wrap) return wrapMe(); + if (latestInstance.length()==0) + log.logRed("No previous instances are available for viewing.\n\n"); + else + doVisualize("XML: "+latestInstance); + return null; + } + + /** This method happens when the user tries to load the evaluator from the main GUI. */ + private Runner doLoadEvaluator() { + if (wrap) return wrapMe(); + log.logRed("Note: the evaluator is now in the visualizer.\n" + +"Just click the \"Evaluator\" toolbar button\n" + +"when an instance is shown in the visualizer.\n"); + log.flush(); + return null; + } + + //===============================================================================================================// + + /** This method refreshes the "Window" menu for either the SimpleGUI window (isViz==false) or the VizGUI window (isViz==true). */ + private Runner doRefreshWindow(Boolean isViz) { + if (wrap) return wrapMe(isViz); + try { + wrap = true; + JMenu w = (isViz ? windowmenu2 : windowmenu); + w.removeAll(); + if (isViz) { + viz.addMinMaxActions(w); + } else { + menuItem(w, "Minimize", 'M', doMinimize(), iconNo); + menuItem(w, "Zoom", doZoom(), iconNo); + } + w.addSeparator(); + int i = 0; + for(String f: text.getFilenames()) { + JMenuItem it = new JMenuItem("Model: "+slightlyShorterFilename(f)+(text.modified(i) ? " *" : ""), null); + it.setIcon((f.equals(text.get().getFilename()) && !isViz) ? iconYes : iconNo); + it.addActionListener(f.equals(text.get().getFilename()) ? doShow() : doOpenFile(f)); + w.add(it); + i++; + } + if (viz!=null) for(String f:viz.getInstances()) { + JMenuItem it = new JMenuItem("Instance: "+viz.getInstanceTitle(f), null); + it.setIcon((isViz && f.equals(viz.getXMLfilename())) ? iconYes : iconNo); + it.addActionListener(doVisualize("XML: "+f)); + w.add(it); + } + } finally { + wrap = false; + } + return null; + } + + /** This method minimizes the window. */ + private Runner doMinimize() { + if (wrap) return wrapMe(); else {OurUtil.minimize(frame); return null;} + } + + /** This method alternatingly maximizes or restores the window. */ + private Runner doZoom() { + if (wrap) return wrapMe(); else {OurUtil.zoom(frame); return null;} + } + + /** This method bring this window to the foreground. */ + private Runner doShow() { + if (wrap) return wrapMe(); + OurUtil.show(frame); + text.get().requestFocusInWindow(); + return null; + } + + //===============================================================================================================// + + /** This method refreshes the "Option" menu. */ + private Runner doRefreshOption() { + if (wrap) return wrapMe(); + try { + wrap = true; + optmenu.removeAll(); + menuItem(optmenu, "Welcome Message at Start Up: "+(Welcome.get() < welcomeLevel ? "Yes" : "No"), doOptWelcome()); + // + final SatSolver now = SatSolver.get(); + final JMenu sat = new JMenu("SAT Solver: "+now); + for(SatSolver sc:satChoices) { menuItem(sat, ""+sc, doOptSolver(sc), sc==now?iconYes:iconNo); } + optmenu.add(sat); + // + menuItem(optmenu, "Warnings are Fatal: "+(WarningNonfatal.get()?"No":"Yes"), doOptWarning()); + // + final int mem = SubMemory.get(); + final JMenu subMemoryMenu = new JMenu("Maximum Memory to Use: " + mem + "M"); + for(int n: allowedMemorySizes) { + menuItem(subMemoryMenu, ""+n+"M", doOptMemory(n), n==mem?iconYes:iconNo); + } + optmenu.add(subMemoryMenu); + // + final int stack = SubStack.get(); + final JMenu subStackMenu = new JMenu("Maximum Stack to Use: " + stack + "k"); + boolean debug = "yes".equals(System.getProperty("debug")); + for(int n: new int[]{16,32,64,128,256,512,1024,2048,4096,8192,16384,32768,65536}) { + if (debug || n>=1024) menuItem(subStackMenu, ""+n+"k", doOptStack(n), n==stack?iconYes:iconNo); + } + optmenu.add(subStackMenu); + // + final Verbosity vnow = Verbosity.get(); + final JMenu verb = new JMenu("Message Verbosity: "+vnow); + for(Verbosity vb: Verbosity.values()) { menuItem(verb, ""+vb, doOptVerbosity(vb), vb==vnow?iconYes:iconNo); } + optmenu.add(verb); + // + menuItem(optmenu, "Syntax Highlighting: "+(SyntaxDisabled.get()?"No":"Yes"), doOptSyntaxHighlighting()); + // + final int fontSize = FontSize.get(); + final JMenu size = new JMenu("Font Size: "+fontSize); + for(int n: new Integer[]{9,10,11,12,14,16,18,20,22,24,26,28,32,36,40,44,48,54,60,66,72}) { + menuItem(size, ""+n, doOptFontsize(n), n==fontSize?iconYes:iconNo); + } + optmenu.add(size); + // + menuItem(optmenu, "Font: "+FontName.get()+"...", doOptFontname()); + // + if (Util.onMac() || Util.onWindows()) menuItem(optmenu, "Use anti-aliasing: Yes", false); + else menuItem(optmenu, "Use anti-aliasing: "+(AntiAlias.get()?"Yes":"No"), doOptAntiAlias()); + // + final int tabSize = TabSize.get(); + final JMenu tabSizeMenu = new JMenu("Tab Size: "+tabSize); + for(int n=1; n<=12; n++) { menuItem(tabSizeMenu, ""+n, doOptTabsize(n), n==tabSize?iconYes:iconNo); } + optmenu.add(tabSizeMenu); + // + final int skDepth = SkolemDepth.get(); + final JMenu skDepthMenu = new JMenu("Skolem Depth: "+skDepth); + for(int n=0; n<=4; n++) { menuItem(skDepthMenu, ""+n, doOptSkolemDepth(n), n==skDepth?iconYes:iconNo); } + optmenu.add(skDepthMenu); + // + if (Version.experimental) { + final int unrolls = Unrolls.get(); + final JMenu unrollsMenu = new JMenu("Recursion Depth: "+(unrolls<0 ? "Disabled" : (""+unrolls))); + for(int n=(-1); n<=3; n++) { menuItem(unrollsMenu, (n<0 ? "Disabled" : (""+n)), doOptUnrolls(n), n==unrolls?iconYes:iconNo); } + optmenu.add(unrollsMenu); + } + // + final int min = CoreMinimization.get(); + final String[] minLabelLong=new String[]{"Slow (guarantees local minimum)", "Medium", "Fast (initial unsat core)"}; + final String[] minLabelShort=new String[]{"Slow", "Medium", "Fast"}; + final JMenu cmMenu = new JMenu("Unsat Core Minimization Strategy: "+minLabelShort[min]); + for(int n=0; n<=2; n++) { menuItem(cmMenu, minLabelLong[n], doOptCore(n), n==min?iconYes:iconNo); } + if (now!=SatSolver.MiniSatProverJNI) cmMenu.setEnabled(false); + optmenu.add(cmMenu); + // + final int gran = CoreGranularity.get(); + final String[] granLabelLong=new String[]{"Top-level conjuncts only", "Flatten the formula once at the beginning", "Flatten the formula at the beginning and after skolemizing", "In addition to flattening the formula twice, expand the quantifiers"}; + final String[] granLabelShort=new String[]{"Top-level", "Flatten once", "Flatten twice", "Expand quantifiers"}; + final JMenu cgMenu = new JMenu("Core Granularity: "+granLabelShort[gran]); + for(int n=0; n0) { + FontName.set(f); + text.setFont(f, size, TabSize.get()); + status.setFont(new Font(f, Font.PLAIN, size)); + log.setFontName(f); + } + return null; + } + + /** This method changes the font size. */ + private Runner doOptFontsize(Integer size) { + if (wrap) return wrapMe(size); + int n=size; + FontSize.set(n); + String f = FontName.get(); + text.setFont(f, n, TabSize.get()); + status.setFont(new Font(f, Font.PLAIN, n)); + log.setFontSize(n); + viz.doSetFontSize(n); + return null; + } + + /** This method changes the tab size. */ + private Runner doOptTabsize(Integer size) { + if (!wrap) { TabSize.set(size.intValue()); text.setFont(FontName.get(), FontSize.get(), size.intValue()); } + return wrapMe(size); + } + + /** This method changes the number of unrolls. */ + private Runner doOptUnrolls(Integer num) { + if (!wrap) Unrolls.set(num.intValue()); + return wrapMe(num); + } + + /** This method changes the skolem depth. */ + private Runner doOptSkolemDepth(Integer size) { + if (!wrap) SkolemDepth.set(size.intValue()); + return wrapMe(size); + } + + /** This method changes the speed of unsat core minimization (larger integer means faster but less optimal). */ + private Runner doOptCore(Integer speed) { + if (!wrap) CoreMinimization.set(speed.intValue()); + return wrapMe(speed); + } + + /** This method changes the granularity of the unsat core (larger integer means more granular). */ + private Runner doCoreGran(Integer gran) { + if (!wrap) CoreGranularity.set(gran.intValue()); + return wrapMe(gran); + } + + /** This method toggles the "antialias" checkbox. */ + private Runner doOptAntiAlias() { + if (!wrap) { boolean newValue = !AntiAlias.get(); AntiAlias.set(newValue); OurAntiAlias.enableAntiAlias(newValue); } + return wrapMe(); + } + + /** This method toggles the "visualize automatically" checkbox. */ + private Runner doOptAutoVisualize() { + if (!wrap) AutoVisualize.set(!AutoVisualize.get()); + return wrapMe(); + } + + /** This method toggles the "record Kodkod input/output" checkbox. */ + private Runner doOptRecordKodkod() { + if (!wrap) RecordKodkod.set(!RecordKodkod.get()); + return wrapMe(); + } + + /** This method toggles the "enable new `implicit this' name resolution" checkbox. */ + private Runner doOptImplicitThis() { + if (!wrap) ImplicitThis.set(!ImplicitThis.get()); + return wrapMe(); + } + + private Runner doOptNoOverflow() { + if (!wrap) NoOverflow.set(!NoOverflow.get()); + return wrapMe(); + } + + /** This method toggles the "syntax highlighting" checkbox. */ + private Runner doOptSyntaxHighlighting() { + if (!wrap) { + boolean flag = SyntaxDisabled.get(); + text.enableSyntax(flag); + SyntaxDisabled.set(!flag); + } + return wrapMe(); + } + + //===============================================================================================================// + + /** This method displays the about box. */ + private Runner doAbout() { + if (wrap) return wrapMe(); + OurDialog.showmsg("About Alloy Analyzer " + Version.version(), + OurUtil.loadIcon("images/logo.gif"), + "Alloy Analyzer " + Version.version(), + "Build date: " + Version.buildDate(), + " ", + "Lead developer: Felix Chang", + "Engine developer: Emina Torlak", + "Graphic design: Julie Pelaez", + "Project lead: Daniel Jackson", + " ", + "Please post comments and questions to the Alloy Community Forum at http://alloy.mit.edu/", + " ", + "Thanks to: Ilya Shlyakhter, Manu Sridharan, Derek Rayside, Jonathan Edwards, Gregory Dennis,", + "Robert Seater, Edmond Lau, Vincent Yeung, Sam Daitch, Andrew Yip, Jongmin Baek, Ning Song,", + "Arturo Arizpe, Li-kuo (Brian) Lin, Joseph Cohen, Jesse Pavel, Ian Schechter, and Uriel Schafer." + ); + return null; + } + + /** This method displays the help html. */ + private Runner doHelp() { + if (wrap) return wrapMe(); + try { + int w=OurUtil.getScreenWidth(), h=OurUtil.getScreenHeight(); + final JFrame frame = new JFrame(); + final JEditorPane html1 = new JEditorPane("text/html", ""); + final JEditorPane html2 = new JEditorPane("text/html", ""); + final HTMLDocument doc1 = (HTMLDocument) (html1.getDocument()); doc1.setAsynchronousLoadPriority(-1); + final HTMLDocument doc2 = (HTMLDocument) (html2.getDocument()); doc2.setAsynchronousLoadPriority(-1); + html1.setPage(this.getClass().getResource("/help/Nav.html")); + html2.setPage(this.getClass().getResource("/help/index.html")); + HyperlinkListener hl=new HyperlinkListener() { + public final void hyperlinkUpdate(HyperlinkEvent e) { + try { + if (e.getEventType()!=HyperlinkEvent.EventType.ACTIVATED) return; + if (e.getURL().getPath().endsWith("quit.htm")) { frame.dispose(); return; } + HTMLDocument doc = (HTMLDocument) (html2.getDocument()); + doc.setAsynchronousLoadPriority(-1); // So that we can catch any exception that may occur + html2.setPage(e.getURL()); + html2.requestFocusInWindow(); + } catch(Throwable ex) { } + } + }; + html1.setEditable(false); html1.setBorder(new EmptyBorder(3,3,3,3)); html1.addHyperlinkListener(hl); + html2.setEditable(false); html2.setBorder(new EmptyBorder(3,3,3,3)); html2.addHyperlinkListener(hl); + JScrollPane scroll1 = OurUtil.scrollpane(html1); + JScrollPane scroll2 = OurUtil.scrollpane(html2); + JSplitPane split = OurUtil.splitpane(JSplitPane.HORIZONTAL_SPLIT, scroll1, scroll2, 150); + split.setResizeWeight(0d); + frame.setTitle("Alloy Analyzer Online Guide"); + frame.getContentPane().setLayout(new BorderLayout()); + frame.getContentPane().add(split, BorderLayout.CENTER); + frame.pack(); + frame.setSize(w-w/10, h-h/10); + frame.setLocation(w/20, h/20); + frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); + frame.setVisible(true); + html2.requestFocusInWindow(); + } catch(Throwable ex) { return null; } + return null; + } + + /** This method displays the license box. */ + private Runner doLicense() { + if (wrap) return wrapMe(); + final String JAR = Util.jarPrefix(); + String alloytxt; + try { alloytxt = Util.readAll(JAR + "LICENSES" + File.separator + "Alloy.txt"); } catch(IOException ex) { return null; } + final JTextArea text = OurUtil.textarea(alloytxt, 15, 85, false, false, new EmptyBorder(2, 2, 2, 2), new Font("Monospaced", Font.PLAIN, 12)); + final JScrollPane scroll = OurUtil.scrollpane(text, new LineBorder(Color.DARK_GRAY, 1)); + final JComboBox combo = new OurCombobox(new String[]{"Alloy","Kodkod","JavaCup","SAT4J","ZChaff","MiniSat"}) { + private static final long serialVersionUID = 0; + @Override public void do_changed(Object value) { + if (value instanceof String) { + try { + String content = Util.readAll(JAR + "LICENSES" + File.separator + value + ".txt"); + text.setText(content); + } catch(IOException ex) { + text.setText("Sorry: an error has occurred in displaying the license file."); + } + } + text.setCaretPosition(0); + } + }; + OurDialog.showmsg("Copyright Notices", + "The source code for the Alloy Analyzer is available under the MIT license.", + " ", + "The Alloy Analyzer utilizes several third-party packages whose code may", + "be distributed under a different license. We are extremely grateful to", + "the authors of these packages for making their source code freely available.", + " ", + OurUtil.makeH(null, "See the copyright notice for: ", combo, null), + " ", + scroll + ); + return null; + } + + /** This method changes the latest instance. */ + void doSetLatest(String arg) { + latestInstance = arg; + latestAutoInstance = arg; + } + + /** The color to use for functions/predicate/paragraphs that contains part of the unsat core. */ + final Color supCoreColor = new Color(0.95f, 0.1f, 0.1f); + + /** The color to use for the unsat core. */ + final Color coreColor = new Color(0.9f, 0.4f, 0.4f); + + /** The color to use for functions/predicate used by the Unsat core. */ + final Color subCoreColor = new Color(0.9f, 0.7f, 0.7f); + + /** This method displays a particular instance or message. */ + @SuppressWarnings("unchecked") + Runner doVisualize(String arg) { + if (wrap) return wrapMe(arg); + text.clearShade(); + if (arg.startsWith("MSG: ")) { // MSG: message + OurDialog.showtext("Detailed Message", arg.substring(5)); + } + if (arg.startsWith("CORE: ")) { // CORE: filename + String filename = Util.canon(arg.substring(6)); + Pair,Set> hCore; + Set lCore; + InputStream is = null; + ObjectInputStream ois = null; + try { + is = new FileInputStream(filename); + ois = new ObjectInputStream(is); + hCore = (Pair,Set>) ois.readObject(); + lCore = (Set) ois.readObject(); + } catch(Throwable ex) { + log.logRed("Error reading or parsing the core \""+filename+"\"\n"); + return null; + } finally { + Util.close(ois); + Util.close(is); + } + text.clearShade(); + text.shade(hCore.b, subCoreColor, false); + text.shade(hCore.a, coreColor, false); + // shade again, because if not all files were open, some shadings will have no effect + text.shade(hCore.b, subCoreColor, false); + text.shade(hCore.a, coreColor, false); + } + if (arg.startsWith("POS: ")) { // POS: x1 y1 x2 y2 filename + Scanner s=new Scanner(arg.substring(5)); + int x1=s.nextInt(), y1=s.nextInt(), x2=s.nextInt(), y2=s.nextInt(); + String f=s.nextLine(); + if (f.length()>0 && f.charAt(0)==' ') f=f.substring(1); // Get rid of the space after Y2 + Pos p=new Pos(Util.canon(f), x1, y1, x2, y2); + text.shade(p); + } + if (arg.startsWith("CNF: ")) { // CNF: filename + String filename=Util.canon(arg.substring(5)); + try { String text=Util.readAll(filename); OurDialog.showtext("Text Viewer", text); } + catch(IOException ex) { log.logRed("Error reading the file \""+filename+"\"\n"); } + } + if (arg.startsWith("XML: ")) { // XML: filename + viz.loadXML(Util.canon(arg.substring(5)), false); + } + return null; + } + + /** This method opens a particular file. */ + private Runner doOpenFile(String arg) { + if (wrap) return wrapMe(arg); + String f=Util.canon(arg); + if (!text.newtab(f)) return null; + if (text.get().isFile()) addHistory(f); + doShow(); + text.get().requestFocusInWindow(); + log.clearError(); + return null; + } + + /** This object performs solution enumeration. */ + private final Computer enumerator = new Computer() { + public String compute(Object input) { + final String arg = (String)input; + OurUtil.show(frame); + if (WorkerEngine.isBusy()) + throw new RuntimeException("Alloy4 is currently executing a SAT solver command. Please wait until that command has finished."); + SimpleCallback1 cb = new SimpleCallback1(SimpleGUI.this, viz, log, Verbosity.get().ordinal(), latestAlloyVersionName, latestAlloyVersion); + SimpleTask2 task = new SimpleTask2(); + task.filename = arg; + try { + WorkerEngine.run(task, SubMemory.get(), SubStack.get(), alloyHome() + fs + "binary", "", cb); +// task.run(cb); + } catch(Throwable ex) { + WorkerEngine.stop(); + log.logBold("Fatal Error: Solver failed due to unknown reason.\n" + + "One possible cause is that, in the Options menu, your specified\n" + + "memory size is larger than the amount allowed by your OS.\n" + + "Also, please make sure \"java\" is in your program path.\n"); + log.logDivider(); + log.flush(); + doStop(2); + return arg; + } + subrunningTask=2; + runmenu.setEnabled(false); + runbutton.setVisible(false); + showbutton.setEnabled(false); + stopbutton.setVisible(true); + return arg; + } + }; + + /** Converts an A4TupleSet into a SimTupleset object. */ + private static SimTupleset convert(Object object) throws Err { + if (!(object instanceof A4TupleSet)) throw new ErrorFatal("Unexpected type error: expecting an A4TupleSet."); + A4TupleSet s = (A4TupleSet)object; + if (s.size()==0) return SimTupleset.EMPTY; + List list = new ArrayList(s.size()); + int arity = s.arity(); + for(A4Tuple t: s) { + String[] array = new String[arity]; + for(int i=0; i fc = new LinkedHashMap(); + XMLNode x = new XMLNode(new File(filename)); + if (!x.is("alloy")) throw new Exception(); + String mainname=null; + for(XMLNode sub: x) if (sub.is("instance")) { + mainname=sub.getAttribute("filename"); + break; + } + if (mainname==null) throw new Exception(); + for(XMLNode sub: x) if (sub.is("source")) { + String name = sub.getAttribute("filename"); + String content = sub.getAttribute("content"); + fc.put(name, content); + } + root = CompUtil.parseEverything_fromFile(A4Reporter.NOP, fc, mainname, (Version.experimental && ImplicitThis.get()) ? 2 : 1); + ans = A4SolutionReader.read(root.getAllReachableSigs(), x); + for(ExprVar a:ans.getAllAtoms()) { root.addGlobal(a.label, a); } + for(ExprVar a:ans.getAllSkolems()) { root.addGlobal(a.label, a); } + } catch(Throwable ex) { + throw new ErrorFatal("Failed to read or parse the XML file."); + } + try { + Expr e = CompUtil.parseOneExpression_fromString(root, str); + if ("yes".equals(System.getProperty("debug")) && Verbosity.get()==Verbosity.FULLDEBUG) { + SimInstance simInst = convert(root, ans); + return simInst.visitThis(e).toString() + (simInst.wasOverflow() ? " (OF)" : ""); + } else + return ans.eval(e).toString(); + } catch(HigherOrderDeclException ex) { + throw new ErrorType("Higher-order quantification is not allowed in the evaluator."); + } + } + }; + + /** Returns true iff the output says "s SATISFIABLE" (while ignoring comment lines and value lines) */ + private static boolean isSat(String output) { + int i=0, n=output.length(); + // skip COMMENT lines and VALUE lines + while(iscreenWidth) width=screenWidth; + int height=AnalyzerHeight.get(); + if (height<=0) height=screenHeight/10*8; else if (height<100) height=100; + if (height>screenHeight) height=screenHeight; + int x=AnalyzerX.get(); if (x<0) x=screenWidth/10; if (x>screenWidth-100) x=screenWidth-100; + int y=AnalyzerY.get(); if (y<0) y=screenHeight/10; if (y>screenHeight-100) y=screenHeight-100; + + // Put up a slash screen + final JFrame frame = new JFrame("Alloy Analyzer"); + frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); + frame.pack(); + if (!Util.onMac() && !Util.onWindows()) { + String gravity = System.getenv("_JAVA_AWT_WM_STATIC_GRAVITY"); + if (gravity==null || gravity.length()==0) { + // many Window managers do not respect ICCCM2; this should help avoid the Title Bar being shifted "off screen" + if (x<30) { if (x<0) x=0; width=width-(30-x); x=30; } + if (y<30) { if (y<0) y=0; height=height-(30-y); y=30; } + } + if (width<100) width=100; + if (height<100) height=100; + } + frame.setSize(width,height); + frame.setLocation(x,y); + frame.setVisible(true); + frame.setTitle("Alloy Analyzer "+Version.version()+" loading... please wait..."); + final int windowWidth = width; + // We intentionally call setVisible(true) first before settings the "please wait" title, + // since we want the minimized window title on Linux/FreeBSD to just say Alloy Analyzer + + // Test the allowed memory sizes + final WorkerEngine.WorkerCallback c = new WorkerEngine.WorkerCallback() { + private final List allowed = new ArrayList(); + private final List toTry = new ArrayList(Arrays.asList(256,512,768,1024,1536,2048,2560,3072,3584,4096)); + private int mem; + public synchronized void callback(Object msg) { + if (toTry.size()==0) { + SwingUtilities.invokeLater(new Runnable() { + public void run() { SimpleGUI.this.frame=frame; SimpleGUI.this.finishInit(args, allowed, windowWidth); } + }); + return; + } + try { mem=toTry.remove(0); WorkerEngine.stop(); WorkerEngine.run(dummyTask, mem, 128, "", "", this); return; } catch(IOException ex) { fail(); } + } + public synchronized void done() { + //System.out.println("Alloy4 can use "+mem+"M"); System.out.flush(); + allowed.add(mem); + callback(null); + } + public synchronized void fail() { + //System.out.println("Alloy4 cannot use "+mem+"M"); System.out.flush(); + callback(null); + } + }; + c.callback(null); + } + + private void finishInit(String[] args, List initialAllowedMemorySizes, int width) { + + // Add the listeners + try { + wrap = true; + frame.addWindowListener(doQuit()); + } finally { + wrap = false; + } + frame.addComponentListener(this); + + // initialize the "allowed memory sizes" array + allowedMemorySizes = new ArrayList(initialAllowedMemorySizes); + int newmem = SubMemory.get(); + if (!allowedMemorySizes.contains(newmem)) { + int newmemlen = allowedMemorySizes.size(); + if (allowedMemorySizes.contains(768) || newmemlen==0) + SubMemory.set(768); // a nice default value + else + SubMemory.set(allowedMemorySizes.get(newmemlen-1)); + } + + // Choose the appropriate font + int fontSize=FontSize.get(); + String fontName=FontName.get(); + while(true) { + if (!OurDialog.hasFont(fontName)) fontName="Lucida Grande"; else break; + if (!OurDialog.hasFont(fontName)) fontName="Verdana"; else break; + if (!OurDialog.hasFont(fontName)) fontName="Courier New"; else break; + if (!OurDialog.hasFont(fontName)) fontName="Lucida Grande"; + break; + } + FontName.set(fontName); + + // Copy required files from the JAR + copyFromJAR(); + final String binary = alloyHome() + fs + "binary"; + + // Create the menu bar + JMenuBar bar = new JMenuBar(); + try { + wrap = true; + filemenu = menu(bar, "&File", doRefreshFile()); + editmenu = menu(bar, "&Edit", doRefreshEdit()); + runmenu = menu(bar, "E&xecute", doRefreshRun()); + optmenu = menu(bar, "&Options", doRefreshOption()); + windowmenu = menu(bar, "&Window", doRefreshWindow(false)); + windowmenu2 = menu(null, "&Window", doRefreshWindow(true)); + helpmenu = menu(bar, "&Help", null); + if (!Util.onMac()) menuItem(helpmenu, "About Alloy...", 'A', doAbout()); + menuItem(helpmenu, "Quick Guide", 'Q', doHelp()); + menuItem(helpmenu, "See the Copyright Notices...", 'L', doLicense()); + } finally { + wrap = false; + } + + // Pre-load the visualizer + viz = new VizGUI(false, "", windowmenu2, enumerator, evaluator); + viz.doSetFontSize(FontSize.get()); + + // Create the toolbar + try { + wrap = true; + toolbar = new JToolBar(); + toolbar.setFloatable(false); + if (!Util.onMac()) toolbar.setBackground(background); + toolbar.add(OurUtil.button("New", "Starts a new blank model", "images/24_new.gif", doNew())); + toolbar.add(OurUtil.button("Open", "Opens an existing model", "images/24_open.gif", doOpen())); + toolbar.add(OurUtil.button("Reload", "Reload all the models from disk", "images/24_reload.gif", doReloadAll())); + toolbar.add(OurUtil.button("Save", "Saves the current model", "images/24_save.gif", doSave())); + toolbar.add(runbutton=OurUtil.button("Execute", "Executes the latest command", "images/24_execute.gif", doExecuteLatest())); + toolbar.add(stopbutton=OurUtil.button("Stop", "Stops the current analysis", "images/24_execute_abort2.gif", doStop(2))); + stopbutton.setVisible(false); + toolbar.add(showbutton=OurUtil.button("Show", "Shows the latest instance", "images/24_graph.gif", doShowLatest())); + toolbar.add(Box.createHorizontalGlue()); + toolbar.setBorder(new OurBorder(false,false,false,false)); + } finally { + wrap = false; + } + + // Choose the antiAlias setting + OurAntiAlias.enableAntiAlias(AntiAlias.get()); + + // Create the message area + logpane = OurUtil.scrollpane(null); + log = new SwingLogPanel(logpane, fontName, fontSize, background, Color.BLACK, new Color(.7f,.2f,.2f), this); + + // Create the text area + text = new OurTabbedSyntaxWidget(fontName, fontSize, TabSize.get()); + text.listeners.add(this); + text.enableSyntax(! SyntaxDisabled.get()); + + // Add everything to the frame, then display the frame + Container all=frame.getContentPane(); + all.setLayout(new BorderLayout()); + all.removeAll(); + JPanel lefthalf=new JPanel(); + lefthalf.setLayout(new BorderLayout()); + lefthalf.add(toolbar, BorderLayout.NORTH); + text.addTo(lefthalf, BorderLayout.CENTER); + splitpane = OurUtil.splitpane(JSplitPane.HORIZONTAL_SPLIT, lefthalf, logpane, width/2); + splitpane.setResizeWeight(0.5D); + status = OurUtil.make(OurAntiAlias.label(" "), new Font(fontName, Font.PLAIN, fontSize), Color.BLACK, background); + status.setBorder(new OurBorder(true,false,false,false)); + all.add(splitpane, BorderLayout.CENTER); + all.add(status, BorderLayout.SOUTH); + + // Generate some informative log messages + log.logBold("Alloy Analyzer "+Version.version()+" (build date: "+Version.buildDate()+")\n\n"); + + // If on Mac, then register an application listener + try { + wrap = true; + if (Util.onMac()) MacUtil.registerApplicationListener(doShow(), doAbout(), doOpenFile(""), doQuit()); + } finally { + wrap = false; + } + + // Add the new JNI location to the java.library.path + try { + System.setProperty("java.library.path", binary); + // The above line is actually useless on Sun JDK/JRE (see Sun's bug ID 4280189) + // The following 4 lines should work for Sun's JDK/JRE (though they probably won't work for others) + String[] newarray = new String[]{binary}; + java.lang.reflect.Field old = ClassLoader.class.getDeclaredField("usr_paths"); + old.setAccessible(true); + old.set(null,newarray); + } catch (Throwable ex) { } + + // Testing the SAT solvers + if (1==1) { + satChoices = SatSolver.values().makeCopy(); +// String test1 = Subprocess.exec(20000, new String[]{binary+fs+"berkmin", binary+fs+"tmp.cnf"}); +// if (!isSat(test1)) satChoices.remove(SatSolver.BerkMinPIPE); + satChoices.remove(SatSolver.BerkMinPIPE); + String test2 = Subprocess.exec(20000, new String[]{binary+fs+"spear", "--model", "--dimacs", binary+fs+"tmp.cnf"}); + if (!isSat(test2)) satChoices.remove(SatSolver.SpearPIPE); + if (!loadLibrary("minisat")) { + log.logBold("Warning: JNI-based SAT solver does not work on this platform.\n"); + log.log("This is okay, since you can still use SAT4J as the solver.\n"+ + "For more information, please visit http://alloy.mit.edu/alloy4/\n"); + log.logDivider(); + log.flush(); + satChoices.remove(SatSolver.MiniSatJNI); + } + if (!loadLibrary("minisatprover")) satChoices.remove(SatSolver.MiniSatProverJNI); + if (!loadLibrary("zchaff")) satChoices.remove(SatSolver.ZChaffJNI); + SatSolver now = SatSolver.get(); + if (!satChoices.contains(now)) { + now=SatSolver.ZChaffJNI; + if (!satChoices.contains(now)) now=SatSolver.SAT4J; + now.set(); + } + if (now==SatSolver.SAT4J && satChoices.size()>3 && satChoices.contains(SatSolver.CNF) && satChoices.contains(SatSolver.KK)) { + log.logBold("Warning: Alloy4 defaults to SAT4J since it is pure Java and very reliable.\n"); + log.log("For faster performance, go to Options menu and try another solver like MiniSat.\n"); + log.log("If these native solvers fail on your computer, remember to change back to SAT4J.\n"); + log.logDivider(); + log.flush(); + } + } + + // If the temporary directory has become too big, then tell the user they can "clear temporary directory". + long space = computeTemporarySpaceUsed(); + if (space<0 || space>=20*1024768) { + if (space<0) log.logBold("Warning: Alloy4's temporary directory has exceeded 1024M.\n"); + else log.logBold("Warning: Alloy4's temporary directory now uses "+(space/1024768)+"M.\n"); + log.log("To clear the temporary directory,\n" + +"go to the File menu and click \"Clear Temporary Directory\"\n"); + log.logDivider(); + log.flush(); + } + + // Refreshes all the menu items + doRefreshFile(); OurUtil.enableAll(filemenu); + doRefreshEdit(); OurUtil.enableAll(editmenu); + doRefreshRun(); OurUtil.enableAll(runmenu); + doRefreshOption(); + doRefreshWindow(false); OurUtil.enableAll(windowmenu); + frame.setJMenuBar(bar); + + // Open the given file, if a filename is given in the command line + for(String f:args) if (f.toLowerCase(Locale.US).endsWith(".als")) { + File file = new File(f); + if (file.exists() && file.isFile()) doOpenFile(file.getPath()); + } + + // Update the title and status bar + notifyChange(); + text.get().requestFocusInWindow(); + + // Launch the welcome screen if needed + if (!"yes".equals(System.getProperty("debug")) && Welcome.get() < welcomeLevel) { + JCheckBox again = new JCheckBox("Show this message every time you start the Alloy Analyzer"); + again.setSelected(true); + OurDialog.showmsg("Welcome", + "Thank you for using the Alloy Analyzer "+Version.version(), + " ", + "Version 4 of the Alloy Analyzer is a complete rewrite,", + "offering improvements in robustness, performance and usability.", + "Models written in Alloy 3 will require some small alterations to run in Alloy 4.", + " ", + "Here are some quick tips:", + " ", + "* Function calls now use [ ] instead of ( )", + " For more details, please see http://alloy.mit.edu/alloy4/quickguide/", + " ", + "* The Execute button always executes the latest command.", + " To choose which command to execute, go to the Execute menu.", + " ", + "* The Alloy Analyzer comes with a variety of sample models.", + " To see them, go to the File menu and click Open Sample Models.", + " ", + again + ); + doShow(); + if (!again.isSelected()) Welcome.set(welcomeLevel); + } + + // Periodically ask the MailBug thread to see if there is a newer version or not + final long now = System.currentTimeMillis(); + final Timer t = new Timer(800, null); + t.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + int n = MailBug.latestBuildNumber(); + // If beyond 3 seconds, then we should stop because the log message may run into other user messages + if (System.currentTimeMillis() - now >= 3000 || n <= Version.buildNumber()) { t.stop(); return; } + latestAlloyVersion = n; + latestAlloyVersionName = MailBug.latestBuildName(); + log.logBold("An updated version of the Alloy Analyzer has been released.\n"); + log.log("Please visit alloy.mit.edu to download the latest version:\nVersion " + latestAlloyVersionName + "\n"); + log.logDivider(); + log.flush(); + t.stop(); + } + }); + t.start(); + } + + /** {@inheritDoc} */ + public Object do_action(Object sender, Event e) { + if (sender instanceof OurTabbedSyntaxWidget) switch(e) { + case FOCUSED: notifyFocusGained(); break; + case STATUS_CHANGE: notifyChange(); break; + } + return true; + } + + /** {@inheritDoc} */ + public Object do_action(Object sender, Event e, Object arg) { + if (sender instanceof OurTree && e==Event.CLICK && arg instanceof Browsable) { + Pos p = ((Browsable)arg).pos(); + if (p==Pos.UNKNOWN) p = ((Browsable)arg).span(); + text.shade(p); + } + return true; + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4whole/SimpleReporter.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4whole/SimpleReporter.java new file mode 100644 index 00000000..eb3b201f --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4whole/SimpleReporter.java @@ -0,0 +1,497 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4whole; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.ObjectOutputStream; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import edu.mit.csail.sdg.alloy4.A4Reporter; +import edu.mit.csail.sdg.alloy4.ConstList; +import edu.mit.csail.sdg.alloy4.ConstMap; +import edu.mit.csail.sdg.alloy4.Err; +import edu.mit.csail.sdg.alloy4.ErrorSyntax; +import edu.mit.csail.sdg.alloy4.ErrorType; +import edu.mit.csail.sdg.alloy4.ErrorWarning; +import edu.mit.csail.sdg.alloy4.MailBug; +import edu.mit.csail.sdg.alloy4.OurDialog; +import edu.mit.csail.sdg.alloy4.Pair; +import edu.mit.csail.sdg.alloy4.Pos; +import edu.mit.csail.sdg.alloy4.Util; +import edu.mit.csail.sdg.alloy4.Version; +import edu.mit.csail.sdg.alloy4.XMLNode; +import edu.mit.csail.sdg.alloy4.WorkerEngine.WorkerCallback; +import edu.mit.csail.sdg.alloy4.WorkerEngine.WorkerTask; +import edu.mit.csail.sdg.alloy4compiler.ast.Command; +import edu.mit.csail.sdg.alloy4compiler.ast.Sig; +import edu.mit.csail.sdg.alloy4compiler.ast.Module; +import edu.mit.csail.sdg.alloy4compiler.parser.CompUtil; +import edu.mit.csail.sdg.alloy4compiler.translator.A4Options; +import edu.mit.csail.sdg.alloy4compiler.translator.A4Solution; +import edu.mit.csail.sdg.alloy4compiler.translator.A4SolutionReader; +import edu.mit.csail.sdg.alloy4compiler.translator.A4SolutionWriter; +import edu.mit.csail.sdg.alloy4compiler.translator.TranslateAlloyToKodkod; +import edu.mit.csail.sdg.alloy4viz.StaticInstanceReader; +import edu.mit.csail.sdg.alloy4viz.VizGUI; + +/** This helper method is used by SimpleGUI. */ + +final class SimpleReporter extends A4Reporter { + + public static final class SimpleCallback1 implements WorkerCallback { + private final SimpleGUI gui; + private final VizGUI viz; + private final SwingLogPanel span; + private final Set warnings = new HashSet(); + private final List results = new ArrayList(); + private int len2=0, len3=0, verbosity=0; + private final String latestName; + private final int latestVersion; + public SimpleCallback1(SimpleGUI gui, VizGUI viz, SwingLogPanel span, int verbosity, String latestName, int latestVersion) { + this.gui=gui; this.viz=viz; this.span=span; this.verbosity=verbosity; + this.latestName=latestName; this.latestVersion=latestVersion; + len2 = len3 = span.getLength(); + } + public void done() { if (viz!=null) span.setLength(len2); else span.logDivider(); span.flush(); gui.doStop(0); } + public void fail() { span.logBold("\nAn error has occurred!\n"); span.logDivider(); span.flush(); gui.doStop(1); } + public void callback(Object msg) { + if (msg==null) { span.logBold("Done\n"); span.flush(); return; } + if (msg instanceof String) { span.logBold( ((String)msg).trim() + "\n" ); span.flush(); return; } + if (msg instanceof Throwable) { + for(Throwable ex = (Throwable)msg; ex!=null; ex=ex.getCause()) { + if (ex instanceof OutOfMemoryError) { + span.logBold("\nFatal Error: the solver ran out of memory!\n" + "Try simplifying your model or reducing the scope,\n" + "or increase memory under the Options menu.\n"); + return; + } + if (ex instanceof StackOverflowError) { + span.logBold("\nFatal Error: the solver ran out of stack space!\n" + "Try simplifying your model or reducing the scope,\n" + "or increase stack under the Options menu.\n"); + return; + } + } + } + if (msg instanceof Err) { + Err ex = (Err)msg; + String text = "fatal"; + boolean fatal = false; + if (ex instanceof ErrorSyntax) text="syntax"; else if (ex instanceof ErrorType) text="type"; else fatal=true; + if (ex.pos==Pos.UNKNOWN) + span.logBold("A "+text+" error has occurred: "); + else + span.logLink("A "+text+" error has occurred: ", "POS: "+ex.pos.x+" "+ex.pos.y+" "+ex.pos.x2+" "+ex.pos.y2+" "+ex.pos.filename); + if (verbosity>2) { + span.log("(see the "); span.logLink("stacktrace", "MSG: "+ex.dump()); span.log(")\n"); + } else { + span.log("\n"); + } + span.logIndented(ex.msg.trim()); + span.log("\n"); + if (fatal && latestVersion>Version.buildNumber()) + span.logBold( + "\nNote: You are running Alloy build#"+Version.buildNumber()+ + ",\nbut the most recent is Alloy build#"+latestVersion+ + ":\n( version "+latestName+" )\nPlease try to upgrade to the newest version,"+ + "\nas the problem may have been fixed already.\n"); + span.flush(); + if (!fatal) gui.doVisualize("POS: "+ex.pos.x+" "+ex.pos.y+" "+ex.pos.x2+" "+ex.pos.y2+" "+ex.pos.filename); + return; + } + if (msg instanceof Throwable) { Throwable ex = (Throwable)msg; span.logBold(ex.toString().trim()+"\n"); span.flush(); return; } + if (!(msg instanceof Object[])) return; + Object[] array = (Object[]) msg; + if (array[0].equals("pop")) { span.setLength(len2); String x=(String)(array[1]); if (viz!=null && x.length()>0) OurDialog.alert(x); } + if (array[0].equals("declare")) { gui.doSetLatest((String)(array[1])); } + if (array[0].equals("S2")) { len3=len2=span.getLength(); span.logBold(""+array[1]); } + if (array[0].equals("R3")) { span.setLength(len3); span.log(""+array[1]); } + if (array[0].equals("link")) { span.logLink((String)(array[1]), (String)(array[2])); } + if (array[0].equals("bold")) { span.logBold(""+array[1]); } + if (array[0].equals("")) { span.log(""+array[1]); } + if (array[0].equals("scope") && verbosity>0) { span.log(" " + array[1]); } + if (array[0].equals("bound") && verbosity>1) { span.log(" " + array[1]); } + if (array[0].equals("resultCNF")) { results.add(null); span.setLength(len3); span.log(" File written to "+array[1]+"\n\n"); } + if (array[0].equals("debug") && verbosity>2) { span.log(" "+array[1]+"\n"); len2=len3=span.getLength(); } + if (array[0].equals("translate")) { span.log(" " + array[1]); len3 = span.getLength(); span.logBold(" Generating CNF...\n"); } + if (array[0].equals("solve")) { span.setLength(len3); span.log(" " + array[1]); len3=span.getLength(); span.logBold(" Solving...\n"); } + if (array[0].equals("warnings")) { + if (warnings.size()==0) span.setLength(len2); + else if (warnings.size()>1) span.logBold("Note: There were "+warnings.size()+" compilation warnings. Please scroll up to see them.\n\n"); + else span.logBold("Note: There was 1 compilation warning. Please scroll up to see them.\n\n"); + if (warnings.size()>0 && Boolean.FALSE.equals(array[1])) { + Pos e = warnings.iterator().next().pos; + gui.doVisualize("POS: "+e.x+" "+e.y+" "+e.x2+" "+e.y2+" "+e.filename); + span.logBold("Warnings often indicate errors in the model.\n" + +"Some warnings can affect the soundness of the analysis.\n" + +"To proceed despite the warnings, go to the Options menu.\n"); + } + } + if (array[0].equals("warning")) { + ErrorWarning e = (ErrorWarning)(array[1]); + if (!warnings.add(e)) return; + Pos p=e.pos; + span.logLink("Warning #"+warnings.size(), "POS: "+p.x+" "+p.y+" "+p.x2+" "+p.y2+" "+p.filename); + span.log("\n"); span.logIndented(e.msg.trim()); span.log("\n\n"); + } + if (array[0].equals("sat")) { + boolean chk = Boolean.TRUE.equals(array[1]); + int expects = (Integer) (array[2]); + String filename = (String) (array[3]), formula = (String) (array[4]); + results.add(filename); + (new File(filename)).deleteOnExit(); + gui.doSetLatest(filename); + span.setLength(len3); + span.log(" "); + span.logLink(chk ? "Counterexample" : "Instance", "XML: "+filename); + span.log(" found. "); + span.logLink(chk?"Assertion":"Predicate", formula); span.log(chk?" is invalid":" is consistent"); + if (expects==0) span.log(", contrary to expectation"); else if (expects==1) span.log(", as expected"); + span.log(". "+array[5]+"ms.\n\n"); + } + if (array[0].equals("metamodel")) { + String outf = (String) (array[1]); + span.setLength(len2); + (new File(outf)).deleteOnExit(); + gui.doSetLatest(outf); + span.logLink("Metamodel", "XML: "+outf); + span.log(" successfully generated.\n\n"); + } + if (array[0].equals("minimizing")) { + boolean chk = Boolean.TRUE.equals(array[1]); + int expects = (Integer) (array[2]); + span.setLength(len3); + span.log(chk ? " No counterexample found." : " No instance found."); + if (chk) span.log(" Assertion may be valid"); else span.log(" Predicate may be inconsistent"); + if (expects==1) span.log(", contrary to expectation"); else if (expects==0) span.log(", as expected"); + span.log(". "+array[4]+"ms.\n"); + span.logBold(" Minimizing the unsat core of "+array[3]+" entries...\n"); + } + if (array[0].equals("unsat")) { + boolean chk = Boolean.TRUE.equals(array[1]); + int expects = (Integer) (array[2]); + String formula = (String) (array[4]); + span.setLength(len3); + span.log(chk ? " No counterexample found. " : " No instance found. "); + span.logLink(chk ? "Assertion" : "Predicate", formula); + span.log(chk? " may be valid" : " may be inconsistent"); + if (expects==1) span.log(", contrary to expectation"); else if (expects==0) span.log(", as expected"); + if (array.length==5) { span.log(". "+array[3]+"ms.\n\n"); span.flush(); return; } + String core = (String) (array[5]); + int mbefore = (Integer) (array[6]), mafter = (Integer) (array[7]); + span.log(". "+array[3]+"ms.\n"); + if (core.length()==0) { results.add(""); span.log(" No unsat core is available in this case. "+array[8]+"ms.\n\n"); span.flush(); return; } + results.add(core); + (new File(core)).deleteOnExit(); + span.log(" "); + span.logLink("Core", core); + if (mbefore<=mafter) span.log(" contains "+mafter+" top-level formulas. "+array[8]+"ms.\n\n"); + else span.log(" reduced from "+mbefore+" to "+mafter+" top-level formulas. "+array[8]+"ms.\n\n"); + } + span.flush(); + } + } + + private void cb(Serializable... objs) { cb.callback(objs); } + + /** {@inheritDoc} */ + @Override public void resultCNF(final String filename) { cb("resultCNF", filename); } + + /** {@inheritDoc} */ + @Override public void warning(final ErrorWarning ex) { warn++; cb("warning", ex); } + + /** {@inheritDoc} */ + @Override public void scope(final String msg) { cb("scope", msg); } + + /** {@inheritDoc} */ + @Override public void bound(final String msg) { cb("bound", msg); } + + /** {@inheritDoc} */ + @Override public void debug(final String msg) { cb("debug", msg.trim()); } + + /** {@inheritDoc} */ + @Override public void translate(String solver, int bitwidth, int maxseq, int skolemDepth, int symmetry) { + lastTime = System.currentTimeMillis(); + cb("translate", "Solver="+solver+" Bitwidth="+bitwidth+" MaxSeq="+maxseq + + (skolemDepth==0?"":" SkolemDepth="+skolemDepth) + + " Symmetry="+(symmetry>0 ? (""+symmetry) : "OFF")+'\n'); + } + + /** {@inheritDoc} */ + @Override public void solve(final int primaryVars, final int totalVars, final int clauses) { + minimized=0; + cb("solve", ""+totalVars+" vars. "+primaryVars+" primary vars. "+clauses+" clauses. "+(System.currentTimeMillis()-lastTime)+"ms.\n"); + lastTime = System.currentTimeMillis(); + } + + /** {@inheritDoc} */ + @Override public void resultSAT(Object command, long solvingTime, Object solution) { + if (!(solution instanceof A4Solution) || !(command instanceof Command)) return; + A4Solution sol = (A4Solution)solution; + Command cmd = (Command)command; + String formula = recordKodkod ? sol.debugExtractKInput() : ""; + String filename = tempfile+".xml"; + synchronized(SimpleReporter.class) { + try { + cb("R3", " Writing the XML file..."); + if (latestModule!=null) writeXML(this, latestModule, filename, sol, latestKodkodSRC); + } catch(Throwable ex) { + cb("bold", "\n" + (ex.toString().trim()) + "\nStackTrace:\n" + (MailBug.dump(ex).trim()) + "\n"); + return; + } + latestKodkods.clear(); + latestKodkods.add(sol.toString()); + latestKodkod=sol; + latestKodkodXML=filename; + } + String formulafilename = ""; + if (formula.length()>0 && tempfile!=null) { + formulafilename = tempfile+".java"; + try { Util.writeAll(formulafilename, formula); formulafilename="CNF: "+formulafilename; } catch(Throwable ex) { formulafilename=""; } + } + cb("sat", cmd.check, cmd.expects, filename, formulafilename, System.currentTimeMillis()-lastTime); + } + + /** {@inheritDoc} */ + @Override public void minimizing(Object command, int before) { + if (!(command instanceof Command)) return; + Command cmd = (Command)command; + minimized = System.currentTimeMillis(); + cb("minimizing", cmd.check, cmd.expects, before, minimized-lastTime); + } + + /** {@inheritDoc} */ + @Override public void minimized(Object command, int before, int after) { minimizedBefore=before; minimizedAfter=after; } + + /** {@inheritDoc} */ + @Override public void resultUNSAT(Object command, long solvingTime, Object solution) { + if (!(solution instanceof A4Solution) || !(command instanceof Command)) return; + A4Solution sol = (A4Solution)solution; + Command cmd = (Command)command; + String originalFormula = recordKodkod ? sol.debugExtractKInput() : ""; + String corefilename="", formulafilename=""; + if (originalFormula.length()>0 && tempfile!=null) { + formulafilename=tempfile+".java"; + try { Util.writeAll(formulafilename, originalFormula); formulafilename="CNF: "+formulafilename; } catch(Throwable ex) { formulafilename=""; } + } + Pair,Set> core = sol.highLevelCore(); + if ((core.a.size()>0 || core.b.size()>0) && tempfile!=null) { + corefilename=tempfile+".core"; + OutputStream fs=null; + ObjectOutputStream os=null; + try { + fs=new FileOutputStream(corefilename); + os=new ObjectOutputStream(fs); + os.writeObject(core); + os.writeObject(sol.lowLevelCore()); + corefilename="CORE: "+corefilename; + } catch(Throwable ex) { + corefilename=""; + } finally { + Util.close(os); + Util.close(fs); + } + } + if (minimized==0) cb("unsat", cmd.check, cmd.expects, (System.currentTimeMillis()-lastTime), formulafilename); + else cb("unsat", cmd.check, cmd.expects, minimized-lastTime, formulafilename, corefilename, minimizedBefore, minimizedAfter, (System.currentTimeMillis()-minimized)); + } + + private final WorkerCallback cb; + + //========== These fields should be set each time we execute a set of commands + + /** Whether we should record Kodkod input/output. */ + private final boolean recordKodkod; + + /** The time that the last action began; we subtract it from System.currentTimeMillis() to determine the elapsed time. */ + private long lastTime=0; + + /** If we performed unsat core minimization, then this is the start of the minimization, else this is 0. */ + private long minimized = 0; + + /** The unsat core size before minimization. */ + private int minimizedBefore; + + /** The unsat core size after minimization. */ + private int minimizedAfter; + + /** The filename where we can write a temporary Java file or Core file. */ + private String tempfile=null; + + //========== These fields may be altered as each successful command generates a Kodkod or Metamodel instance + + /** The set of Strings already enumerated for this current solution. */ + private static final Set latestKodkods=new LinkedHashSet(); + + /** The A4Solution corresponding to the latest solution generated by Kodkod; this field must be synchronized. */ + private static A4Solution latestKodkod=null; + + /** The root Module corresponding to this.latestKodkod; this field must be synchronized. */ + private static Module latestModule=null; + + /** The source code corresponding to the latest solution generated by Kodkod; this field must be synchronized. */ + private static ConstMap latestKodkodSRC = null; + + /** The XML filename corresponding to the latest solution generated by Kodkod; this field must be synchronized. */ + private static String latestKodkodXML=null; + + /** The XML filename corresponding to the latest metamodel generated by TranslateAlloyToMetamodel; this field must be synchronized. */ + private static String latestMetamodelXML=null; + + /** Constructor is private. */ + private SimpleReporter(WorkerCallback cb, boolean recordKodkod) { this.cb=cb; this.recordKodkod=recordKodkod; } + + /** Helper method to write out a full XML file. */ + private static void writeXML(A4Reporter rep, Module mod, String filename, A4Solution sol, Map sources) throws Exception { + sol.writeXML(rep, filename, mod.getAllFunc(), sources); + if ("yes".equals(System.getProperty("debug"))) validate(filename); + } + + private int warn=0; + + /** Task that performs solution enumeration. */ + static final class SimpleTask2 implements WorkerTask { + private static final long serialVersionUID = 0; + public String filename = ""; + public transient WorkerCallback out = null; + private void cb(Object... objs) throws Exception { out.callback(objs); } + public void run(WorkerCallback out) throws Exception { + this.out = out; + cb("S2", "Enumerating...\n"); + A4Solution sol; + Module mod; + synchronized(SimpleReporter.class) { + if (latestMetamodelXML!=null && latestMetamodelXML.equals(filename)) + {cb("pop", "You cannot enumerate a metamodel.\n"); return;} + if (latestKodkodXML==null || !latestKodkodXML.equals(filename)) + {cb("pop", "You can only enumerate the solutions of the most-recently-solved command."); return;} + if (latestKodkod==null || latestModule==null || latestKodkodSRC==null) + {cb("pop", "Error: the SAT solver that generated the instance has exited,\nso we cannot enumerate unless you re-solve that command.\n"); return;} + sol=latestKodkod; + mod=latestModule; + } + if (!sol.satisfiable()) + {cb("pop", "Error: This command is unsatisfiable,\nso there are no solutions to enumerate."); return;} + if (!sol.isIncremental()) + {cb("pop", "Error: This solution was not generated by an incremental SAT solver.\n" + + "Currently only MiniSat and SAT4J are supported."); return;} + int tries=0; + while(true) { + sol=sol.next(); + if (!sol.satisfiable()) + {cb("pop", "There are no more satisfying instances.\n\n" + + "Note: due to symmetry breaking and other optimizations,\n" + + "some equivalent solutions may have been omitted."); return;} + String toString = sol.toString(); + synchronized(SimpleReporter.class) { + if (!latestKodkods.add(toString)) if (tries<100) { tries++; continue; } + // The counter is needed to avoid a Kodkod bug where sometimes we might repeat the same solution infinitely number of times; this at least allows the user to keep going + writeXML(null, mod, filename, sol, latestKodkodSRC); latestKodkod=sol; + } + cb("declare", filename); + return; + } + } + } + + /** Validate the given filename to see if it is a valid Alloy XML instance file. */ + private static void validate(String filename) throws Exception { + A4SolutionReader.read(new ArrayList(), new XMLNode(new File(filename))).toString(); + StaticInstanceReader.parseInstance(new File(filename)); + } + + /** Task that perform one command. */ + static final class SimpleTask1 implements WorkerTask { + private static final long serialVersionUID = 0; + public A4Options options; + public String tempdir; + public boolean bundleWarningNonFatal; + public int bundleIndex; + public int resolutionMode; + public Map map; + public SimpleTask1() { } + public void cb(WorkerCallback out, Object... objs) throws IOException { out.callback(objs); } + public void run(WorkerCallback out) throws Exception { + cb(out, "S2", "Starting the solver...\n\n"); + final SimpleReporter rep = new SimpleReporter(out, options.recordKodkod); + final Module world = CompUtil.parseEverything_fromFile(rep, map, options.originalFilename, resolutionMode); + final List sigs = world.getAllReachableSigs(); + final ConstList cmds = world.getAllCommands(); + cb(out, "warnings", bundleWarningNonFatal); + if (rep.warn>0 && !bundleWarningNonFatal) return; + List result = new ArrayList(cmds.size()); + if (bundleIndex==-2) { + final String outf=tempdir+File.separatorChar+"m.xml"; + cb(out, "S2", "Generating the metamodel...\n"); + PrintWriter of = new PrintWriter(outf, "UTF-8"); + Util.encodeXMLs(of, "\n\n\n"); + A4SolutionWriter.writeMetamodel(ConstList.make(sigs), options.originalFilename, of); + Util.encodeXMLs(of, "\n"); + Util.close(of); + if ("yes".equals(System.getProperty("debug"))) validate(outf); + cb(out, "metamodel", outf); + synchronized(SimpleReporter.class) { latestMetamodelXML=outf; } + } else for(int i=0; i0) result.add(tempCNF+".core"); + else result.add(""); + } + (new File(tempdir)).delete(); // In case it was UNSAT, or canceled... + if (result.size()>1) { + rep.cb("bold", "" + result.size() + " commands were executed. The results are:\n"); + for(int i=0; i1) rep.cb("bold", "Note: There were "+rep.warn+" compilation warnings. Please scroll up to see them.\n"); + if (rep.warn==1) rep.cb("bold", "Note: There was 1 compilation warning. Please scroll up to see it.\n"); + } + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4whole/SwingLogPanel.java b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4whole/SwingLogPanel.java new file mode 100644 index 00000000..0d590551 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4whole/SwingLogPanel.java @@ -0,0 +1,353 @@ +/* Alloy Analyzer 4 -- Copyright (c) 2006-2009, Felix Chang + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package edu.mit.csail.sdg.alloy4whole; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.util.ArrayList; +import java.util.List; +import java.util.StringTokenizer; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTextPane; +import javax.swing.border.EmptyBorder; +import javax.swing.text.AbstractDocument; +import javax.swing.text.BadLocationException; +import javax.swing.text.BoxView; +import javax.swing.text.Element; +import javax.swing.text.Style; +import javax.swing.text.StyleConstants; +import javax.swing.text.StyledDocument; +import javax.swing.text.StyledEditorKit; +import javax.swing.text.View; +import javax.swing.text.ViewFactory; +import edu.mit.csail.sdg.alloy4.OurAntiAlias; +import edu.mit.csail.sdg.alloy4.OurUtil; + +/** This helper method is used by SimpleGUI; only the AWT Event Thread may call methods in this class. */ + +final class SwingLogPanel { + + /** Try to wrap the input to about 60 characters per line; however, if a token is too long, we won't break it. */ + private static void linewrap(StringBuilder sb, String msg) { + StringTokenizer tokenizer=new StringTokenizer(msg,"\r\n\t "); + final int max=60; + int now=0; + while(tokenizer.hasMoreTokens()) { + String x=tokenizer.nextToken(); + if (now+1+x.length() > max) { + if (now>0) { sb.append('\n'); } + sb.append(x); + now=x.length(); + } else { + if (now>0) { now++; sb.append(' '); } + sb.append(x); + now=now+x.length(); + } + } + } + + /** This field buffers previous calls to log() so that we can write them out later in a single Swing call + * (If there is nothing buffered, this field can be an empty list or even null). + */ + private final List batch = new ArrayList(); + + /** The newly created JTextPane object that will display the log; null if this log has been destroyed. */ + private JTextPane log; + + /** The style to use when writing regular messages. */ + private final Style styleRegular; + + /** The style to use when writing bold messages. */ + private final Style styleBold; + + /** The style to use when writing red messages. */ + private final Style styleRed; + + /** This stores the JLabels used for displaying hyperlinks. */ + private final List links = new ArrayList(); + + /** When the window gains focus, we'll call handler.run(ev_logFocused); + * When a hyperlink is clicked, we'll call handler.run(evs_visualize, linkURL). + */ + private final SimpleGUI handler; + + /** The current length of the log, not counting any "red" error message at the end of the log. */ + private int lastSize = 0; + + /** The current font name. */ + private String fontName; + + /** The current font size. */ + private int fontSize; + + /** The color to use for hyperlinks when the mouse is not hovering over it. */ + private static final Color linkColor = new Color(0.3f, 0.3f, 0.9f); + + /** The color to use for a hyperlink when the mouse is hovering over it. */ + private static final Color hoverColor = new Color(0.9f, 0.3f, 0.3f); + + /** This stores a default ViewFactory that will handle the View requests that we don't care to override. */ + private static final ViewFactory defaultFactory = (new StyledEditorKit()).getViewFactory(); + + /** Constructs a new JTextPane logger, and put it inside an existing JScrollPane. + * @param parent - the existing JScrollPane to insert the JTextPane into + * @param fontName - the font name to use + * @param fontSize - the font size to use + * @param background - the background color to use + * @param regular - the color to use for regular messages + * @param red - the color to use for red messages + * @param handler - the SimpleGUI parent + */ + public SwingLogPanel( + final JScrollPane parent, String fontName, int fontSize, + final Color background, final Color regular, final Color red, + final SimpleGUI handler) { + this.handler = handler; + this.fontName = fontName; + this.fontSize = fontSize; + this.log = OurUtil.make(OurAntiAlias.pane(), Color.BLACK, background, new EmptyBorder(1,1,1,1), new Font(fontName, Font.PLAIN, fontSize)); + // This customized StyledEditorKit prevents line-wrapping up to 30000 pixels wide. + // 30000 is a good number; value higher than about 32768 will cause errors. + this.log.setEditorKit(new StyledEditorKit() { + private static final long serialVersionUID = 0; + @Override public final ViewFactory getViewFactory() { + return new ViewFactory() { + public final View create(Element x) { + if (!AbstractDocument.SectionElementName.equals(x.getName())) return defaultFactory.create(x); + return new BoxView(x, View.Y_AXIS) { + @Override public final float getMinimumSpan(int axis) { return super.getPreferredSpan(axis); } + @Override public final void layout(int width,int height) { super.layout(30000, height); } + }; + } + }; + } + }); + log.setEditable(false); + log.addFocusListener(new FocusListener() { + public final void focusGained(FocusEvent e) { if (handler!=null) handler.notifyFocusLost(); } + public final void focusLost(FocusEvent e) { } + }); + StyledDocument doc = log.getStyledDocument(); + styleRegular = doc.addStyle("regular", null); + StyleConstants.setFontFamily(styleRegular, fontName); + StyleConstants.setFontSize(styleRegular, fontSize); + StyleConstants.setForeground(styleRegular, regular); + styleBold = doc.addStyle("bold", styleRegular); + StyleConstants.setBold(styleBold, true); + styleRed = doc.addStyle("red", styleBold); + StyleConstants.setForeground(styleRed, red); + parent.setViewportView(log); + parent.setBackground(background); + } + + /** Write a horizontal separator into the log window. */ + public void logDivider() { + if (log==null) return; + clearError(); + StyledDocument doc = log.getStyledDocument(); + Style dividerStyle = doc.addStyle("bar", styleRegular); + JPanel jpanel = new JPanel(); + jpanel.setBackground(Color.LIGHT_GRAY); + jpanel.setPreferredSize(new Dimension(300,1)); // 300 is arbitrary, since it will auto-stretch + StyleConstants.setComponent(dividerStyle, jpanel); + reallyLog(".", dividerStyle); // Any character would do; "." will be replaced by the JPanel + reallyLog("\n\n", styleRegular); + log.setCaretPosition(doc.getLength()); + lastSize = doc.getLength(); + } + + /** Write a clickable link into the log window. */ + public void logLink(final String link, final String linkDestination) { + if (log==null || link.length()==0) return; + if (linkDestination==null || linkDestination.length()==0) { log(link); return; } + clearError(); + StyledDocument doc = log.getStyledDocument(); + Style linkStyle = doc.addStyle("link", styleRegular); + final JLabel label = OurUtil.make(OurAntiAlias.label(link), new Font(fontName, Font.BOLD, fontSize), linkColor); + label.setAlignmentY(0.8f); + label.setMaximumSize(label.getPreferredSize()); + label.addMouseListener(new MouseListener(){ + public final void mousePressed(MouseEvent e) { if (handler!=null) handler.doVisualize(linkDestination); } + public final void mouseClicked(MouseEvent e) { } + public final void mouseReleased(MouseEvent e) { } + public final void mouseEntered(MouseEvent e) { label.setForeground(hoverColor); } + public final void mouseExited(MouseEvent e) { label.setForeground(linkColor); } + }); + StyleConstants.setComponent(linkStyle, label); + links.add(label); + reallyLog(".", linkStyle); // Any character would do; the "." will be replaced by the JLabel + log.setCaretPosition(doc.getLength()); + lastSize = doc.getLength(); + } + + /** Write "msg" in regular style. */ + public void log(String msg) { if (log!=null && msg.length()>0) batch.add(msg); } + + /** Write "msg" in bold style. */ + public void logBold(String msg) { if (msg.length()>0) {clearError(); reallyLog(msg, styleBold);} } + + private void reallyLog(String text, Style style) { + if (log==null || text.length()==0) return; + int i=text.lastIndexOf('\n'), j=text.lastIndexOf('\r'); + if (i>=0 && i0) { + int i = msg.indexOf('\n'); + if (i>=0) { + linewrap(sb, msg.substring(0,i)); + sb.append('\n'); + msg=msg.substring(i+1); + } else { + linewrap(sb, msg); + break; + } + } + clearError(); + reallyLog(sb.toString(), styleRed); + } + + /** Write "msg" in regular style (with automatic line wrap). */ + public void logIndented (String msg) { + if (log==null || msg.length()==0) return; + StringBuilder sb=new StringBuilder(); + while(msg.length()>0) { + int i = msg.indexOf('\n'); + if (i>=0) { + linewrap(sb, msg.substring(0,i)); + sb.append('\n'); + msg=msg.substring(i+1); + } else { + linewrap(sb, msg); + break; + } + } + clearError(); + reallyLog(sb.toString(), styleRegular); + } + + /** Set the font name. */ + public void setFontName(String fontName) { + if (log==null) return; + this.fontName = fontName; + log.setFont(new Font(fontName, Font.PLAIN, fontSize)); + StyleConstants.setFontFamily(styleRegular, fontName); + StyleConstants.setFontFamily(styleBold, fontName); + StyleConstants.setFontFamily(styleRed, fontName); + StyleConstants.setFontSize(styleRegular, fontSize); + StyleConstants.setFontSize(styleBold, fontSize); + StyleConstants.setFontSize(styleRed, fontSize); + // Changes all existing text + StyledDocument doc=log.getStyledDocument(); + Style temp=doc.addStyle("temp", null); + StyleConstants.setFontFamily(temp, fontName); + StyleConstants.setFontSize(temp, fontSize); + doc.setCharacterAttributes(0, doc.getLength(), temp, false); + // Changes all existing hyperlinks + Font newFont = new Font(fontName, Font.BOLD, fontSize); + for(JLabel link: links) { link.setFont(newFont); } + } + + /** Set the font size. */ + public void setFontSize(int fontSize) { + if (log==null) return; + this.fontSize = fontSize; + setFontName(this.fontName); + } + + /** Set the background color. */ + public void setBackground(Color background) { + if (log==null) return; + log.setBackground(background); + } + + /** Query the current length of the log. */ + int getLength() { + if (log==null) return 0; + clearError(); + return log.getStyledDocument().getLength(); + } + + /** Truncate the log to the given length; if the log is shorter than the number given, then nothing happens. */ + void setLength(int newLength) { + if (log==null) return; + clearError(); + StyledDocument doc=log.getStyledDocument(); + int n=doc.getLength(); + if (n<=newLength) return; + try { + doc.remove(newLength, n-newLength); + } catch (BadLocationException e) { + // Harmless + } + if (lastSize>doc.getLength()) { lastSize=doc.getLength(); } + } + + /** This method copies the currently selected text in the log (if any) into the clipboard. */ + public void copy() { + if (log==null) return; + log.copy(); + } + + /** Removes any messages writtin in "red" style. */ + public void clearError() { + if (log==null) return; + // Since this class always removes "red" messages prior to writing anything, + // that means if there are any red messages, they will always be at the end of the JTextPane. + StyledDocument doc=log.getStyledDocument(); + int n=doc.getLength(); + if (n>lastSize) { + try {doc.remove(lastSize, n-lastSize);} catch (BadLocationException e) {} + } + if (batch.size()>0) { + for(String msg: batch) { reallyLog(msg, styleRegular); } + batch.clear(); + } + } + + /** Commits all outstanding writes (if the messages are buffered). */ + public void flush() { + if (log==null) return; + if (batch.size()>0) clearError(); + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4whole/package.html b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4whole/package.html new file mode 100644 index 00000000..0f604622 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/edu/mit/csail/sdg/alloy4whole/package.html @@ -0,0 +1,6 @@ + + +This package contains a simple GUI client, +as well as several examples on using the API. + + diff --git a/Source/eu.modelwriter.alloyanalyzer/src/help/Nav.html b/Source/eu.modelwriter.alloyanalyzer/src/help/Nav.html new file mode 100644 index 00000000..918c1039 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/help/Nav.html @@ -0,0 +1,44 @@ + + +The Alloy Analyzer + + + + + + +

Quit

+ +

1. Running the Alloy Analyzer + +

2. The Alloy Analyzer GUI + +

3. Performing +Analyses on Alloy Models + +

3.1 Determining the Module Path + +

3.1.1 Built-in Utility Modules + +

3.2 Visualizing the Result + +

3.2.1 The Viz View + +

3.2.2 The Tree View + +

3.2.3 The XML View + +

3.3 Skolemization Relations + +

4. New Syntactic Features in Alloy 4 + +

4.1 sequence of atoms + +

4.2 private namespace + + + diff --git a/Source/eu.modelwriter.alloyanalyzer/src/help/a4.html b/Source/eu.modelwriter.alloyanalyzer/src/help/a4.html new file mode 100644 index 00000000..309faf5b --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/help/a4.html @@ -0,0 +1,347 @@ + + +New Syntactic Features in Alloy 4 + + + + + + +

New Syntactic Features in Alloy 4

+ +The syntax of Alloy 4 differs very slightly from the syntax of Alloy 3. Changes +were made for three reasons: to make the syntax more uniform; to add some new +features for greater convenience; to simplify the grammar to allow faster +parsing and to make it easier for others to implement tools for Alloy. The +grammar is now LALR(1), and compilation is instantaneous for all but the +largest models. + +

+ +The changes are explained in detail below. For each change, the rationale is +explained, and short comments highlight the small changes that users are likely +to need to make to Alloy 3 models. We expect that, for most users, the only +changes needed will be replacing round by square brackets in invocations, and +adding aliases for imported modules. + +

+ +1. To cast between integers and Int atoms, use Int[ ] and int[ ] + +

+ +

Change: + To cast from integer to an Int atom, + you must use the new "Int[ ]" function.
+ Likewise, to cast from Int to integer, + you should use the new "int[ ]" function.

+ +

Rationale: + This simplifies the grammar by using the function invocation syntax + to do type casts.

+ +

Impact: + To update an Alloy3 model, you need to replace sum X and int X with int[X], and replace Int X with Int[X].

+ + + + + + + + +
Alloy 3    int SomeIntegerSet    Int 2
Alloy 4    int[SomeIntegerSet]    Int[2]
+
+ +2. The ":" symbol can only be used to declare a variable or field. + +
+

Change: To say an expression has + a particular multiplicity, you must now use the "in" operator + rather than the ":" operator.

+ +

Rationale: + The ":" symbol in Alloy 3 has two meanings.
+ The first meaning is to introduce a new name.
+ For example: "some a:A | a!=b".
+ The second meaning is to say that an expression + has a particular multiplicity.
+ For example: "bank.accounts : Person -> one Account".
+ The second usage intuitively fits better with the existing meaning of + the "in" keyword. +

+ +

Impact: Inside a formula, the ":" operator must be changed + to the "in" operator. +

+ +

+ Alloy 3:    bank.accounts : Person -> one Account
+ Alloy 4:    bank.accounts in Person -> one Account +

+
+ +3. if-then-else is now written as "condition=>x else y" + +
+ In Alloy 3, if-then-else formulas are written as "condition=>formula1,formula2" + where as if-then-else expressions are written as "if condition then x else y". +

In Alloy 4, both forms are now replaced by "condition=>x else y". + + + + + + + + + +
Alloy 3 condition
=> formula
 condition
=> formula1,
formula2
 if condition
then expression1
else expression2
Alloy 4 condition
=> formula
 condition
=> formula1
else formula2
 condition
=> expression1
else expression2
+

+ +4. Function/predicate calls must use the + same operators [ ] and . as relational joins. + +
+ To invoke f(a,b), you must write it as f[a,b], + f[a][b], a.f[b], + or b.(a.f) +

+ To invoke f(a), you must write it as f[a] or a.f +

+ To invoke f(), you must write it as f[ ] or simply f +

+ In particular, note that + a.add[b].sub[c] is equivalent to sub[add[a,b],c] +

+ +
+ Likewise, a function or predicate can be declared using [ ]: +
    pred contains [ m:Map, k:Key, v:Value ] {...} +

Furthermore, if the list of arguments is empty, the [ ] can be omitted: +
    pred acyclic {...} +

Finally, the first argument can be declared using the receiver syntax: +
    pred List.contains [ e:Element ] {...} +
is internally converted into +
    pred contains [ this:List, e:Element ] {...} +
+

+ +5. Grammar for int expressions, set/relation expressions, and formulas + are unified. + +
+ This means some expressions legal in Alloy 3 may require + additional parentheses for it to parse. +

+ Operator Precedence (from low to high)

+   let    all a:X|F   no a:X|F   some a:X|F   lone a:X|F   one a:x|F   sum a:x|F
+   ||
+   <=>
+   =>     => else
+   &&
+   !
+   in     =        <        >       <=      >=      !in   !=   !<   !>  !<=  !>=
+   no X   some X   lone X   one X   set X   seq X
+   <<     >>       >>>
+   +      -
+   #X
+   ++
+   &
+   ->
+   <:
+   :>
+   []
+   .
+   ~    *     ^
+   
+ +6. You can no longer set a separate scope on the number of Int atoms. + +
+ Its scope is always exactly equal to the number of possible integers + corresponding to the current bitwidth (default is 4). +

+ To set the bitwidth, use the "int" keyword in a run or check command. +

+ For example, if you write "check MyAssertion for 4 int", + the assertion + will be checked with integer bitwidth + of 4. That means there are exactly 16 Int atoms ranging + from -8 to 7. +

+ +7. You can no longer declare a signature that extends Int, + or declare a signature to be a subset of Int. + +

+ +8. We don't allow "part" and "exh" in declarations any more. + +

Additional Changes

+ +9. Module Search Path: + +
+ When importing a module, Alloy 4 first searches in the installation + directory. + If not found, it will attempt to derive a relative path based on the + current module's name and the name of the module being imported. +

+ For example, if the following model is /Desktop/MyProject/main.als,
+ then we will infer that the "helper" module is + located at /Desktop/MyProject/additonal/helper.als +

+

+   module MyProject/main
+   open MyProject/additional/helper
+   ...
+   
+
+ +10. "module" declaration is now optional. + +
+ If a model is not parametric, you can omit the "module" declaration. +

+ Example 1: +

+ In this example, the first line is require, since it lists + the parameters: +

+ module MyProject/main[T]
+ ... +
+

+ Example 2: +

In this example, the first line is optional. But its presense + or absense will affect where Alloy 4 searches for imported modules. +

+ module MyProject/main
+ open MyProject/library/helper
+ ... +
+ If the module line is specified, then Alloy 4 will infer + that the helper module is located in a subdirectory called + library. +

+ If the module line is omitted, then Alloy 4 assumes the main file + has no path. Thus, the helper module is assumed + to be in the subdirectory MyProject/library. +

+ +11. You can now write "check {...}" and "run {...}" + +
+ Instead of declaring an assertion X and then write check X,
+ you can now combine them by just writing check {some formula}. +

+ Likewise, instead of declaring a predicate X and then write run X,
+ you can now combine them by just writing run {some formula}. +

+ For example:
+   check { A!=B } for 3
+ is equivalent to
+   assert NOTEQUAL { A!=B }
+   check NOTEQUAL for 3 +

+ These are called "anonymous" assertions and predicates.
+ Alternatively, you can prepend an explcit label if you wish.
+ For example: +

+ somelabel: check { A != B } for 3
+ somelabel: run { some a:A, b:B | a=b } for 3 +

+ +12. predicates, functions, and fields can now overload each other. + +
+ That is, you can declare functions, predicates, and fields + with the same name. + When there's an ambiguity, we'll use the following rule to determine + whether each candidate is compatible: +

+ (a) First of all, its value must be relevant to the overall expression. +

+ (b) Furthermore, if it's a predicate or function, then + the type of each parameter must have nonempty intersection with + the type of each argument. +

+ If exactly one function, predicate, or field is compatible, Alloy 4 + will choose it automatically. Otherwise, an ambiguity error will be reported. +

+ +13. When necessary, Alloy4 will add int->Int and Int->int casts automatically. +
+

+ For example, given an atom X, then the relational product X->3 is illegal,
+ since both operands of -> must be set or relation values.
+ Alloy4 knows the only way for this to be legal is to add an int-to-Int cast,
+ so Alloy4 will parse it as if the user wrote X->Int[3] +

+

 

+

+ Likewise, given an Int atom X, then the left shift expression X<<2 is illegal,
+ since both operands of << must be int values.
+ Alloy4 knows the only way for this to be legal is to add an Int-to-int cast,
+ so Alloy4 will parse it as if the user wrote int[X]<<2 +

+

 

+

+ Note: When there are two ways to make an expression legal,
+ by adding either Int-to-int cast or int-to-Int cast,
+ then Alloy4 prefers the Int-to-int cast since it is cheaper. +

+

 

+ For example, given an Int atom X, then the expression X+3 is illegal,
+ since both operands of + must be of the same type.
+ Alloy4 knows there are two ways for this to be legal:
+ 1)   convert X to int[X], and you get the int value representing the sum of X and 3.
+ 2)   convert 3 to Int[3], and you get the union containing two atoms ("X" and "3").
+ Since Alloy4 prefers the Int-to-int cast since it is cheaper,
+ so Alloy4 will parse it as if the user wrote int[X]+3 +

+
+ +14. Alloy4 now has syntax support for "sequence of atoms". +
+ For more information, please click this. +
+ +15. Alloy4 now has syntax support for "private namespace". +
+ Alloy4 allows you to declare a sig, a field, a function, or a predicate as "private" to the module, and not visible from other modules.
+ For more information, please click this. +
+ +16. Alloy4 now supports all the standard operations on int values. +
+ + + + + + + + + + + + + + + + + + + +
addition   a.add[b] (If unambiguous, you can shorten this to be a+b)
subtraction   a.sub[b] (If unambiguous, you can shorten this to be a-b)
multiplication   a.mul[b]  
division   a.div[b]  
remainder   a.rem[b]  
negation   - a  
 
equal   a = b  
not equal   a != b  
less than   a < b  
greater than   a > b  
less than or equal to   a <= b  
greater than or equal to   a >= b  
 
left-shift   a << b  
sign-extended right-shift   a >> b  
zero-extended right-shift   a >>> b  
 
+ Note: The first five operators (add, sub, mul, div, and rem) requires that you add "open util/integer" to your model. +
+ + + diff --git a/Source/eu.modelwriter.alloyanalyzer/src/help/analyses.html b/Source/eu.modelwriter.alloyanalyzer/src/help/analyses.html new file mode 100644 index 00000000..5a656ab7 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/help/analyses.html @@ -0,0 +1,94 @@ + + +Performing Analyses on Alloy Models + + + + + + +

Performing Analyses on Alloy Models

+ +

Loading the Model

+ +

When executing commands in an Alloy model, Alloy always uses +the current content in the text editor as the model to compile.
+Therefore, if you made some changes to the model, +you do not have to save them before running the model. +

+ +

Performing an Analysis on Alloy Models

+ +

A run command is used to search for solutions that satisfy the specification and a predicate, +while a check command is used to search for solutions that satisfy the specification but violate an assertion.

+ +

To run either type of analysis, select the command to be run from +the run menu, or click the "run" toolbar button.

+ +

The run menu will display the list of check and run commands present +in the model. You can execute the commands one at a time, +or click "run all" to execute them all.

+ +

The run toolbar button will executes the most recently executed command. +If no command has been executed, it will execute the first command in +the model.

+ +

The analysis will either terminate with a solution or indicate that one cannot be found within the search space specified by the type scopes of the command.
+If a solution is found, it can be displayed by clicking on the blue clickable hyperlink in the message panel.
+Or, if you enable "visualize automatically" in the Options menu, then the solution will be displayed automatically. +

+ +

Troubleshooting

+ +

Here are some of the common errors that may be encountered:

+ +
    + +
  • Higher-order quantification:
    + The declaration for + a variable in a quantified formula is higher-order and cannot be reduced via + skolemization.
    + Solving such formulas will always take + a significant amount of time and memory + unless the scope is very, very small, + since every combination must be enumerated.
    + Thus, the Alloy Analyzer will not attempt to solve such a formula. +

    +

  • + +
  • The module X cannot be found:
    + The Alloy Analyzer cannot + find the desired model to import in an "open" statement.
    + For details + on the module finding mechanism used, see the page + on module paths. +

    +

  • + +
  • No scope specified for top-level type X in command: +
    A top level type is one that has no supertype (other than the implicit universal type univ).
    + All such types must be given a scope for execution, with the following exceptions:
    + 1) The command has no explicit scopes at all -- all top-level types are given an implicit default scope of 3.
    + 2) Type X is defined as "abstract" -- in this case, if all children + of X are scoped, then the scope of X is inferred and need not be set explicitly. +

    +

  • + +
+ +

Things that may affect execution speed

+ +

Various things may affect the speed of analysis. These include:

+
    +
  • The size of the type scopes specified in the command. The difficulty of the analysis increases with scope in a quicker than linear fashion.

  • +
  • The SAT solver used. By default, the SAT4J solver is used.
    + However, other solvers may fare better in specific models.
    + The SAT solver may changed by clicking the Options menu.

  • +
+ + + diff --git a/Source/eu.modelwriter.alloyanalyzer/src/help/gui.html b/Source/eu.modelwriter.alloyanalyzer/src/help/gui.html new file mode 100644 index 00000000..a2a4170b --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/help/gui.html @@ -0,0 +1,149 @@ + + +About the Alloy Analyzer Layout + + + + + + +

The Alloy Analyzer Layout

+ +

Here is a screenshot of the main window of the Analyzer:

+ +

+ +

Toolbar

+ +

The main toolbar of the Alloy Analyzer provides +quick access to the most commonly used operations:

+ +

+ +
    + +
  • New +Creates a new text file in the editor. +
  • + +
  • Open +Opens an existing Alloy model in the editor. +
  • + +
  • Reload +For each file currently open in the editor, reload its content from the file system. +
  • + +
  • Save +Saves the currently active model in the editor. +
  • + +
  • Execute +Executes the most recently executed command.
    +Executes the first command from the file if no command has been executed so far. +
  • + +
  • Show +Displays the most recent counterexample or instance. +
  • + +
+ +

Editor Panel and Message Panel

+ +

The user interface consists of the editor panel and the message panel.
+ The relative sizes of panels may be adjusted by clicking and dragging the split bars that separate the panels.

+ +
    +
  • Editor panel: contains a tabbed text editor for modifying Alloy models.
    + It supports tabbing so you can edit multiple text files simultaneously.
    + It also supports error highlighting during model compilation. +
  • +

    +

  • Message panel: displays the results of analysis.
    + Each counterexample and each satisfying instance will have a clickable hyperlink.
    + Clicking on it will launch the Alloy Visualizer to display the counterexample or instance. +
  • +
+ +

+The message panel is also used for general status messages and error messages.
+For example, if a model cannot be compiled, an error message is displayed,
+and the error will be highlighted in the source Alloy model (see the figure below): +

+ +

+ +

Options and Preferences

+ +

The preferences can be set by +clicking the Options menu (see the figure below):

+ +

+ +
    + +
  • SAT Solver: + Alloy4 comes prepackaged with a selection of SAT solvers.
    + By default, the pure Java solver "SAT4J" is chosen since it runs on every platform and operating system.
    + If you require faster performance, you can try one of the native solver such as MiniSat or ZChaff.
    + But if MiniSat or ZChaff crashes due to platform or operating system incompatibility, then change the solver back to SAT4J. +

  • + +
  • Warnings are Fatal: + By default, a model that contains one or more compilation warnings cannot be executed. +

  • + +
  • Maximum Memory to Use: + The amount of memory to allocate for Alloy4; larger and more complicated models require more memory. +

  • + +
  • Message Verbosity: + This controls how verbose the messages will be. +

  • + +
  • Font Size: + This controls the font size in the editor panel and the message panel. +

  • + +
  • Font: + This controls the font in the editor panel and the message panel. +

  • + +
  • Tab Size: + This controls the tab size in the editor panel. +

  • + +
  • Skolem Depth: + This controls the maximum depth of alternating universal-vs-existential
    + quantifier that we will permit when generating a skolem function.
    + If a formula exceeds this depth, we will not generate a skolem function for it. +

  • + +
  • Unsat Core Minimization Strategy: + This controls the strategy used to minimize the unsat core.
    + The fast strategy performs no minimization at all.
    + The medium strategy uses a hybrid algorithm that attempts to reduce the core size.
    + The slow strategy guarantees that, at the logic level, the core is a locally minimum core. +

  • + +
  • Visualize Automatically: + If this option is enabled, after executing any command,
    + the Alloy Analyzer will automatically load the visualizer
    + to visualize the counterexample or instance (if any). +

  • + +
  • Record the Kodkod Input/output: + If this option is enabled, after executing any command,
    + then Alloy Analyzer will record the Kodkod input model generated for that command,
    + as well as the Kodkod solution correspoding to that command. +

  • + +
+ + + diff --git a/Source/eu.modelwriter.alloyanalyzer/src/help/image/error.gif b/Source/eu.modelwriter.alloyanalyzer/src/help/image/error.gif new file mode 100644 index 00000000..d37e28b4 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/src/help/image/error.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/src/help/image/general.gif b/Source/eu.modelwriter.alloyanalyzer/src/help/image/general.gif new file mode 100644 index 00000000..52976128 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/src/help/image/general.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/src/help/image/pref.gif b/Source/eu.modelwriter.alloyanalyzer/src/help/image/pref.gif new file mode 100644 index 00000000..eb2fd296 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/src/help/image/pref.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/src/help/image/theme.gif b/Source/eu.modelwriter.alloyanalyzer/src/help/image/theme.gif new file mode 100644 index 00000000..ad9e58d1 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/src/help/image/theme.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/src/help/image/toolbar.gif b/Source/eu.modelwriter.alloyanalyzer/src/help/image/toolbar.gif new file mode 100644 index 00000000..f3cbda1b Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/src/help/image/toolbar.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/src/help/image/toolbarviz.gif b/Source/eu.modelwriter.alloyanalyzer/src/help/image/toolbarviz.gif new file mode 100644 index 00000000..98abc4f5 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/src/help/image/toolbarviz.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/src/help/image/tree.gif b/Source/eu.modelwriter.alloyanalyzer/src/help/image/tree.gif new file mode 100644 index 00000000..40bd56a8 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/src/help/image/tree.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/src/help/image/viz.gif b/Source/eu.modelwriter.alloyanalyzer/src/help/image/viz.gif new file mode 100644 index 00000000..0f27b631 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/src/help/image/viz.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/src/help/image/xml.gif b/Source/eu.modelwriter.alloyanalyzer/src/help/image/xml.gif new file mode 100644 index 00000000..45316860 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/src/help/image/xml.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/src/help/index.html b/Source/eu.modelwriter.alloyanalyzer/src/help/index.html new file mode 100644 index 00000000..bccdfe8c --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/help/index.html @@ -0,0 +1,46 @@ + + +The Alloy Analyzer + + + + + + +

The Alloy Analyzer Quick Guide

+

This user guide is divided into the following sections:

+ +

1. Running the Alloy Analyzer

+ +

2. The Alloy Analyzer GUI

+ +

3. Performing Analyses on Alloy Models +

+

+ +

4. New Syntactic Features in Alloy 4

+ + + + diff --git a/Source/eu.modelwriter.alloyanalyzer/src/help/path.html b/Source/eu.modelwriter.alloyanalyzer/src/help/path.html new file mode 100644 index 00000000..cfc1ede0 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/help/path.html @@ -0,0 +1,67 @@ + + +About Module Paths + + + + + + +

About Module Paths

+ +

Alloy models may contain "open" statements, like this one:

+ +
 open util/integer
+ +

The Alloy "open" statement is roughly similar + to the Java "import" + or the C/C++ "include" in that it tells + the tool to look at some source + in another file. +

+ +

+ Alloy will look for util/integer.als in two places: +

+ +

+ First of all, if the module is one of the sample model that comes with Alloy 4, it will load it.
+ This includes all the "util" modules + such as util/ordering and util/boolean, as well as the example models such as examples/algorithms/dijkstra.
+ You can see the list of sample models by clicking on the File menu, then click on "open sample models". +

+ +

+ Failing that, it will infer the location of the file based on the location + of the current model and the name of the current model. +

+ +

+ For example, let's suppose we are analyzing the model "C:\Desktop\main.als" + with the following content: +
+

+    model filesystem/main
+
+    open filesystem/debug as DEBUG
+    open filesystem/library/dirmodel as DIR
+    open filesystem/library/filemodel as FILE
+   
+

+ +

+ The "filesystem/debug" module is on the same level as "filesystem/main", + so we will infer that it is at C:\Desktop\debug.als +

+ +

+ The dirmodel and filemodel are in a subdirectory called "library", + so we will infer that they are at C:\Desktop\library\dirmodel.als + and C:\Desktop\library\filemodel.als +

+ + diff --git a/Source/eu.modelwriter.alloyanalyzer/src/help/private.html b/Source/eu.modelwriter.alloyanalyzer/src/help/private.html new file mode 100644 index 00000000..f4e4e4c1 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/help/private.html @@ -0,0 +1,129 @@ + + +Skolemization Relations + + + + + + +

private namespace

+ +

+A new reserved keyword "private" has been added for declaring a sig, fun, pred, field, or an entire imported module +as being "private" to this module (and does not show up in the namespace of other modules that import this module). +

+ +

Example 1: private sig, private field, private function, and private predicate

+ + + + + + + + + + + +
+
+  module moduleA
+
+  open moduleB
+
+
+
+  module moduleB
+
+  sig Person {
+      favorite: Book,
+      knows: set Person,
+      private likes: set Person
+  }
+
+  private sig Book { year: Int }
+
+  fun allBook: set Book { Book }
+
+  private fun union[a, b: set univ]: set univ { a+b }
+
+
+ +

+From moduleA, you can refer to "Person", "favorite", "knows", and "allBook". +
+You cannot refer to "likes" because it is a private field, and you cannot refer +to "union" because it is a private function. +

+ +

+From moduleA, you cannot refer to "Book" since it is a private sig, +
+and you cannot refer to "year" because it is a field in a private sig (and thus +it is automatically private). +

+ +

+The important thing to note here is this is purely a namespace management mechanism, and you may still +be able to access these "hidden" values via other names. For example, if you have Person X, you can access +X.favorite to see the Book atom corresponding to his favorite book, even though you cannot refer to the Book signature +by name. Likewise, you can get every atom in the Book sig by calling the "allBook" function. +

+ +

Example 2: private open

+ + + + + + + + + + + + + + + +
+
+  module moduleA
+
+  open moduleB
+
+
+
+  module moduleB
+
+  private open moduleC
+
+  open moduleD
+
+  sig B { }
+
+
+
+  module moduleC
+
+  sig C { }
+
+
+
+  module moduleD
+
+  sig D { }
+
+
+ +

In moduleB, you can certainly refer to B, C, and D.

+ +

In moduleA, you can refer to B and D, but you cannot refer to C, +since moduleB imported moduleC as a "private import".

+ + diff --git a/Source/eu.modelwriter.alloyanalyzer/src/help/run.html b/Source/eu.modelwriter.alloyanalyzer/src/help/run.html new file mode 100644 index 00000000..0bd8f774 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/help/run.html @@ -0,0 +1,32 @@ + + +Running the Alloy Analyzer + + + + + + +

Running the Alloy Analyzer on Mac OS X

+ +

Just open the downloaded alloy4.dmg file (available at alloy.mit.edu), and double click on the Alloy4 icon.
+To keep the application, drag the icon out of the dmg window and place it somewhere in your home directory or on your desktop.

+ +

Running the Alloy Analyzer on other platforms

+ +

Just download the alloy4.jar file (available at alloy.mit.edu) then double-click on the jar file, +or type:

+
 java -jar alloy4.jar
+

+in the console. +

+ +

Please note: +Platform-dependent libraries are currently available only for Mac OS X, Windows (x86), Linux (x86), and FreeBSD (x86).
+On other platforms, you can still run the Alloy Analyzer, but you will need to select the pure Java solver "SAT4J" (through the Options menu). + + diff --git a/Source/eu.modelwriter.alloyanalyzer/src/help/seq.html b/Source/eu.modelwriter.alloyanalyzer/src/help/seq.html new file mode 100644 index 00000000..9a14affe --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/help/seq.html @@ -0,0 +1,172 @@ + + +Skolemization Relations + + + + + + +

sequence of atoms

+ +

+A new reserved keyword "seq" has been added for declaring a field as a sequence of atoms.
+In the following example, for each person p, "p.books" is a sequence of Book: +

+ +
+  sig Book { }
+  sig Person {
+      books: seq Book
+  }
+
+ +

+The actual type of a sequence of Book is "Int->Book".
+So if s is a sequence of Book, then the first element is s[0]
+and you can get the set of all elements by writing "univ.s"
+

+ +

+You can also use "seq" in quantifications, like this: +

+ +
some s: seq Book | FORMULA
+ +

+You can also use "seq" in function argument declaration, like this: +

+ +
+  fun getAllElements [s: seq Book] : set Book {
+      univ.s
+  }
+
+ +

+Just like the other multiplicity symbols, when you use "seq" +in a function argument declaration, we do not enforce that +you always call the function/predicate with a well-formed sequence. +So it is only for documentation purpose, to denote s +is a binary relation from Int->Book. +

+ +

+Note: for effifiency, we bound the length of allowed sequences. +You can change this bound by setting the scope on "seq".
+For example, if you want to allow sequences of up to 4 elements, +you write +

+ +
  check SomeAssertion for 4 seq
+
+ +

+To make it easier to manipulate sequences, +we provide a number of helper functions: (these are +defined in the pre-included util/sequniv.als file) +

+ +

#s
+ Return the number of elements in sequence s.
+

+ +

s.elems
+ Return the set of elements in sequence s.
+

+ +

s.first
+ If #s > 0, it returns the first element of s
+ Otherwise, it returns the empty set
+

+ +

s.last
+ If #s > 0, it returns the last element of s
+ Otherwise, it returns the empty set
+

+ +

s.rest
+ If #s > 1, it returns s with its first element removed
+ Otherwise, it returns the empty sequence
+

+ +

s.butLast
+ If #s > 1, it returns s with its last element removed
+ Otherwise, it returns the empty sequence
+

+ +

s.isEmpty
+ It returns true if #s==0.
+

+ +

s.hasDups
+ It returns true if s contains duplicate elements.
+

+ +

s.inds
+ If #s > 0, it returns the set of integers {0 .. (#s)-1}
+ Otherwise, it returns the empty set
+

+ +

s.lastIdx
+ If #s > 0, it returns the integer (#s)-1
+ Otherwise, it returns the empty set
+

+ +

s.afterLastIdx
+ If (#s < the longest allowed sequence length), it returns #s
+ Otherwise, it returns the empty set
+

+ +

s.idxOf [x]
+ If x does not appear in s, it returns the empty set.
+ Otherwise, it returns the first index where x appears in s.
+

+ +

s.lastIdxOf [x]
+ If x does not appear in s, it returns the empty set.
+ Otherwise, it returns the last index where x appears in s.
+

+ +

s.indsOf [x]
+ If x does not appear in s, it returns the empty set.
+ Otherwise, it returns the set of indices where x appears in s.
+

+ +

s.add [x]
+ If (#s < the longest allowed sequence length), it appends x to s.
+ Otherwise, it returns s.
+

+ +

s.setAt [i, x]
+ Precondition: 0 <= i < #s
+ It returns a new sequence where the i-th entry is changed to x.
+

+ +

s.insert [i, x]
+ Precondition: 0 <= i <= #s
+ It returns a new sequence where x is inserted at index i.
+ Note: if #s was already equal to the longest allowed sequence + length, then the last element of s will be removed first.
+

+ +

s.delete [i]
+ Precondition: 0 <= i < #s
+ It returns the result of deleting the element at index i
+

+ +

a.append [b]
+ Returns the result of concatenating sequence a and sequence b
+ (If the resulting sequence is too longer, it will be truncated)
+

+ +

s.subseq [from, to]
+ Precondition: 0 <= from <= to < #s
+ Returns the subsequence between from and to, inclusively.
+

+ + diff --git a/Source/eu.modelwriter.alloyanalyzer/src/help/skolem.html b/Source/eu.modelwriter.alloyanalyzer/src/help/skolem.html new file mode 100644 index 00000000..d77a2c24 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/help/skolem.html @@ -0,0 +1,38 @@ + + +Skolemization Relations + + + + + + +

Skolemization Relations

+

Introduction

+

Often times, quantified formulas can be reduced to equivalent formulas without the use of quantifiers.
+This reduction is called skolemization and is based on the introduction of one or more skolem constants or functions
+that capture the constraint of the quantified formula in their values.

+

Consider the following example:

+
sig A { r: lone B }
+sig B {}
+
fact {
+    some x: A | no x.r
+}
+

The "some" formula may be equivalently expressed as:

+
    x' in A && no x'.r 
+

x' is the skolem reation in this case. The existential quantifier "some" is not needed because the analysis will search for the existence of the skolem realtion x'.

+

 

+

Determining the names of skolem relations

+

The Alloy Analyzer automatically generates and assigns names to skolem relations.
+The names can be determined by looking at the solved instance using any of the three (Text, Tree, Viz) views.
+For instance, in the above example, the name $x may have been assigned to the skolem relation x'.

+

 

+

Using skolem relations

+

Skolem relations are displayed in the output like any other relation in the original model. Hence, its visualization may be customized to be made more conspicuous. +

+ + diff --git a/Source/eu.modelwriter.alloyanalyzer/src/help/treeview.html b/Source/eu.modelwriter.alloyanalyzer/src/help/treeview.html new file mode 100644 index 00000000..9d19b0dd --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/help/treeview.html @@ -0,0 +1,23 @@ + + +Tree View + + + + + + +

Tree View

+

As implied by its name, the Tree view displays an instance in a tree. +The nodes of the tree may be expanded/collapsed to reveal/hide the values +of relations. A relation node (such as that for a signature or a field) +is expanded to reveal its tuples, and an atom may be expanded to reveal +the value of joining the atom with the fields defined in its correponding +signature.

+

+ + diff --git a/Source/eu.modelwriter.alloyanalyzer/src/help/util.html b/Source/eu.modelwriter.alloyanalyzer/src/help/util.html new file mode 100644 index 00000000..1e3153da --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/help/util.html @@ -0,0 +1,72 @@ + + +Alloy Models + + + + + + +

Utility Modules

+ +

+Alloy 4 contains a number of utility modules that provide common +operations on graphs, integers, etc. Here is a list of the modules +and a short description for each module: +

+ +module util/boolean + +
+ Creates a Bool type with two singleton subtypes: True and False. +
+ +module util/graph[node] + +
+ Utilities for common operations and contraints on graphs. +
+ +module util/integer + +
+ Utilities for using integers in Alloy. +
+ +module util/natural + +
+ Utilities for using the set of nonnegative integers (0, 1, 2, ...). +
+ +module util/ordering[element] + +
+ Creates a single linear ordering over the atoms in elem. +
+ +module util/relation + +
+ Utilities for common operations and constraints on binary relations. +
+ +module util/sequniv + +
+ This module models each sequence of elements using a relation.
+ (This module is imported automatically if your model uses the new seq keyword. +
+ +module util/ternary + +
+ Utilities for common operations and constraints on ternary relations. +
+ + + diff --git a/Source/eu.modelwriter.alloyanalyzer/src/help/viz.html b/Source/eu.modelwriter.alloyanalyzer/src/help/viz.html new file mode 100644 index 00000000..f7d25cf2 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/help/viz.html @@ -0,0 +1,42 @@ + + +About the Alloy Analyzer Layout + + + + + + +

Visualizer

+ +

The visualizer offers 3 views, which can be selected + in its toolbar at the top (see the figure below).

+ +

+ +

Visualization Modes

+ +
    + +
  • Viz: brings up the graphical view. + The labels, colors, and various other settings can be configured + by clicking the Theme button.
    + +
  • + +
  • Tree: brings up the tree view.
    + +
  • + +
  • XML: shows the instance as an XML document.
    + +
  • + +
+ + + diff --git a/Source/eu.modelwriter.alloyanalyzer/src/help/vizview.html b/Source/eu.modelwriter.alloyanalyzer/src/help/vizview.html new file mode 100644 index 00000000..95cfe06d --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/help/vizview.html @@ -0,0 +1,23 @@ + + +Visualization View + + + + + + +

Visualization View

+

The Visualization ("Viz") view displays instances as a graph, + where each node is an atom (member of a signature) and arcs represent the + relations between atoms. The graph may be customized by +clicking on the theme toolbar button.

+

+

+ + + diff --git a/Source/eu.modelwriter.alloyanalyzer/src/help/xmlview.html b/Source/eu.modelwriter.alloyanalyzer/src/help/xmlview.html new file mode 100644 index 00000000..d7bb965f --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/help/xmlview.html @@ -0,0 +1,57 @@ + + +XML View + + + + + + +

XML View

+ +

As implied by its name, the XML view displays an instance +in XML format. If you save the XML text, you can load the instance +later from the visualizer.

+ +

Here is a sample XML file:

+ +
+
+<alloy>
+
+<sig name="Name" extends="univ">
+  <atom name="Name$0"/>
+  <atom name="Name$1"/>
+</sig>
+
+<sig name="Date" extends="univ">
+  <atom name="Date$0"/>
+</sig>
+
+<sig name="BirthdayBook" extends="univ">
+  <atom name="BirthdayBook$0"/>
+  <atom name="BirthdayBook$1"/>
+</sig>
+
+<field name="known">
+    <type> <sig name="BirthdayBook"/> <sig name="Name"/> </type>
+    <tuple> <atom name="BirthdayBook$1"/> <atom name="Name$1"/> </tuple>
+</field>
+
+<field name="date">
+    <type> <sig name="BirthdayBook"/> <sig name="Name"/> <sig name="Date"/> </type>
+    <tuple> <atom name="BirthdayBook$1"/> <atom name="Name$1"/> <atom name="Date$0"/> </tuple>
+</field>
+
+</instance>
+
+</alloy>
+
+
+ + + diff --git a/Source/eu.modelwriter.alloyanalyzer/src/icons/ColorIcons/black.gif b/Source/eu.modelwriter.alloyanalyzer/src/icons/ColorIcons/black.gif new file mode 100644 index 00000000..bba84da2 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/src/icons/ColorIcons/black.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/src/icons/ColorIcons/blue.gif b/Source/eu.modelwriter.alloyanalyzer/src/icons/ColorIcons/blue.gif new file mode 100644 index 00000000..e6b2a7e9 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/src/icons/ColorIcons/blue.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/src/icons/ColorIcons/cadetblue.gif b/Source/eu.modelwriter.alloyanalyzer/src/icons/ColorIcons/cadetblue.gif new file mode 100644 index 00000000..d01b37fe Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/src/icons/ColorIcons/cadetblue.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/src/icons/ColorIcons/chartreuse2.gif b/Source/eu.modelwriter.alloyanalyzer/src/icons/ColorIcons/chartreuse2.gif new file mode 100644 index 00000000..0502739f Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/src/icons/ColorIcons/chartreuse2.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/src/icons/ColorIcons/cornflowerblue.gif b/Source/eu.modelwriter.alloyanalyzer/src/icons/ColorIcons/cornflowerblue.gif new file mode 100644 index 00000000..64a86a64 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/src/icons/ColorIcons/cornflowerblue.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/src/icons/ColorIcons/cyan.gif b/Source/eu.modelwriter.alloyanalyzer/src/icons/ColorIcons/cyan.gif new file mode 100644 index 00000000..720fb8cd Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/src/icons/ColorIcons/cyan.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/src/icons/ColorIcons/darkolivegreen2.gif b/Source/eu.modelwriter.alloyanalyzer/src/icons/ColorIcons/darkolivegreen2.gif new file mode 100644 index 00000000..827b3f9c Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/src/icons/ColorIcons/darkolivegreen2.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/src/icons/ColorIcons/gold.gif b/Source/eu.modelwriter.alloyanalyzer/src/icons/ColorIcons/gold.gif new file mode 100644 index 00000000..f6dc8ce8 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/src/icons/ColorIcons/gold.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/src/icons/ColorIcons/green2.gif b/Source/eu.modelwriter.alloyanalyzer/src/icons/ColorIcons/green2.gif new file mode 100644 index 00000000..5b689651 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/src/icons/ColorIcons/green2.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/src/icons/ColorIcons/lightgoldenrod.gif b/Source/eu.modelwriter.alloyanalyzer/src/icons/ColorIcons/lightgoldenrod.gif new file mode 100644 index 00000000..27fb805b Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/src/icons/ColorIcons/lightgoldenrod.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/src/icons/ColorIcons/lightgray.gif b/Source/eu.modelwriter.alloyanalyzer/src/icons/ColorIcons/lightgray.gif new file mode 100644 index 00000000..b3f1bfa7 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/src/icons/ColorIcons/lightgray.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/src/icons/ColorIcons/limegreen.gif b/Source/eu.modelwriter.alloyanalyzer/src/icons/ColorIcons/limegreen.gif new file mode 100644 index 00000000..29edc92b Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/src/icons/ColorIcons/limegreen.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/src/icons/ColorIcons/magenta.gif b/Source/eu.modelwriter.alloyanalyzer/src/icons/ColorIcons/magenta.gif new file mode 100644 index 00000000..8d3d29ca Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/src/icons/ColorIcons/magenta.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/src/icons/ColorIcons/magic.gif b/Source/eu.modelwriter.alloyanalyzer/src/icons/ColorIcons/magic.gif new file mode 100644 index 00000000..61598714 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/src/icons/ColorIcons/magic.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/src/icons/ColorIcons/palevioletred.gif b/Source/eu.modelwriter.alloyanalyzer/src/icons/ColorIcons/palevioletred.gif new file mode 100644 index 00000000..1de7a229 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/src/icons/ColorIcons/palevioletred.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/src/icons/ColorIcons/red.gif b/Source/eu.modelwriter.alloyanalyzer/src/icons/ColorIcons/red.gif new file mode 100644 index 00000000..c7604a5a Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/src/icons/ColorIcons/red.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/src/icons/ColorIcons/salmon.gif b/Source/eu.modelwriter.alloyanalyzer/src/icons/ColorIcons/salmon.gif new file mode 100644 index 00000000..1d3dca18 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/src/icons/ColorIcons/salmon.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/src/icons/ColorIcons/white.gif b/Source/eu.modelwriter.alloyanalyzer/src/icons/ColorIcons/white.gif new file mode 100644 index 00000000..02a767d3 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/src/icons/ColorIcons/white.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/src/icons/ColorIcons/yellow.gif b/Source/eu.modelwriter.alloyanalyzer/src/icons/ColorIcons/yellow.gif new file mode 100644 index 00000000..a09f1c23 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/src/icons/ColorIcons/yellow.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/src/icons/ShapeIcons/Mcircle.gif b/Source/eu.modelwriter.alloyanalyzer/src/icons/ShapeIcons/Mcircle.gif new file mode 100644 index 00000000..49a659f8 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/src/icons/ShapeIcons/Mcircle.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/src/icons/ShapeIcons/Mdiamond.gif b/Source/eu.modelwriter.alloyanalyzer/src/icons/ShapeIcons/Mdiamond.gif new file mode 100644 index 00000000..071f9807 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/src/icons/ShapeIcons/Mdiamond.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/src/icons/ShapeIcons/Msquare.gif b/Source/eu.modelwriter.alloyanalyzer/src/icons/ShapeIcons/Msquare.gif new file mode 100644 index 00000000..2db39335 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/src/icons/ShapeIcons/Msquare.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/src/icons/ShapeIcons/box.gif b/Source/eu.modelwriter.alloyanalyzer/src/icons/ShapeIcons/box.gif new file mode 100644 index 00000000..788dee55 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/src/icons/ShapeIcons/box.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/src/icons/ShapeIcons/circle.gif b/Source/eu.modelwriter.alloyanalyzer/src/icons/ShapeIcons/circle.gif new file mode 100644 index 00000000..87fe62e4 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/src/icons/ShapeIcons/circle.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/src/icons/ShapeIcons/diamond.gif b/Source/eu.modelwriter.alloyanalyzer/src/icons/ShapeIcons/diamond.gif new file mode 100644 index 00000000..e0a1df4b Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/src/icons/ShapeIcons/diamond.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/src/icons/ShapeIcons/doublecircle.gif b/Source/eu.modelwriter.alloyanalyzer/src/icons/ShapeIcons/doublecircle.gif new file mode 100644 index 00000000..33156e8c Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/src/icons/ShapeIcons/doublecircle.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/src/icons/ShapeIcons/doubleoctagon.gif b/Source/eu.modelwriter.alloyanalyzer/src/icons/ShapeIcons/doubleoctagon.gif new file mode 100644 index 00000000..064307f3 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/src/icons/ShapeIcons/doubleoctagon.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/src/icons/ShapeIcons/egg.gif b/Source/eu.modelwriter.alloyanalyzer/src/icons/ShapeIcons/egg.gif new file mode 100644 index 00000000..3d49e5a9 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/src/icons/ShapeIcons/egg.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/src/icons/ShapeIcons/ellipse.gif b/Source/eu.modelwriter.alloyanalyzer/src/icons/ShapeIcons/ellipse.gif new file mode 100644 index 00000000..83dc2f41 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/src/icons/ShapeIcons/ellipse.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/src/icons/ShapeIcons/hexagon.gif b/Source/eu.modelwriter.alloyanalyzer/src/icons/ShapeIcons/hexagon.gif new file mode 100644 index 00000000..e85ae033 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/src/icons/ShapeIcons/hexagon.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/src/icons/ShapeIcons/house.gif b/Source/eu.modelwriter.alloyanalyzer/src/icons/ShapeIcons/house.gif new file mode 100644 index 00000000..4812b028 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/src/icons/ShapeIcons/house.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/src/icons/ShapeIcons/invhouse.gif b/Source/eu.modelwriter.alloyanalyzer/src/icons/ShapeIcons/invhouse.gif new file mode 100644 index 00000000..71dc4b83 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/src/icons/ShapeIcons/invhouse.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/src/icons/ShapeIcons/invtrapezium.gif b/Source/eu.modelwriter.alloyanalyzer/src/icons/ShapeIcons/invtrapezium.gif new file mode 100644 index 00000000..7eaf0c04 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/src/icons/ShapeIcons/invtrapezium.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/src/icons/ShapeIcons/invtriangle.gif b/Source/eu.modelwriter.alloyanalyzer/src/icons/ShapeIcons/invtriangle.gif new file mode 100644 index 00000000..c3394467 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/src/icons/ShapeIcons/invtriangle.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/src/icons/ShapeIcons/octagon.gif b/Source/eu.modelwriter.alloyanalyzer/src/icons/ShapeIcons/octagon.gif new file mode 100644 index 00000000..7ec0b4bb Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/src/icons/ShapeIcons/octagon.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/src/icons/ShapeIcons/parallelogram.gif b/Source/eu.modelwriter.alloyanalyzer/src/icons/ShapeIcons/parallelogram.gif new file mode 100644 index 00000000..a4925d03 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/src/icons/ShapeIcons/parallelogram.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/src/icons/ShapeIcons/trapezium.gif b/Source/eu.modelwriter.alloyanalyzer/src/icons/ShapeIcons/trapezium.gif new file mode 100644 index 00000000..2381a684 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/src/icons/ShapeIcons/trapezium.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/src/icons/ShapeIcons/triangle.gif b/Source/eu.modelwriter.alloyanalyzer/src/icons/ShapeIcons/triangle.gif new file mode 100644 index 00000000..730a1b0c Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/src/icons/ShapeIcons/triangle.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/src/icons/ShapeIcons/tripleoctagon.gif b/Source/eu.modelwriter.alloyanalyzer/src/icons/ShapeIcons/tripleoctagon.gif new file mode 100644 index 00000000..1f604228 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/src/icons/ShapeIcons/tripleoctagon.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/src/icons/StyleIcons/bold.gif b/Source/eu.modelwriter.alloyanalyzer/src/icons/StyleIcons/bold.gif new file mode 100644 index 00000000..dda8863b Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/src/icons/StyleIcons/bold.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/src/icons/StyleIcons/dashed.gif b/Source/eu.modelwriter.alloyanalyzer/src/icons/StyleIcons/dashed.gif new file mode 100644 index 00000000..b12004d2 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/src/icons/StyleIcons/dashed.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/src/icons/StyleIcons/dotted.gif b/Source/eu.modelwriter.alloyanalyzer/src/icons/StyleIcons/dotted.gif new file mode 100644 index 00000000..be61201c Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/src/icons/StyleIcons/dotted.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/src/icons/StyleIcons/solid.gif b/Source/eu.modelwriter.alloyanalyzer/src/icons/StyleIcons/solid.gif new file mode 100644 index 00000000..7d837f15 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/src/icons/StyleIcons/solid.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/src/images/24_execute.gif b/Source/eu.modelwriter.alloyanalyzer/src/images/24_execute.gif new file mode 100644 index 00000000..ddd93932 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/src/images/24_execute.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/src/images/24_execute_abort2.gif b/Source/eu.modelwriter.alloyanalyzer/src/images/24_execute_abort2.gif new file mode 100644 index 00000000..332d42ab Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/src/images/24_execute_abort2.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/src/images/24_graph.gif b/Source/eu.modelwriter.alloyanalyzer/src/images/24_graph.gif new file mode 100644 index 00000000..ac830a0a Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/src/images/24_graph.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/src/images/24_history.gif b/Source/eu.modelwriter.alloyanalyzer/src/images/24_history.gif new file mode 100644 index 00000000..c18eb6b4 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/src/images/24_history.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/src/images/24_new.gif b/Source/eu.modelwriter.alloyanalyzer/src/images/24_new.gif new file mode 100644 index 00000000..9d9f0ee2 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/src/images/24_new.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/src/images/24_open.gif b/Source/eu.modelwriter.alloyanalyzer/src/images/24_open.gif new file mode 100644 index 00000000..de4696ce Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/src/images/24_open.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/src/images/24_plaintext.gif b/Source/eu.modelwriter.alloyanalyzer/src/images/24_plaintext.gif new file mode 100644 index 00000000..7eace4b3 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/src/images/24_plaintext.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/src/images/24_reload.gif b/Source/eu.modelwriter.alloyanalyzer/src/images/24_reload.gif new file mode 100644 index 00000000..2652ff48 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/src/images/24_reload.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/src/images/24_save.gif b/Source/eu.modelwriter.alloyanalyzer/src/images/24_save.gif new file mode 100644 index 00000000..e243bd53 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/src/images/24_save.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/src/images/24_settings.gif b/Source/eu.modelwriter.alloyanalyzer/src/images/24_settings.gif new file mode 100644 index 00000000..0195f5bb Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/src/images/24_settings.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/src/images/24_settings_apply.gif b/Source/eu.modelwriter.alloyanalyzer/src/images/24_settings_apply.gif new file mode 100644 index 00000000..87fe2dc1 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/src/images/24_settings_apply.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/src/images/24_settings_apply2.gif b/Source/eu.modelwriter.alloyanalyzer/src/images/24_settings_apply2.gif new file mode 100644 index 00000000..b91773cb Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/src/images/24_settings_apply2.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/src/images/24_settings_apply3.gif b/Source/eu.modelwriter.alloyanalyzer/src/images/24_settings_apply3.gif new file mode 100644 index 00000000..7472cf1e Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/src/images/24_settings_apply3.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/src/images/24_settings_close.gif b/Source/eu.modelwriter.alloyanalyzer/src/images/24_settings_close.gif new file mode 100644 index 00000000..a550fa6d Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/src/images/24_settings_close.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/src/images/24_settings_close2.gif b/Source/eu.modelwriter.alloyanalyzer/src/images/24_settings_close2.gif new file mode 100644 index 00000000..652ad79f Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/src/images/24_settings_close2.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/src/images/24_settings_close3.gif b/Source/eu.modelwriter.alloyanalyzer/src/images/24_settings_close3.gif new file mode 100644 index 00000000..20f9c5fb Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/src/images/24_settings_close3.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/src/images/24_settings_close4.gif b/Source/eu.modelwriter.alloyanalyzer/src/images/24_settings_close4.gif new file mode 100644 index 00000000..50209486 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/src/images/24_settings_close4.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/src/images/24_settings_close5.gif b/Source/eu.modelwriter.alloyanalyzer/src/images/24_settings_close5.gif new file mode 100644 index 00000000..3c417b5e Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/src/images/24_settings_close5.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/src/images/24_texttree.gif b/Source/eu.modelwriter.alloyanalyzer/src/images/24_texttree.gif new file mode 100644 index 00000000..ab735b1f Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/src/images/24_texttree.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/src/images/cb0.gif b/Source/eu.modelwriter.alloyanalyzer/src/images/cb0.gif new file mode 100644 index 00000000..4e47578d Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/src/images/cb0.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/src/images/cb1.gif b/Source/eu.modelwriter.alloyanalyzer/src/images/cb1.gif new file mode 100644 index 00000000..491dcb86 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/src/images/cb1.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/src/images/logo.gif b/Source/eu.modelwriter.alloyanalyzer/src/images/logo.gif new file mode 100644 index 00000000..c23a7f05 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/src/images/logo.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/src/images/menu0.gif b/Source/eu.modelwriter.alloyanalyzer/src/images/menu0.gif new file mode 100644 index 00000000..b1d498cd Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/src/images/menu0.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/src/images/menu1.gif b/Source/eu.modelwriter.alloyanalyzer/src/images/menu1.gif new file mode 100644 index 00000000..f204c0b5 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/src/images/menu1.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/src/images/tcb01.gif b/Source/eu.modelwriter.alloyanalyzer/src/images/tcb01.gif new file mode 100644 index 00000000..dd3972f8 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/src/images/tcb01.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/src/images/tcb02.gif b/Source/eu.modelwriter.alloyanalyzer/src/images/tcb02.gif new file mode 100644 index 00000000..7755c002 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/src/images/tcb02.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/src/images/tcb03.gif b/Source/eu.modelwriter.alloyanalyzer/src/images/tcb03.gif new file mode 100644 index 00000000..d3ad1e95 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/src/images/tcb03.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/src/images/tcb04.gif b/Source/eu.modelwriter.alloyanalyzer/src/images/tcb04.gif new file mode 100644 index 00000000..d0dff531 Binary files /dev/null and b/Source/eu.modelwriter.alloyanalyzer/src/images/tcb04.gif differ diff --git a/Source/eu.modelwriter.alloyanalyzer/src/java_cup/runtime/DefaultSymbolFactory.java b/Source/eu.modelwriter.alloyanalyzer/src/java_cup/runtime/DefaultSymbolFactory.java new file mode 100644 index 00000000..0421f089 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/java_cup/runtime/DefaultSymbolFactory.java @@ -0,0 +1,54 @@ +package java_cup.runtime; + +import edu.mit.csail.sdg.alloy4.Pos; + +/** + * Default Implementation for SymbolFactory, creates + * plain old Symbols + * + * @version last updated 27-03-2006 + * @author Michael Petter + */ + +/* ************************************************* + class DefaultSymbolFactory + + interface for creating new symbols + ***************************************************/ +public class DefaultSymbolFactory implements SymbolFactory{ + // Factory methods + /** + * DefaultSymbolFactory for CUP. + * Users are strongly encoraged to use ComplexSymbolFactory instead, since + * it offers more detailed information about Symbols in source code. + * Yet since migrating has always been a critical process, You have the + * chance of still using the oldstyle Symbols. + * + *@deprecated as of CUP v11a + * replaced by the new java_cup.runtime.ComplexSymbolFactory + */ + //@deprecated + public DefaultSymbolFactory(){ + } + public Symbol newSymbol(String name ,int id, Symbol left, Symbol right, Object value){ + return new Symbol(id,left,right,value); + } + public Symbol newSymbol(String name, int id, Symbol left, Symbol right){ + return new Symbol(id,left,right); + } + public Symbol newSymbol(String name, int id, int left, int right, Object value){ + return new Symbol(id,left,right,value); + } + public Symbol newSymbol(String name, int id, int left, int right){ + return new Symbol(id,left,right); + } + public Symbol startSymbol(String name, int id, int state){ + return new Symbol(id,state); + } + public Symbol newSymbol(String name, int id){ + return new Symbol(id); + } + public Symbol newSymbol(String name, Pos pos, int id, Object value) { + return new Symbol(id,pos,value); + } +} \ No newline at end of file diff --git a/Source/eu.modelwriter.alloyanalyzer/src/java_cup/runtime/Scanner.java b/Source/eu.modelwriter.alloyanalyzer/src/java_cup/runtime/Scanner.java new file mode 100644 index 00000000..53114c01 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/java_cup/runtime/Scanner.java @@ -0,0 +1,25 @@ +package java_cup.runtime; + +/** + * Defines the Scanner interface, which CUP uses in the default + * implementation of lr_parser.scan(). Integration + * of scanners implementing Scanner is facilitated. + * + * @version last updated 23-Jul-1999 + * @author David MacMahon + */ + +/* ************************************************* + Interface Scanner + + Declares the next_token() method that should be + implemented by scanners. This method is typically + called by lr_parser.scan(). End-of-file can be + indicated either by returning + new Symbol(lr_parser.EOF_sym()) or + null. + ***************************************************/ +public interface Scanner { + /** Return the next token, or null on end-of-file. */ + public Symbol next_token() throws java.lang.Exception; +} \ No newline at end of file diff --git a/Source/eu.modelwriter.alloyanalyzer/src/java_cup/runtime/Symbol.java b/Source/eu.modelwriter.alloyanalyzer/src/java_cup/runtime/Symbol.java new file mode 100644 index 00000000..9a5d622c --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/java_cup/runtime/Symbol.java @@ -0,0 +1,119 @@ +package java_cup.runtime; + +import edu.mit.csail.sdg.alloy4.Pos; + +/** + * Defines the Symbol class, which is used to represent all terminals + * and nonterminals while parsing. The lexer should pass CUP Symbols + * and CUP returns a Symbol. + * + * @version last updated: 7/3/96 + * @author Frank Flannery + */ + +/* **************************************************************** + Class Symbol + what the parser expects to receive from the lexer. + the token is identified as follows: + sym: the symbol type + parse_state: the parse state. + value: is the lexical value of type Object + left : is the left position in the original input file + right: is the right position in the original input file + xleft: is the left position Object in the original input file + xright: is the left position Object in the original input file +******************************************************************/ + +public class Symbol { + +// TUM 20060327: Added new Constructor to provide more flexible way +// for location handling +/******************************* + *******************************/ + public Symbol(int id, Symbol left, Symbol right, Object o){ + this(id,left.left,right.right,o); + } + public Symbol(int id, Symbol left, Symbol right){ + this(id,left.left,right.right); + } +/******************************* + Constructor for l,r values + *******************************/ + + public Symbol(int id, int l, int r, Object o) { + this(id); + left = l; + right = r; + value = o; + } + +/******************************* + Constructor for no l,r values +********************************/ + + public Symbol(int id, Pos pos, Object o) { + this(id, -1, -1, o); + this.pos = pos; + } + +/***************************** + Constructor for no value + ***************************/ + + public Symbol(int id, int l, int r) { + this(id, l, r, null); + } + +/*********************************** + Constructor for no value or l,r +***********************************/ + + public Symbol(int sym_num) { + this(sym_num, -1); + left = -1; + right = -1; + } + +/*********************************** + Constructor to give a start state +***********************************/ + Symbol(int sym_num, int state) + { + sym = sym_num; + parse_state = state; + } + +/*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/ + + /** The symbol number of the terminal or non terminal being represented */ + public int sym; + + /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/ + + /** The parse state to be recorded on the parse stack with this symbol. + * This field is for the convenience of the parser and shouldn't be + * modified except by the parser. + */ + public int parse_state; + /** This allows us to catch some errors caused by scanners recycling + * symbols. For the use of the parser only. [CSA, 23-Jul-1999] */ + boolean used_by_parser = false; + +/******************************* + The data passed to parser + *******************************/ + + public Pos pos; + public int left, right; + public Object value; + + /***************************** + Printing this token out. (Override for pretty-print). + ****************************/ + public String toString() { return "#"+sym; } +} + + + + + diff --git a/Source/eu.modelwriter.alloyanalyzer/src/java_cup/runtime/SymbolFactory.java b/Source/eu.modelwriter.alloyanalyzer/src/java_cup/runtime/SymbolFactory.java new file mode 100644 index 00000000..9b640d76 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/java_cup/runtime/SymbolFactory.java @@ -0,0 +1,35 @@ +package java_cup.runtime; + +import edu.mit.csail.sdg.alloy4.Pos; + +/** + * Creates the Symbols interface, which CUP uses as default + * + * @version last updated 27-03-2006 + * @author Michael Petter + */ + +/* ************************************************* + Interface SymbolFactory + + interface for creating new symbols + You can also use this interface for your own callback hooks + Declare Your own factory methods for creation of Objects in Your scanner! + ***************************************************/ +public interface SymbolFactory { + // Factory methods + /** + * Construction with left/right propagation switched on + */ + public Symbol newSymbol(String name, int id, Symbol left, Symbol right, Object value); + public Symbol newSymbol(String name, int id, Symbol left, Symbol right); + /** + * Construction with left/right propagation switched off + */ + public Symbol newSymbol(String name, Pos pos, int id, Object value); + public Symbol newSymbol(String name, int id); + /** + * Construction of start symbol + */ + public Symbol startSymbol(String name, int id, int state); +} \ No newline at end of file diff --git a/Source/eu.modelwriter.alloyanalyzer/src/java_cup/runtime/lr_parser.java b/Source/eu.modelwriter.alloyanalyzer/src/java_cup/runtime/lr_parser.java new file mode 100644 index 00000000..1ac3d543 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/java_cup/runtime/lr_parser.java @@ -0,0 +1,1253 @@ + +package java_cup.runtime; + +import java.util.Stack; + +/** This class implements a skeleton table driven LR parser. In general, + * LR parsers are a form of bottom up shift-reduce parsers. Shift-reduce + * parsers act by shifting input onto a parse stack until the Symbols + * matching the right hand side of a production appear on the top of the + * stack. Once this occurs, a reduce is performed. This involves removing + * the Symbols corresponding to the right hand side of the production + * (the so called "handle") and replacing them with the non-terminal from + * the left hand side of the production.

+ * + * To control the decision of whether to shift or reduce at any given point, + * the parser uses a state machine (the "viable prefix recognition machine" + * built by the parser generator). The current state of the machine is placed + * on top of the parse stack (stored as part of a Symbol object representing + * a terminal or non terminal). The parse action table is consulted + * (using the current state and the current lookahead Symbol as indexes) to + * determine whether to shift or to reduce. When the parser shifts, it + * changes to a new state by pushing a new Symbol (containing a new state) + * onto the stack. When the parser reduces, it pops the handle (right hand + * side of a production) off the stack. This leaves the parser in the state + * it was in before any of those Symbols were matched. Next the reduce-goto + * table is consulted (using the new state and current lookahead Symbol as + * indexes) to determine a new state to go to. The parser then shifts to + * this goto state by pushing the left hand side Symbol of the production + * (also containing the new state) onto the stack.

+ * + * This class actually provides four LR parsers. The methods parse() and + * debug_parse() provide two versions of the main parser (the only difference + * being that debug_parse() emits debugging trace messages as it parses). + * In addition to these main parsers, the error recovery mechanism uses two + * more. One of these is used to simulate "parsing ahead" in the input + * without carrying out actions (to verify that a potential error recovery + * has worked), and the other is used to parse through buffered "parse ahead" + * input in order to execute all actions and re-synchronize the actual parser + * configuration.

+ * + * This is an abstract class which is normally filled out by a subclass + * generated by the JavaCup parser generator. In addition to supplying + * the actual parse tables, generated code also supplies methods which + * invoke various pieces of user supplied code, provide access to certain + * special Symbols (e.g., EOF and error), etc. Specifically, the following + * abstract methods are normally supplied by generated code: + *

+ *
short[][] production_table() + *
Provides a reference to the production table (indicating the index of + * the left hand side non terminal and the length of the right hand side + * for each production in the grammar). + *
short[][] action_table() + *
Provides a reference to the parse action table. + *
short[][] reduce_table() + *
Provides a reference to the reduce-goto table. + *
int start_state() + *
Indicates the index of the start state. + *
int start_production() + *
Indicates the index of the starting production. + *
int EOF_sym() + *
Indicates the index of the EOF Symbol. + *
int error_sym() + *
Indicates the index of the error Symbol. + *
Symbol do_action() + *
Executes a piece of user supplied action code. This always comes at + * the point of a reduce in the parse, so this code also allocates and + * fills in the left hand side non terminal Symbol object that is to be + * pushed onto the stack for the reduce. + *
void init_actions() + *
Code to initialize a special object that encapsulates user supplied + * actions (this object is used by do_action() to actually carry out the + * actions). + *
+ * + * In addition to these routines that must be supplied by the + * generated subclass there are also a series of routines that may + * be supplied. These include: + *
+ *
Symbol scan() + *
Used to get the next input Symbol from the scanner. + *
Scanner getScanner() + *
Used to provide a scanner for the default implementation of + * scan(). + *
int error_sync_size() + *
This determines how many Symbols past the point of an error + * must be parsed without error in order to consider a recovery to + * be valid. This defaults to 3. Values less than 2 are not + * recommended. + *
void report_error(String message, Object info) + *
This method is called to report an error. The default implementation + * simply prints a message to System.err and where the error occurred. + * This method is often replaced in order to provide a more sophisticated + * error reporting mechanism. + *
void report_fatal_error(String message, Object info) + *
This method is called when a fatal error that cannot be recovered from + * is encountered. In the default implementation, it calls + * report_error() to emit a message, then throws an exception. + *
void syntax_error(Symbol cur_token) + *
This method is called as soon as syntax error is detected (but + * before recovery is attempted). In the default implementation it + * invokes: report_error("Syntax error", null); + *
void unrecovered_syntax_error(Symbol cur_token) + *
This method is called if syntax error recovery fails. In the default + * implementation it invokes:
+ * report_fatal_error("Couldn't repair and continue parse", null); + *
+ * + * @see java_cup.runtime.Symbol + * @see java_cup.runtime.Symbol + * @see java_cup.runtime.virtual_parse_stack + * @version last updated: 7/3/96 + * @author Frank Flannery + */ +@SuppressWarnings("unchecked") +public abstract class lr_parser { + /*-----------------------------------------------------------*/ + /*--- Constructor(s) ----------------------------------------*/ + /*-----------------------------------------------------------*/ + + /** + * Simple constructor. + */ + public lr_parser() { + symbolFactory = new DefaultSymbolFactory(); + } + + /** + * Constructor that sets the default scanner. [CSA/davidm] + */ + public lr_parser(Scanner s) { + this(s,new DefaultSymbolFactory()); // TUM 20060327 old cup v10 Symbols as default + } + /** + * Constructor that sets the default scanner and a SymbolFactory + */ + public lr_parser(Scanner s, SymbolFactory symfac) { + this(); // in case default constructor someday does something + symbolFactory = symfac; + setScanner(s); + } + public SymbolFactory symbolFactory;// = new DefaultSymbolFactory(); + /** + * Whenever creation of a new Symbol is necessary, one should use this factory. + */ + public SymbolFactory getSymbolFactory(){ + return symbolFactory; + } + /*-----------------------------------------------------------*/ + /*--- (Access to) Static (Class) Variables ------------------*/ + /*-----------------------------------------------------------*/ + + /** The default number of Symbols after an error we much match to consider + * it recovered from. + */ + protected final static int _error_sync_size = 3; + + /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/ + + /** The number of Symbols after an error we much match to consider it + * recovered from. + */ + protected int error_sync_size() {return _error_sync_size; } + + /*-----------------------------------------------------------*/ + /*--- (Access to) Instance Variables ------------------------*/ + /*-----------------------------------------------------------*/ + + /** Table of production information (supplied by generated subclass). + * This table contains one entry per production and is indexed by + * the negative-encoded values (reduce actions) in the action_table. + * Each entry has two parts, the index of the non-terminal on the + * left hand side of the production, and the number of Symbols + * on the right hand side. + */ + public abstract short[][] production_table(); + + /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/ + + /** The action table (supplied by generated subclass). This table is + * indexed by state and terminal number indicating what action is to + * be taken when the parser is in the given state (i.e., the given state + * is on top of the stack) and the given terminal is next on the input. + * States are indexed using the first dimension, however, the entries for + * a given state are compacted and stored in adjacent index, value pairs + * which are searched for rather than accessed directly (see get_action()). + * The actions stored in the table will be either shifts, reduces, or + * errors. Shifts are encoded as positive values (one greater than the + * state shifted to). Reduces are encoded as negative values (one less + * than the production reduced by). Error entries are denoted by zero. + * + * @see java_cup.runtime.lr_parser#get_action + */ + public abstract short[][] action_table(); + + /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/ + + /** The reduce-goto table (supplied by generated subclass). This + * table is indexed by state and non-terminal number and contains + * state numbers. States are indexed using the first dimension, however, + * the entries for a given state are compacted and stored in adjacent + * index, value pairs which are searched for rather than accessed + * directly (see get_reduce()). When a reduce occurs, the handle + * (corresponding to the RHS of the matched production) is popped off + * the stack. The new top of stack indicates a state. This table is + * then indexed by that state and the LHS of the reducing production to + * indicate where to "shift" to. + * + * @see java_cup.runtime.lr_parser#get_reduce + */ + public abstract short[][] reduce_table(); + + /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/ + + /** The index of the start state (supplied by generated subclass). */ + public abstract int start_state(); + + /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/ + + /** The index of the start production (supplied by generated subclass). */ + public abstract int start_production(); + + /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/ + + /** The index of the end of file terminal Symbol (supplied by generated + * subclass). + */ + public abstract int EOF_sym(); + + /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/ + + /** The index of the special error Symbol (supplied by generated subclass). */ + public abstract int error_sym(); + + /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/ + + /** Internal flag to indicate when parser should quit. */ + protected boolean _done_parsing = false; + + /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/ + + /** This method is called to indicate that the parser should quit. This is + * normally called by an accept action, but can be used to cancel parsing + * early in other circumstances if desired. + */ + public void done_parsing() + { + _done_parsing = true; + } + + /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/ + /* Global parse state shared by parse(), error recovery, and + * debugging routines */ + /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/ + + /** Indication of the index for top of stack (for use by actions). */ + protected int tos; + + /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/ + + /** The current lookahead Symbol. */ + protected Symbol cur_token; + + /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/ + + /** The parse stack itself. */ + protected Stack stack = new Stack(); + + /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/ + + /** Direct reference to the production table. */ + protected short[][] production_tab; + + /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/ + + /** Direct reference to the action table. */ + protected short[][] action_tab; + + /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/ + + /** Direct reference to the reduce-goto table. */ + protected short[][] reduce_tab; + + /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/ + + /** This is the scanner object used by the default implementation + * of scan() to get Symbols. To avoid name conflicts with existing + * code, this field is private. [CSA/davidm] */ + private Scanner _scanner; + + /** + * Simple accessor method to set the default scanner. + */ + public void setScanner(Scanner s) { _scanner = s; } + + /** + * Simple accessor method to get the default scanner. + */ + public Scanner getScanner() { return _scanner; } + + /*-----------------------------------------------------------*/ + /*--- General Methods ---------------------------------------*/ + /*-----------------------------------------------------------*/ + + /** Perform a bit of user supplied action code (supplied by generated + * subclass). Actions are indexed by an internal action number assigned + * at parser generation time. + * + * @param act_num the internal index of the action to be performed. + * @param parser the parser object we are acting for. + * @param stack the parse stack of that object. + * @param top the index of the top element of the parse stack. + */ + public abstract Symbol do_action( + int act_num, + lr_parser parser, + Stack stack, + int top) + throws java.lang.Exception; + + /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/ + + /** User code for initialization inside the parser. Typically this + * initializes the scanner. This is called before the parser requests + * the first Symbol. Here this is just a placeholder for subclasses that + * might need this and we perform no action. This method is normally + * overridden by the generated code using this contents of the "init with" + * clause as its body. + */ + public void user_init() throws java.lang.Exception { } + + /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/ + + /** Initialize the action object. This is called before the parser does + * any parse actions. This is filled in by generated code to create + * an object that encapsulates all action code. + */ + protected abstract void init_actions() throws java.lang.Exception; + + /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/ + + /** Get the next Symbol from the input (supplied by generated subclass). + * Once end of file has been reached, all subsequent calls to scan + * should return an EOF Symbol (which is Symbol number 0). By default + * this method returns getScanner().next_token(); this implementation + * can be overriden by the generated parser using the code declared in + * the "scan with" clause. Do not recycle objects; every call to + * scan() should return a fresh object. + */ + public Symbol scan() throws java.lang.Exception { + Symbol sym = getScanner().next_token(); + return (sym!=null) ? sym : getSymbolFactory().newSymbol("END_OF_FILE",EOF_sym()); + } + + /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/ + + /** Report a fatal error. This method takes a message string and an + * additional object (to be used by specializations implemented in + * subclasses). Here in the base class a very simple implementation + * is provided which reports the error then throws an exception. + * + * @param message an error message. + * @param info an extra object reserved for use by specialized subclasses. + */ + public void report_fatal_error( + String message, + Object info) + throws java.lang.Exception + { + /* stop parsing (not really necessary since we throw an exception, but) */ + done_parsing(); + + /* use the normal error message reporting to put out the message */ + report_error(message, info); + + /* throw an exception */ + throw new Exception("Can't recover from previous error(s)"); + } + + /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/ + + /** Report a non fatal error (or warning). This method takes a message + * string and an additional object (to be used by specializations + * implemented in subclasses). Here in the base class a very simple + * implementation is provided which simply prints the message to + * System.err. + * + * @param message an error message. + * @param info an extra object reserved for use by specialized subclasses. + */ + public void report_error(String message, Object info) + { + System.err.print(message); + System.err.flush(); + if (info instanceof Symbol) + if (((Symbol)info).left != -1) + System.err.println(" at character " + ((Symbol)info).left + + " of input"); + else System.err.println(""); + else System.err.println(""); + } + + /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/ + + /** This method is called when a syntax error has been detected and recovery + * is about to be invoked. Here in the base class we just emit a + * "Syntax error" error message. + * + * @param cur_token the current lookahead Symbol. + */ + public void syntax_error(Symbol cur_token) throws Exception + { + report_error("Syntax error", cur_token); + } + + /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/ + + /** This method is called if it is determined that syntax error recovery + * has been unsuccessful. Here in the base class we report a fatal error. + * + * @param cur_token the current lookahead Symbol. + */ + public void unrecovered_syntax_error(Symbol cur_token) + throws java.lang.Exception + { + report_fatal_error("Couldn't repair and continue parse", cur_token); + } + + /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/ + + /** Fetch an action from the action table. The table is broken up into + * rows, one per state (rows are indexed directly by state number). + * Within each row, a list of index, value pairs are given (as sequential + * entries in the table), and the list is terminated by a default entry + * (denoted with a Symbol index of -1). To find the proper entry in a row + * we do a linear or binary search (depending on the size of the row). + * + * @param state the state index of the action being accessed. + * @param sym the Symbol index of the action being accessed. + */ + protected final short get_action(int state, int sym) + { + short tag; + int first, last, probe; + short[] row = action_tab[state]; + + /* linear search if we are < 10 entries */ + if (row.length < 20) + for (probe = 0; probe < row.length; probe++) + { + /* is this entry labeled with our Symbol or the default? */ + tag = row[probe++]; + if (tag == sym || tag == -1) + { + /* return the next entry */ + return row[probe]; + } + } + /* otherwise binary search */ + else + { + first = 0; + last = (row.length-1)/2 - 1; /* leave out trailing default entry */ + while (first <= last) + { + probe = (first+last)/2; + if (sym == row[probe*2]) + return row[probe*2+1]; + else if (sym > row[probe*2]) + first = probe+1; + else + last = probe-1; + } + + /* not found, use the default at the end */ + return row[row.length-1]; + } + + /* shouldn't happened, but if we run off the end we return the + default (error == 0) */ + return 0; + } + + /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/ + + /** Fetch a state from the reduce-goto table. The table is broken up into + * rows, one per state (rows are indexed directly by state number). + * Within each row, a list of index, value pairs are given (as sequential + * entries in the table), and the list is terminated by a default entry + * (denoted with a Symbol index of -1). To find the proper entry in a row + * we do a linear search. + * + * @param state the state index of the entry being accessed. + * @param sym the Symbol index of the entry being accessed. + */ + protected final short get_reduce(int state, int sym) + { + short tag; + short[] row = reduce_tab[state]; + + /* if we have a null row we go with the default */ + if (row == null) + return -1; + + for (int probe = 0; probe < row.length; probe++) + { + /* is this entry labeled with our Symbol or the default? */ + tag = row[probe++]; + if (tag == sym || tag == -1) + { + /* return the next entry */ + return row[probe]; + } + } + /* if we run off the end we return the default (error == -1) */ + return -1; + } + + /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/ + + /** This method provides the main parsing routine. It returns only when + * done_parsing() has been called (typically because the parser has + * accepted, or a fatal error has been reported). See the header + * documentation for the class regarding how shift/reduce parsers operate + * and how the various tables are used. + */ +public Symbol parse() throws java.lang.Exception + { + /* the current action code */ + int act; + + /* the Symbol/stack element returned by a reduce */ + Symbol lhs_sym = null; + + /* information about production being reduced with */ + short handle_size, lhs_sym_num; + + /* set up direct reference to tables to drive the parser */ + + production_tab = production_table(); + action_tab = action_table(); + reduce_tab = reduce_table(); + + /* initialize the action encapsulation object */ + init_actions(); + + /* do user initialization */ + user_init(); + + /* get the first token */ + cur_token = scan(); + + /* push dummy Symbol with start state to get us underway */ + stack.removeAllElements(); + stack.push(getSymbolFactory().startSymbol("START", 0, start_state())); + tos = 0; + + /* continue until we are told to stop */ + for (_done_parsing = false; !_done_parsing; ) + { + /* Check current token for freshness. */ + if (cur_token.used_by_parser) + throw new Error("Symbol recycling detected (fix your scanner)."); + + /* current state is always on the top of the stack */ + + /* look up action out of the current state with the current input */ + act = get_action(((Symbol)stack.peek()).parse_state, cur_token.sym); + + /* decode the action -- > 0 encodes shift */ + if (act > 0) + { + /* shift to the encoded state by pushing it on the stack */ + cur_token.parse_state = act-1; + cur_token.used_by_parser = true; + stack.push(cur_token); + tos++; + + /* advance to the next Symbol */ + cur_token = scan(); + } + /* if its less than zero, then it encodes a reduce action */ + else if (act < 0) + { + /* perform the action for the reduce */ + lhs_sym = do_action((-act)-1, this, stack, tos); + + /* look up information about the production */ + lhs_sym_num = production_tab[(-act)-1][0]; + handle_size = production_tab[(-act)-1][1]; + + /* pop the handle off the stack */ + for (int i = 0; i < handle_size; i++) + { + stack.pop(); + tos--; + } + + /* look up the state to go to from the one popped back to */ + act = get_reduce(((Symbol)stack.peek()).parse_state, lhs_sym_num); + + /* shift to that state */ + lhs_sym.parse_state = act; + lhs_sym.used_by_parser = true; + stack.push(lhs_sym); + tos++; + } + /* finally if the entry is zero, we have an error */ + else if (act == 0) + { + /* call user syntax error reporting routine */ + syntax_error(cur_token); + + /* try to error recover */ + if (!error_recovery(false)) + { + /* if that fails give up with a fatal syntax error */ + unrecovered_syntax_error(cur_token); + + /* just in case that wasn't fatal enough, end parse */ + done_parsing(); + } else { + lhs_sym = (Symbol)stack.peek(); + } + } + } + return lhs_sym; + } + + /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/ + + /** Write a debugging message to System.err for the debugging version + * of the parser. + * + * @param mess the text of the debugging message. + */ + public void debug_message(String mess) + { + System.err.println(mess); + } + + /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/ + + /** Dump the parse stack for debugging purposes. */ + public void dump_stack() + { + if (stack == null) + { + debug_message("# Stack dump requested, but stack is null"); + return; + } + + debug_message("============ Parse Stack Dump ============"); + + /* dump the stack */ + for (int i=0; i"); + if ((i%3)==2 || (i==(stack.size()-1))) { + debug_message(sb.toString()); + sb = new StringBuffer(" "); + } + } + } + + /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/ + + /** Perform a parse with debugging output. This does exactly the + * same things as parse(), except that it calls debug_shift() and + * debug_reduce() when shift and reduce moves are taken by the parser + * and produces various other debugging messages. + */ + public Symbol debug_parse() + throws java.lang.Exception + { + /* the current action code */ + int act; + + /* the Symbol/stack element returned by a reduce */ + Symbol lhs_sym = null; + + /* information about production being reduced with */ + short handle_size, lhs_sym_num; + + /* set up direct reference to tables to drive the parser */ + production_tab = production_table(); + action_tab = action_table(); + reduce_tab = reduce_table(); + + debug_message("# Initializing parser"); + + /* initialize the action encapsulation object */ + init_actions(); + + /* do user initialization */ + user_init(); + + /* the current Symbol */ + cur_token = scan(); + + debug_message("# Current Symbol is #" + cur_token.sym); + + /* push dummy Symbol with start state to get us underway */ + stack.removeAllElements(); + stack.push(getSymbolFactory().startSymbol("START",0, start_state())); + tos = 0; + + /* continue until we are told to stop */ + for (_done_parsing = false; !_done_parsing; ) + { + /* Check current token for freshness. */ + if (cur_token.used_by_parser) + throw new Error("Symbol recycling detected (fix your scanner)."); + + /* current state is always on the top of the stack */ + //debug_stack(); + + /* look up action out of the current state with the current input */ + act = get_action(((Symbol)stack.peek()).parse_state, cur_token.sym); + + /* decode the action -- > 0 encodes shift */ + if (act > 0) + { + /* shift to the encoded state by pushing it on the stack */ + cur_token.parse_state = act-1; + cur_token.used_by_parser = true; + debug_shift(cur_token); + stack.push(cur_token); + tos++; + + /* advance to the next Symbol */ + cur_token = scan(); + debug_message("# Current token is " + cur_token); + } + /* if its less than zero, then it encodes a reduce action */ + else if (act < 0) + { + /* perform the action for the reduce */ + lhs_sym = do_action((-act)-1, this, stack, tos); + + /* look up information about the production */ + lhs_sym_num = production_tab[(-act)-1][0]; + handle_size = production_tab[(-act)-1][1]; + + debug_reduce((-act)-1, lhs_sym_num, handle_size); + + /* pop the handle off the stack */ + for (int i = 0; i < handle_size; i++) + { + stack.pop(); + tos--; + } + + /* look up the state to go to from the one popped back to */ + act = get_reduce(((Symbol)stack.peek()).parse_state, lhs_sym_num); + debug_message("# Reduce rule: top state " + + ((Symbol)stack.peek()).parse_state + + ", lhs sym " + lhs_sym_num + " -> state " + act); + + /* shift to that state */ + lhs_sym.parse_state = act; + lhs_sym.used_by_parser = true; + stack.push(lhs_sym); + tos++; + + debug_message("# Goto state #" + act); + } + /* finally if the entry is zero, we have an error */ + else if (act == 0) + { + /* call user syntax error reporting routine */ + syntax_error(cur_token); + + /* try to error recover */ + if (!error_recovery(true)) + { + /* if that fails give up with a fatal syntax error */ + unrecovered_syntax_error(cur_token); + + /* just in case that wasn't fatal enough, end parse */ + done_parsing(); + } else { + lhs_sym = (Symbol)stack.peek(); + } + } + } + return lhs_sym; + } + + /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/ + /* Error recovery code */ + /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/ + + /** Attempt to recover from a syntax error. This returns false if recovery + * fails, true if it succeeds. Recovery happens in 4 steps. First we + * pop the parse stack down to a point at which we have a shift out + * of the top-most state on the error Symbol. This represents the + * initial error recovery configuration. If no such configuration is + * found, then we fail. Next a small number of "lookahead" or "parse + * ahead" Symbols are read into a buffer. The size of this buffer is + * determined by error_sync_size() and determines how many Symbols beyond + * the error must be matched to consider the recovery a success. Next, + * we begin to discard Symbols in attempt to get past the point of error + * to a point where we can continue parsing. After each Symbol, we attempt + * to "parse ahead" though the buffered lookahead Symbols. The "parse ahead" + * process simulates that actual parse, but does not modify the real + * parser's configuration, nor execute any actions. If we can parse all + * the stored Symbols without error, then the recovery is considered a + * success. Once a successful recovery point is determined, we do an + * actual parse over the stored input -- modifying the real parse + * configuration and executing all actions. Finally, we return the the + * normal parser to continue with the overall parse. + * + * @param debug should we produce debugging messages as we parse. + */ + protected boolean error_recovery(boolean debug) + throws java.lang.Exception + { + if (debug) debug_message("# Attempting error recovery"); + + /* first pop the stack back into a state that can shift on error and + do that shift (if that fails, we fail) */ + if (!find_recovery_config(debug)) + { + if (debug) debug_message("# Error recovery fails"); + return false; + } + + /* read ahead to create lookahead we can parse multiple times */ + read_lookahead(); + + /* repeatedly try to parse forward until we make it the required dist */ + for (;;) + { + /* try to parse forward, if it makes it, bail out of loop */ + if (debug) debug_message("# Trying to parse ahead"); + if (try_parse_ahead(debug)) + { + break; + } + + /* if we are now at EOF, we have failed */ + if (lookahead[0].sym == EOF_sym()) + { + if (debug) debug_message("# Error recovery fails at EOF"); + return false; + } + + /* otherwise, we consume another Symbol and try again */ + // BUG FIX by Bruce Hutton + // Computer Science Department, University of Auckland, + // Auckland, New Zealand. + // It is the first token that is being consumed, not the one + // we were up to parsing + if (debug) + debug_message("# Consuming Symbol #" + lookahead[ 0 ].sym); + restart_lookahead(); + } + + /* we have consumed to a point where we can parse forward */ + if (debug) debug_message("# Parse-ahead ok, going back to normal parse"); + + /* do the real parse (including actions) across the lookahead */ + parse_lookahead(debug); + + /* we have success */ + return true; + } + + /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/ + + /** Determine if we can shift under the special error Symbol out of the + * state currently on the top of the (real) parse stack. + */ + protected boolean shift_under_error() + { + /* is there a shift under error Symbol */ + return get_action(((Symbol)stack.peek()).parse_state, error_sym()) > 0; + } + + /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/ + + /** Put the (real) parse stack into error recovery configuration by + * popping the stack down to a state that can shift on the special + * error Symbol, then doing the shift. If no suitable state exists on + * the stack we return false + * + * @param debug should we produce debugging messages as we parse. + */ + protected boolean find_recovery_config(boolean debug) + { + Symbol error_token; + int act; + + if (debug) debug_message("# Finding recovery state on stack"); + + /* Remember the right-position of the top symbol on the stack */ + Symbol right = ((Symbol)stack.peek());// TUM 20060327 removed .right + Symbol left = right;// TUM 20060327 removed .left + + /* pop down until we can shift under error Symbol */ + while (!shift_under_error()) + { + /* pop the stack */ + if (debug) + debug_message("# Pop stack by one, state was # " + + ((Symbol)stack.peek()).parse_state); + left = ((Symbol)stack.pop()); // TUM 20060327 removed .left + tos--; + + /* if we have hit bottom, we fail */ + if (stack.empty()) + { + if (debug) debug_message("# No recovery state found on stack"); + return false; + } + } + + /* state on top of the stack can shift under error, find the shift */ + act = get_action(((Symbol)stack.peek()).parse_state, error_sym()); + if (debug) + { + debug_message("# Recover state found (#" + + ((Symbol)stack.peek()).parse_state + ")"); + debug_message("# Shifting on error to state #" + (act-1)); + } + + /* build and shift a special error Symbol */ + error_token = getSymbolFactory().newSymbol("ERROR",error_sym(), left, right); + error_token.parse_state = act-1; + error_token.used_by_parser = true; + stack.push(error_token); + tos++; + + return true; + } + + /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/ + + /** Lookahead Symbols used for attempting error recovery "parse aheads". */ + protected Symbol lookahead[]; + + /** Position in lookahead input buffer used for "parse ahead". */ + protected int lookahead_pos; + + /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/ + + /** Read from input to establish our buffer of "parse ahead" lookahead + * Symbols. + */ + protected void read_lookahead() throws java.lang.Exception + { + /* create the lookahead array */ + lookahead = new Symbol[error_sync_size()]; + + /* fill in the array */ + for (int i = 0; i < error_sync_size(); i++) + { + lookahead[i] = cur_token; + cur_token = scan(); + } + + /* start at the beginning */ + lookahead_pos = 0; + } + + /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/ + + /** Return the current lookahead in our error "parse ahead" buffer. */ + protected Symbol cur_err_token() { return lookahead[lookahead_pos]; } + + /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/ + + /** Advance to next "parse ahead" input Symbol. Return true if we have + * input to advance to, false otherwise. + */ + protected boolean advance_lookahead() + { + /* advance the input location */ + lookahead_pos++; + + /* return true if we didn't go off the end */ + return lookahead_pos < error_sync_size(); + } + + /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/ + + /** Reset the parse ahead input to one Symbol past where we started error + * recovery (this consumes one new Symbol from the real input). + */ + protected void restart_lookahead() throws java.lang.Exception + { + /* move all the existing input over */ + for (int i = 1; i < error_sync_size(); i++) + lookahead[i-1] = lookahead[i]; + + /* read a new Symbol into the last spot */ + // BUG Fix by Bruce Hutton + // Computer Science Department, University of Auckland, + // Auckland, New Zealand. [applied 5-sep-1999 by csa] + // The following two lines were out of order!! + lookahead[error_sync_size()-1] = cur_token; + cur_token = scan(); + + /* reset our internal position marker */ + lookahead_pos = 0; + } + + /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/ + + /** Do a simulated parse forward (a "parse ahead") from the current + * stack configuration using stored lookahead input and a virtual parse + * stack. Return true if we make it all the way through the stored + * lookahead input without error. This basically simulates the action of + * parse() using only our saved "parse ahead" input, and not executing any + * actions. + * + * @param debug should we produce debugging messages as we parse. + */ + protected boolean try_parse_ahead(boolean debug) + throws java.lang.Exception + { + int act; + short lhs, rhs_size; + + /* create a virtual stack from the real parse stack */ + virtual_parse_stack vstack = new virtual_parse_stack(stack); + + /* parse until we fail or get past the lookahead input */ + for (;;) + { + /* look up the action from the current state (on top of stack) */ + act = get_action(vstack.top(), cur_err_token().sym); + + /* if its an error, we fail */ + if (act == 0) return false; + + /* > 0 encodes a shift */ + if (act > 0) + { + /* push the new state on the stack */ + vstack.push(act-1); + + if (debug) debug_message("# Parse-ahead shifts Symbol #" + + cur_err_token().sym + " into state #" + (act-1)); + + /* advance simulated input, if we run off the end, we are done */ + if (!advance_lookahead()) return true; + } + /* < 0 encodes a reduce */ + else + { + /* if this is a reduce with the start production we are done */ + if ((-act)-1 == start_production()) + { + if (debug) debug_message("# Parse-ahead accepts"); + return true; + } + + /* get the lhs Symbol and the rhs size */ + lhs = production_tab[(-act)-1][0]; + rhs_size = production_tab[(-act)-1][1]; + + /* pop handle off the stack */ + for (int i = 0; i < rhs_size; i++) + vstack.pop(); + + if (debug) + debug_message("# Parse-ahead reduces: handle size = " + + rhs_size + " lhs = #" + lhs + " from state #" + vstack.top()); + + /* look up goto and push it onto the stack */ + vstack.push(get_reduce(vstack.top(), lhs)); + if (debug) + debug_message("# Goto state #" + vstack.top()); + } + } + } + + /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/ + + /** Parse forward using stored lookahead Symbols. In this case we have + * already verified that parsing will make it through the stored lookahead + * Symbols and we are now getting back to the point at which we can hand + * control back to the normal parser. Consequently, this version of the + * parser performs all actions and modifies the real parse configuration. + * This returns once we have consumed all the stored input or we accept. + * + * @param debug should we produce debugging messages as we parse. + */ + protected void parse_lookahead(boolean debug) + throws java.lang.Exception + { + /* the current action code */ + int act; + + /* the Symbol/stack element returned by a reduce */ + Symbol lhs_sym = null; + + /* information about production being reduced with */ + short handle_size, lhs_sym_num; + + /* restart the saved input at the beginning */ + lookahead_pos = 0; + + if (debug) + { + debug_message("# Reparsing saved input with actions"); + debug_message("# Current Symbol is #" + cur_err_token().sym); + debug_message("# Current state is #" + + ((Symbol)stack.peek()).parse_state); + } + + /* continue until we accept or have read all lookahead input */ + while(!_done_parsing) + { + /* current state is always on the top of the stack */ + + /* look up action out of the current state with the current input */ + act = + get_action(((Symbol)stack.peek()).parse_state, cur_err_token().sym); + + /* decode the action -- > 0 encodes shift */ + if (act > 0) + { + /* shift to the encoded state by pushing it on the stack */ + cur_err_token().parse_state = act-1; + cur_err_token().used_by_parser = true; + if (debug) debug_shift(cur_err_token()); + stack.push(cur_err_token()); + tos++; + + /* advance to the next Symbol, if there is none, we are done */ + if (!advance_lookahead()) + { + if (debug) debug_message("# Completed reparse"); + + /* scan next Symbol so we can continue parse */ + // BUGFIX by Chris Harris : + // correct a one-off error by commenting out + // this next line. + /*cur_token = scan();*/ + + /* go back to normal parser */ + return; + } + + if (debug) + debug_message("# Current Symbol is #" + cur_err_token().sym); + } + /* if its less than zero, then it encodes a reduce action */ + else if (act < 0) + { + /* perform the action for the reduce */ + lhs_sym = do_action((-act)-1, this, stack, tos); + + /* look up information about the production */ + lhs_sym_num = production_tab[(-act)-1][0]; + handle_size = production_tab[(-act)-1][1]; + + if (debug) debug_reduce((-act)-1, lhs_sym_num, handle_size); + + /* pop the handle off the stack */ + for (int i = 0; i < handle_size; i++) + { + stack.pop(); + tos--; + } + + /* look up the state to go to from the one popped back to */ + act = get_reduce(((Symbol)stack.peek()).parse_state, lhs_sym_num); + + /* shift to that state */ + lhs_sym.parse_state = act; + lhs_sym.used_by_parser = true; + stack.push(lhs_sym); + tos++; + + if (debug) debug_message("# Goto state #" + act); + + } + /* finally if the entry is zero, we have an error + (shouldn't happen here, but...)*/ + else if (act == 0) + { + report_fatal_error("Syntax error", lhs_sym); + return; + } + } + + + } + + /*-----------------------------------------------------------*/ + + /** Utility function: unpacks parse tables from strings */ + protected static short[][] unpackFromStrings(String[] sa) + { + // Concatanate initialization strings. + StringBuffer sb = new StringBuffer(sa[0]); + for (int i=1; i= real_stack.size()) return; + + /* get a copy of the first Symbol we have not transfered */ + stack_sym = (Symbol)real_stack.elementAt(real_stack.size()-1-real_next); + + /* record the transfer */ + real_next++; + + /* put the state number from the Symbol onto the virtual stack */ + vstack.push(new Integer(stack_sym.parse_state)); + } + + /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/ + + /** Indicate whether the stack is empty. */ + public boolean empty() + { + /* if vstack is empty then we were unable to transfer onto it and + the whole thing is empty. */ + return vstack.empty(); + } + + /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/ + + /** Return value on the top of the stack (without popping it). */ + public int top() throws java.lang.Exception + { + if (vstack.empty()) + throw new Exception( + "Internal parser error: top() called on empty virtual stack"); + + return ((Integer)vstack.peek()).intValue(); + } + + /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/ + + /** Pop the stack. */ + public void pop() throws java.lang.Exception + { + if (vstack.empty()) + throw new Exception( + "Internal parser error: pop from empty virtual stack"); + + /* pop it */ + vstack.pop(); + + /* if we are now empty transfer an element (if there is one) */ + if (vstack.empty()) + get_from_real(); + } + + /*. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .*/ + + /** Push a state number onto the stack. */ + public void push(int state_num) + { + vstack.push(new Integer(state_num)); + } + + /*-----------------------------------------------------------*/ + +} \ No newline at end of file diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/BinaryExpression.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/BinaryExpression.java new file mode 100644 index 00000000..b664587f --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/BinaryExpression.java @@ -0,0 +1,126 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.ast; + +import kodkod.ast.operator.ExprOperator; +import kodkod.ast.visitor.ReturnVisitor; +import kodkod.ast.visitor.VoidVisitor; + + +/** + * A relational {@link kodkod.ast.Expression expression} with two children. + * + * @specfield left: Expression + * @specfield right: Expression + * @specfield op: ExprOperator + * @specfield op.binary() + * @invariant children = 0->left + 1->right + * @author Emina Torlak + */ +public final class BinaryExpression extends Expression { + private final ExprOperator op; + private final Expression left; + private final Expression right; + private final int arity; + + /** + * Constructs a new binary expression: left op right + * + * @ensures this.left' = left && this.right' = right && this.op' = op + * @throws NullPointerException - left = null || right = null || op = null + * @throws IllegalArgumentException - left and right cannot be combined with the specified operator. + */ + BinaryExpression(final Expression left, final ExprOperator op, final Expression right) { + switch(op) { + case UNION : case INTERSECTION : case DIFFERENCE : case OVERRIDE : + this.arity = left.arity(); + if (arity!=right.arity()) + throw new IllegalArgumentException("Incompatible arities: " + left + " and " + right); + break; + case JOIN : + this.arity = left.arity() + right.arity() - 2; + if (arity < 1) + throw new IllegalArgumentException("Incompatible arities: " + left + " and " + right); + break; + case PRODUCT : + this.arity = left.arity() + right.arity(); + break; + default : + throw new IllegalArgumentException("Not a binary operator: " + op); + } + + this.op = op; + this.left = left; + this.right = right; + + } + + /** + * Returns the arity of this binary expression. + * @return this.arity + * @see kodkod.ast.Expression#arity() + */ + public int arity() { + return arity; + } + + /** + * Returns this.op. + * @return this.op + */ + public ExprOperator op() { return op ; } + /** + * Returns the left child of this. + * @return this.left + */ + public Expression left() {return left;} + + /** + * Returns the right child of this. + * @return this.right + */ + public Expression right() {return right;} + + /** + * {@inheritDoc} + * @see kodkod.ast.Expression#accept(kodkod.ast.visitor.ReturnVisitor) + */ + public E accept(ReturnVisitor visitor) { + return visitor.visit(this); + } + + /** + * {@inheritDoc} + * @see kodkod.ast.Node#accept(kodkod.ast.visitor.VoidVisitor) + */ + public void accept(VoidVisitor visitor) { + visitor.visit(this); + } + + /** + * {@inheritDoc} + * @see kodkod.ast.Node#toString() + */ + public String toString() { + return "(" + left + " " + op + " " + right + ")"; + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/BinaryFormula.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/BinaryFormula.java new file mode 100644 index 00000000..2a982a56 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/BinaryFormula.java @@ -0,0 +1,97 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.ast; + +import kodkod.ast.operator.FormulaOperator; +import kodkod.ast.visitor.ReturnVisitor; +import kodkod.ast.visitor.VoidVisitor; + +/** + * A {@link kodkod.ast.Formula formula} with two children. + * + * @specfield left: Formula + * @specfield right: Formula + * @specfield op: FormulaOperator + * @invariant children = 0->left + 1->right + * @author Emina Torlak + */ +public final class BinaryFormula extends Formula { + + private final Formula left; + private final Formula right; + private final FormulaOperator op; + + /** + * Constructs a new binary formula: left op right + * + * @ensures this.left' = left && this.right' = right && this.op' = op + * @throws NullPointerException - left = null || right = null || op = null + */ + BinaryFormula(Formula left, FormulaOperator op, Formula right) { + this.left = left; + this.right = right; + this.op = op; + } + + /** + * Returns the left child of this. + * @return this.left + */ + public Formula left() {return left;} + + /** + * Returns the right child of this. + * @return this.right + */ + public Formula right() {return right;} + + /** + * Returns the operator of this. + * @return this.op + */ + public FormulaOperator op() {return op;} + + /** + * {@inheritDoc} + * @see kodkod.ast.Formula#accept(kodkod.ast.visitor.ReturnVisitor) + */ + public F accept(ReturnVisitor visitor) { + return visitor.visit(this); + } + + /** + * {@inheritDoc} + * @see kodkod.ast.Node#accept(kodkod.ast.visitor.VoidVisitor) + */ + public void accept(VoidVisitor visitor) { + visitor.visit(this); + } + + /** + * {@inheritDoc} + * @see kodkod.ast.Node#toString() + */ + public String toString() { + return "(" + left + " " + op + " " + right + ")"; + } + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/BinaryIntExpression.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/BinaryIntExpression.java new file mode 100644 index 00000000..c1faf40d --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/BinaryIntExpression.java @@ -0,0 +1,98 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.ast; + + +import kodkod.ast.operator.IntOperator; +import kodkod.ast.visitor.ReturnVisitor; +import kodkod.ast.visitor.VoidVisitor; + +/** + * A binary integer expression such as x + y. + * @specfield left: IntExpression + * @specfield right: IntExpression + * @specfield op: IntOperator + * @specfield op.binary() + * @invariant children = 0->left + 1->right + * @author Emina Torlak + */ +public final class BinaryIntExpression extends IntExpression { + private final IntOperator op; + private final IntExpression left, right; + + /** + * Constructs a new binary int formula: left op right + * + * @ensures this.left' = left && this.right' = right && this.op' = op + * @throws NullPointerException - left = null || right = null || op = null + */ + public BinaryIntExpression(final IntExpression left, final IntOperator op, final IntExpression right) { + if (!op.binary()) throw new IllegalArgumentException("Not a binary operator: " + op); + this.left = left; + this.right = right; + this.op = op; + } + + /** + * Returns the left child of this. + * @return this.left + */ + public IntExpression left() {return left;} + + /** + * Returns the right child of this. + * @return this.right + */ + public IntExpression right() {return right;} + + /** + * Returns the operator of this. + * @return this.op + */ + public IntOperator op() {return op;} + + /** + * {@inheritDoc} + * @see kodkod.ast.IntExpression#accept(kodkod.ast.visitor.ReturnVisitor) + */ + @Override + public I accept(ReturnVisitor visitor) { + return visitor.visit(this); + } + + /** + * {@inheritDoc} + * @see kodkod.ast.IntExpression#accept(kodkod.ast.visitor.VoidVisitor) + */ + @Override + public void accept(VoidVisitor visitor) { + visitor.visit(this); + } + + /** + * {@inheritDoc} + * @see kodkod.ast.Node#toString() + */ + public String toString() { + return "(" + left + " " + op + " " + right + ")"; + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/ComparisonFormula.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/ComparisonFormula.java new file mode 100644 index 00000000..50a94db2 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/ComparisonFormula.java @@ -0,0 +1,104 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.ast; + +import kodkod.ast.operator.ExprCompOperator; +import kodkod.ast.visitor.ReturnVisitor; +import kodkod.ast.visitor.VoidVisitor; + + + +/** + * A formula that compares two expressions, e.g. x = y + * + * @specfield left: Expression + * @specfield right: Expression + * @specfield op: ExprCompOperator + * @invariant children = 0->left + 1->right + * @author Emina Torlak + */ +public final class ComparisonFormula extends Formula{ + private final Expression left; + private final Expression right; + private final ExprCompOperator op; + + /** + * Constructs a new comparison formula: left op right + * + * @ensures this.left' = left && this.right' = right && this.op' = op + * * @throws NullPointerException - left = null || right = null || op = null + * @throws IllegalArgumentException - left.arity != right.arity + */ + ComparisonFormula(Expression left, ExprCompOperator op, Expression right) { + if (left.arity()!=right.arity()) { + throw new IllegalArgumentException( + "Arity mismatch: " + left + "::" + left.arity() + + " and " + right + "::" + right.arity()); + } + this.left = left; + this.right = right; + this.op = op; + } + + /** + * Returns the left child of this. + * @return this.left + */ + public Expression left() {return left;} + + /** + * Returns the right child of this. + * @return this.right + */ + public Expression right() {return right;} + + /** + * Returns the operator of this. + * @return this.op + */ + public ExprCompOperator op() {return op;} + + /** + * {@inheritDoc} + * @see kodkod.ast.Formula#accept(kodkod.ast.visitor.ReturnVisitor) + */ + public F accept(ReturnVisitor visitor) { + return visitor.visit(this); + } + + /** + * {@inheritDoc} + * @see kodkod.ast.Node#accept(kodkod.ast.visitor.VoidVisitor) + */ + public void accept(VoidVisitor visitor) { + visitor.visit(this); + } + + /** + * {@inheritDoc} + * @see kodkod.ast.Node#toString() + */ + public String toString() { + return "(" + left + " " + op + " " + right + ")"; + } + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/Comprehension.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/Comprehension.java new file mode 100644 index 00000000..fffb69fb --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/Comprehension.java @@ -0,0 +1,107 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.ast; + + +import kodkod.ast.operator.Multiplicity; +import kodkod.ast.visitor.ReturnVisitor; +import kodkod.ast.visitor.VoidVisitor; + + + +/** + * A comprehension expression, e.g. { a: A, b: B | a.r = b } + * + * @specfield decls: Declarations + * @specfield formula: Formula + * @invariant arity = sum(decls.declarations().arity) + * @invariant children = 0->decls + 1->formula + * @author Emina Torlak + */ +public final class Comprehension extends Expression { + + private final Decls decls; + private final Formula formula; + + + /** + * Constructs a comprehension expression with the specified decls + * and formula + * + * @ensures this.decls' = decls && this.formula' = formula + * @throws NullPointerException - decls = null || formula = null + */ + Comprehension(Decls declarations, Formula formula) { + if (formula == null) throw new NullPointerException("null formula"); + for(Decl decl : declarations) { + if (decl.variable().arity()>1 || decl.multiplicity()!=Multiplicity.ONE) + throw new IllegalArgumentException("Cannot have a higher order declaration in a comprehension: "+decl); + } + this.decls = declarations; + this.formula = formula; + } + + /** + * @return this.formula + */ + public Formula formula() {return formula; } + + /** + * @return this.decls + */ + public Decls decls() {return decls;} + + /** + * Returns the arity of this comprehension expression, which is the sum + * of the arities of declared variables + * + * @return #this.decls + */ + public int arity() { + return decls.size(); + } + + /** + * {@inheritDoc} + * @see kodkod.ast.Expression#accept(kodkod.ast.visitor.ReturnVisitor) + */ + public E accept(ReturnVisitor visitor) { + return visitor.visit(this); + } + + /** + * {@inheritDoc} + * @see kodkod.ast.Node#accept(kodkod.ast.visitor.VoidVisitor) + */ + public void accept(VoidVisitor visitor) { + visitor.visit(this); + } + + /** + * {@inheritDoc} + * @see kodkod.ast.Node#toString() + */ + public String toString() { + return "{ " + decls().toString() + " | " + formula().toString() + " }"; + } + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/ConstantExpression.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/ConstantExpression.java new file mode 100644 index 00000000..47835731 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/ConstantExpression.java @@ -0,0 +1,61 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.ast; + + +import kodkod.ast.visitor.ReturnVisitor; +import kodkod.ast.visitor.VoidVisitor; + +/** + * A constant valued expression. + * + * @invariant no children + * @author Emina Torlak + */ +public final class ConstantExpression extends LeafExpression { + + /** + * Constructs a constant expression with the given arity. + */ + ConstantExpression(String name, int arity) { + super(name, arity); + } + + /** + * {@inheritDoc} + * @see kodkod.ast.Expression#accept(kodkod.ast.visitor.ReturnVisitor) + */ + public E accept(ReturnVisitor visitor) { + return visitor.visit(this); + } + + /** + * {@inheritDoc} + * @see kodkod.ast.Node#accept(kodkod.ast.visitor.VoidVisitor) + */ + public void accept(VoidVisitor visitor) { + visitor.visit(this); + } + + + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/ConstantFormula.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/ConstantFormula.java new file mode 100644 index 00000000..317d44be --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/ConstantFormula.java @@ -0,0 +1,74 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.ast; + + +import kodkod.ast.visitor.ReturnVisitor; +import kodkod.ast.visitor.VoidVisitor; + +/** + * A constant {@link kodkod.ast.Formula formula}, true or false. + * + * @invariant no children + * @author Emina Torlak + */ +public abstract class ConstantFormula extends Formula { + private final boolean value; + /** + * Constructs a constant formula with the given value. + */ + ConstantFormula(boolean value) { + this.value = value; + } + + /** + * Returns the boolean value that corresponds to this + * constant formula. + * @return this=TRUE => true, false + */ + public final boolean booleanValue() { return value; } + + /** + * {@inheritDoc} + * @see kodkod.ast.Formula#accept(kodkod.ast.visitor.ReturnVisitor) + */ + public F accept(ReturnVisitor visitor) { + return visitor.visit(this); + } + + /** + * {@inheritDoc} + * @see kodkod.ast.Node#accept(kodkod.ast.visitor.VoidVisitor) + */ + public void accept(VoidVisitor visitor) { + visitor.visit(this); + } + + /** + * {@inheritDoc} + * @see kodkod.ast.Node#toString() + */ + public String toString() { + return String.valueOf(booleanValue()); + } +} + diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/Decl.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/Decl.java new file mode 100644 index 00000000..76f2e886 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/Decl.java @@ -0,0 +1,109 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.ast; + + +import kodkod.ast.operator.Multiplicity; +import kodkod.ast.visitor.ReturnVisitor; +import kodkod.ast.visitor.VoidVisitor; + + + + +/** + * A variable declaration, such as 'x : lone X'. Declarations + * are used with quantified formulas and comprehension expressions. + * + * @specfield variable: Variable + * @specfield expression: Expression + * @specfield multiplicity: LONE + ONE + SOME + SET + * @invariant variable.arity = expression.arity + * @invariant children = 0->variable + 1->expression + * @author Emina Torlak + */ +public final class Decl extends Decls { + + private final Variable variable; + private final Multiplicity mult; + private final Expression expression; + + /** + * Constructs a new declaration from the specified variable and + * expression, with the specified order. + * + * @ensures this.variable' = variable && this.expression' = expression && this.multiplicity' = mult + * @throws NullPointerException - variable = null || expression = null || mult = null + * @throws IllegalArgumentException - variable.arity != expression.arity + */ + Decl(Variable variable, Multiplicity mult, Expression expression) { + if (mult==Multiplicity.NO) + throw new IllegalArgumentException("NO is not a valid multiplicity in a declaration."); + if (variable.arity() != expression.arity()) + throw new IllegalArgumentException("Unmatched arities in a declaration: " + variable + " and " + expression); + if (mult != Multiplicity.SET && expression.arity()>1) + throw new IllegalArgumentException("Cannot use multiplicity " + mult + " with an expression of arity > 1."); + this.variable = variable; + this.mult = mult; + this.expression = expression; + } + + /** + * Returns the variable in this declaration. + * @return this.variable + */ + public Variable variable() { return variable; } + + /** + * Returns the multiplicity in this declaration. + * @return this.multiplicity + */ + public Multiplicity multiplicity() { return mult; } + + /** + * Returns the expression in this declaration. + * @return this.exresssion + */ + public Expression expression() { return expression; } + + /** + * {@inheritDoc} + * @see kodkod.ast.Node#accept(kodkod.ast.visitor.ReturnVisitor) + */ + public D accept(ReturnVisitor visitor) { + return visitor.visit(this); + } + + /** + * {@inheritDoc} + * @see kodkod.ast.Node#accept(kodkod.ast.visitor.VoidVisitor) + */ + public void accept(VoidVisitor visitor) { + visitor.visit(this); + } + + /** + * {@inheritDoc} + * @see kodkod.ast.Node#toString() + */ + public String toString() { return variable + ": " + mult + " " + expression; } + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/Decls.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/Decls.java new file mode 100644 index 00000000..d6a6d2a2 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/Decls.java @@ -0,0 +1,125 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.ast; + +import java.util.Arrays; +import java.util.Iterator; + +import kodkod.ast.visitor.ReturnVisitor; +import kodkod.ast.visitor.VoidVisitor; +import kodkod.util.collections.Containers; + + +/** + * A sequence of decls. + * + * @specfield size: int + * @specfield decls: [0..size) -> one Decl + * @invariant size > 0 + * @invariant children = decls + * @author Emina Torlak + */ +public class Decls extends Node implements Iterable { + private final Decl[] decls; + + /** + * Constructs a Decls object with itself as its sole + * declaration. This constructor can only be called + * from inside the Decl constructor; otherwise it will + * throw a ClassCastException. + * @ensures this.declarations' = 0->this + * @throws ClassCastException - this !in Decl + */ + Decls() { + this.decls = new Decl[]{ (Decl) this }; + } + + /** + * Constructs a new Decls with the specified head and tail. + * @requires head.size > 0 && tail.size > 0 + * @ensures this.size' = head.size + tail.size && + * (all i: [0..head.size) | this.decls[i] = head.decls[i]) && + * (all i: [head.size..this.size') | this.decls[i] = tail.decls[i]) + * @throws NullPointerException - head = null || tail is null + */ + private Decls(Decls head, Decls tail) { + this.decls = new Decl[head.size()+tail.size()]; + System.arraycopy(head.decls, 0, decls, 0, head.size()); + System.arraycopy(tail.decls, 0, decls, head.size(), tail.size()); + } + + /** + * Returns the number of decls in this Decls object. + * @return this.size + */ + public int size() { return decls.length; } + + /** + * Returns the ith declaration in this Decls sequence. + * @requires 0 <= i < this.size + * @return this.decls[i] + */ + public Decl get(int i) { return decls[i]; } + + /** + * Returns an unmodifiable iterator over the decls in this Decls object. + * @return an unmodifiable iterator over the decls in this Decls object. + */ + public Iterator iterator() { return Containers.iterate(decls); } + + /** + * Returns a sequence of this.size + other.size decls that has + * these decls as the prefix and the given decls as the suffix. + * @return {ds: Decls | ds.size = this.size + other.size && + * ds.decls = this.decls + + * {i: [this.size..this.size+other.size), d: Decl | d = other.decls[i-this.size] } + * @throws NullPointerException - decl = null + */ + public final Decls and(Decls other) { + return new Decls(this, other); + } + + /** + * {@inheritDoc} + * @see kodkod.ast.Node#accept(kodkod.ast.visitor.ReturnVisitor) + */ + public D accept(ReturnVisitor visitor) { + return visitor.visit(this); + } + + /** + * {@inheritDoc} + * @see kodkod.ast.Node#accept(kodkod.ast.visitor.VoidVisitor) + */ + public void accept(VoidVisitor visitor) { + visitor.visit(this); + } + + /** + * {@inheritDoc} + * @see kodkod.ast.Node#toString() + */ + public String toString() { + return Arrays.toString(decls); + } + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/ExprToIntCast.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/ExprToIntCast.java new file mode 100644 index 00000000..28e8a7ff --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/ExprToIntCast.java @@ -0,0 +1,94 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.ast; + + +import kodkod.ast.operator.ExprCastOperator; +import kodkod.ast.visitor.ReturnVisitor; +import kodkod.ast.visitor.VoidVisitor; + +/** + * An {@link kodkod.ast.IntExpression } representing the + * cardinality of an {@link kodkod.ast.Expression} or the + * sum of all the integer atoms contained in the expression. + * @specfield expression: Expression + * @specfield op: ExprCastOperator + * @invariant children = 0->expression + * @author Emina Torlak + */ +public final class ExprToIntCast extends IntExpression { + private final Expression expression; + private final ExprCastOperator op; + /** + * Constructs a new cardinality expression. + * + * @ensures this.expression' = expression && this.op' = op + * @throws NullPointerException - expression = null || op = null + * @throws IllegalArgumentException - op = SUM && child.arity != 1 + */ + ExprToIntCast(Expression child, ExprCastOperator op) { + if (child.arity()>1 && op==ExprCastOperator.SUM) + throw new IllegalArgumentException("cannot apply " + op + " to " + child); + this.expression = child; + this.op = op; + } + + /** + * Returns this.expression. + * @return this.expression + */ + public Expression expression() {return expression;} + + /** + * Returns this.op. + * @return this.op + */ + public ExprCastOperator op() { return op; } + + + /** + * {@inheritDoc} + * @see kodkod.ast.IntExpression#accept(kodkod.ast.visitor.ReturnVisitor) + */ + @Override + public I accept(ReturnVisitor visitor) { + return visitor.visit(this); + } + + /** + * {@inheritDoc} + * @see kodkod.ast.IntExpression#accept(kodkod.ast.visitor.VoidVisitor) + */ + @Override + public void accept(VoidVisitor visitor) { + visitor.visit(this); + } + + /** + * {@inheritDoc} + * @see kodkod.ast.Node#toString() + */ + public String toString() { + return op + "("+expression.toString()+")"; + } + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/Expression.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/Expression.java new file mode 100644 index 00000000..d2728b8c --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/Expression.java @@ -0,0 +1,413 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.ast; + +import static kodkod.ast.operator.ExprCastOperator.CARDINALITY; +import static kodkod.ast.operator.ExprCastOperator.SUM; +import static kodkod.ast.operator.ExprCompOperator.EQUALS; +import static kodkod.ast.operator.ExprCompOperator.SUBSET; +import static kodkod.ast.operator.ExprOperator.CLOSURE; +import static kodkod.ast.operator.ExprOperator.DIFFERENCE; +import static kodkod.ast.operator.ExprOperator.INTERSECTION; +import static kodkod.ast.operator.ExprOperator.JOIN; +import static kodkod.ast.operator.ExprOperator.OVERRIDE; +import static kodkod.ast.operator.ExprOperator.PRODUCT; +import static kodkod.ast.operator.ExprOperator.REFLEXIVE_CLOSURE; +import static kodkod.ast.operator.ExprOperator.TRANSPOSE; +import static kodkod.ast.operator.ExprOperator.UNION; +import static kodkod.ast.operator.Multiplicity.LONE; +import static kodkod.ast.operator.Multiplicity.NO; +import static kodkod.ast.operator.Multiplicity.ONE; +import static kodkod.ast.operator.Multiplicity.SOME; + + +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; + +import kodkod.ast.operator.ExprCastOperator; +import kodkod.ast.operator.ExprCompOperator; +import kodkod.ast.operator.ExprOperator; +import kodkod.ast.operator.Multiplicity; +import kodkod.ast.visitor.ReturnVisitor; +import kodkod.util.collections.Containers; + +/** + * A relational expression. Unless otherwise noted, + * all methods in this class throw a NullPointerException when given + * null arguments. + * + * @specfield arity: int + * @invariant arity > 0 + * + * @author Emina Torlak + */ +public abstract class Expression extends Node { + + /** The universal relation: contains all atoms in a {@link kodkod.instance.Universe universe of discourse}. */ + public static final Expression UNIV = new ConstantExpression("univ", 1); + + /** The identity relation: maps all atoms in a {@link kodkod.instance.Universe universe of discourse} to themselves. */ + public static final Expression IDEN = new ConstantExpression("iden", 2); + + /** The empty relation: contains no atoms. */ + public static final Expression NONE = new ConstantExpression("none", 1); + + /** The integer relation: contains all atoms {@link kodkod.instance.Bounds bound} to integers */ + public static final Expression INTS = new ConstantExpression("ints", 1); + + /** + * Constructs a leaf expression + * @ensures no this.children' + */ + Expression() { } + + /** + * Returns the join of this and the specified expression. The effect + * of this method is the same as calling this.compose(JOIN, expr). + * @return this.compose(JOIN, expr) + */ + public final Expression join(Expression expr) { + return compose(JOIN,expr); + } + + /** + * Returns the product of this and the specified expression. The effect + * of this method is the same as calling this.compose(PRODUCT, expr). + * @return this.compose(PRODUCT, expr) + */ + public final Expression product(Expression expr) { + return compose(PRODUCT,expr); + } + + /** + * Returns the union of this and the specified expression. The effect + * of this method is the same as calling this.compose(UNION, expr). + * @return this.compose(UNION, expr) + */ + public final Expression union(Expression expr) { + return compose(UNION,expr); + } + + /** + * Returns the difference of this and the specified expression. The effect + * of this method is the same as calling this.compose(DIFFERENCE, expr). + * @return this.compose(DIFFERENCE, expr) + */ + public final Expression difference(Expression expr) { + return compose(DIFFERENCE,expr); + } + + /** + * Returns the intersection of this and the specified expression. The effect + * of this method is the same as calling this.compose(INTERSECTION, expr). + * @return this.compose(INTERSECTION, expr) + */ + public final Expression intersection(Expression expr) { + return compose(INTERSECTION,expr); + } + + /** + * Returns the relational override of this with the specified expression. The effect + * of this method is the same as calling this.compose(OVERRIDE, expr). + * @return this.compose(OVERRIDE, expr) + */ + public final Expression override(Expression expr) { + return compose(OVERRIDE,expr); + } + + /** + * Returns the composition of this and the specified expression, using the + * given binary operator. + * @requires op in ExprOperator.BINARY + * @return {e: Expression | e.left = this and e.right = expr and e.op = this } + */ + public final Expression compose(ExprOperator op, Expression expr) { + return new BinaryExpression(this, op, expr); + } + + /** + * Returns the union of the given expressions. The effect of this method is the + * same as calling compose(UNION, exprs). + * @return compose(UNION, exprs) + */ + public static Expression union(Expression...exprs) { + return compose(UNION, exprs); + } + + /** + * Returns the union of the given expressions. The effect of this method is the + * same as calling compose(UNION, exprs). + * @return compose(UNION, exprs) + */ + public static Expression union(Collection exprs) { + return compose(UNION, exprs); + } + + /** + * Returns the intersection of the given expressions. The effect of this method is the + * same as calling compose(INTERSECTION, exprs). + * @return compose(INTERSECTION, exprs) + */ + public static Expression intersection(Expression...exprs) { + return compose(INTERSECTION, exprs); + } + + /** + * Returns the intersection of the given expressions. The effect of this method is the + * same as calling compose(INTERSECTION, exprs). + * @return compose(INTERSECTION, exprs) + */ + public static Expression intersection(Collection exprs) { + return compose(INTERSECTION, exprs); + } + + /** + * Returns the product of the given expressions. The effect of this method is the + * same as calling compose(PRODUCT, exprs). + * @return compose(PRODUCT, exprs) + */ + public static Expression product(Expression...exprs) { + return compose(PRODUCT, exprs); + } + + /** + * Returns the product of the given expressions. The effect of this method is the + * same as calling compose(PRODUCT, exprs). + * @return compose(PRODUCT, exprs) + */ + public static Expression product(Collection exprs) { + return compose(PRODUCT, exprs); + } + + /** + * Returns the override of the given expressions. The effect of this method is the + * same as calling compose(OVERRIDE, exprs). + * @return compose(OVERRIDE, exprs) + */ + public static Expression override(Expression...exprs) { + return compose(OVERRIDE, exprs); + } + + /** + * Returns the override of the given expressions. The effect of this method is the + * same as calling compose(OVERRIDE, exprs). + * @return compose(OVERRIDE, exprs) + */ + public static Expression override(Collection exprs) { + return compose(OVERRIDE, exprs); + } + + /** + * Returns the composition of the given expressions using the given operator. + * @requires exprs.length = 2 => op.binary(), exprs.length > 2 => op.nary() + * @return exprs.length=1 => exprs[0] else {e: Expression | e.children = exprs and e.op = this } + */ + public static Expression compose(ExprOperator op, Expression...exprs) { + switch(exprs.length) { + case 0 : throw new IllegalArgumentException("Expected at least one argument: " + Arrays.toString(exprs)); + case 1 : return exprs[0]; + case 2 : return new BinaryExpression(exprs[0], op, exprs[1]); + default : return new NaryExpression(op, Containers.copy(exprs, new Expression[exprs.length])); + } + } + + /** + * Returns the composition of the given expressions using the given operator. + * @requires exprs.size() = 2 => op.binary(), exprs.size() > 2 => op.nary() + * @return exprs.size()=1 => exprs.iterator().next() else {e: Expression | e.children = exprs.toArray() and e.op = this } + */ + public static Expression compose(ExprOperator op, Collection exprs) { + switch(exprs.size()) { + case 0 : throw new IllegalArgumentException("Expected at least one argument: " + exprs); + case 1 : return exprs.iterator().next(); + case 2 : + final Iterator itr = exprs.iterator(); + return new BinaryExpression(itr.next(), op, itr.next()); + default : + return new NaryExpression(op, exprs.toArray(new Expression[exprs.size()])); + } + } + + + + /** + * Returns the transpose of this. The effect of this method is the same + * as calling this.apply(TRANSPOSE). + * @return this.apply(TRANSPOSE) + */ + public final Expression transpose() { + return apply(TRANSPOSE); + } + + /** + * Returns the transitive closure of this. The effect of this method is the same + * as calling this.apply(CLOSURE). + * @return this.apply(CLOSURE) + */ + public final Expression closure() { + return apply(CLOSURE); + } + + /** + * Returns the reflexive transitive closure of this. The effect of this + * method is the same + * as calling this.apply(REFLEXIVE_CLOSURE). + * @return this.apply(REFLEXIVE_CLOSURE) + */ + public final Expression reflexiveClosure() { + return apply(REFLEXIVE_CLOSURE); + } + + /** + * Returns the expression that results from applying the given unary operator + * to this. + * @requires op.unary() + * @return {e: Expression | e.expression = this && e.op = this } + * @throws IllegalArgumentException - this.arity != 2 + */ + public final Expression apply(ExprOperator op) { + return new UnaryExpression(op, this); + } + + /** + * Returns the projection of this expression onto the specified columns. + * @return {e: Expression | e = project(this, columns) } + * @throws IllegalArgumentException - columns.length < 1 + */ + public final Expression project(IntExpression... columns) { + return new ProjectExpression(this, columns); + } + + /** + * Returns the cardinality of this expression. The effect of this method is the + * same as calling this.apply(CARDINALITY). + * @return this.apply(CARDINALITY) + */ + public final IntExpression count() { + return apply(CARDINALITY); + } + + /** + * Returns the sum of the integer atoms in this expression. The effect of this method is the + * same as calling this.apply(SUM). + * @return this.apply(SUM) + */ + public final IntExpression sum() { + return apply(SUM); + } + + /** + * Returns the cast of this expression to an integer expression, + * that represents either the cardinality of this expression (if op is CARDINALITY) + * or the sum of the integer atoms it contains (if op is SUM). + * @return {e: IntExpression | e.op = op && e.expression = this} + */ + public final IntExpression apply(ExprCastOperator op) { + return new ExprToIntCast(this, op); + } + + /** + * Returns the formula 'this = expr'. The effect of this method is the same + * as calling this.compare(EQUALS, expr). + * @return this.compare(EQUALS, expr) + */ + public final Formula eq(Expression expr) { + return compare(EQUALS, expr); + } + + /** + * Returns the formula 'this in expr'. The effect of this method is the same + * as calling this.compare(SUBSET, expr). + * @return this.compare(SUBSET, expr) + */ + public final Formula in(Expression expr) { + return compare(SUBSET, expr); + } + + /** + * Returns the formula that represents the comparison of this and the + * given expression using the given comparison operator. + * @return {f: Formula | f.left = this && f.right = expr && f.op = op} + */ + public final Formula compare(ExprCompOperator op, Expression expr) { + return new ComparisonFormula(this, op, expr); + } + + /** + * Returns the formula 'some this'. The effect of this method is the same as calling + * this.apply(SOME). + * @return this.apply(SOME) + */ + public final Formula some() { + return apply(SOME); + } + + /** + * Returns the formula 'no this'. The effect of this method is the same as calling + * this.apply(NO). + * @return this.apply(NO) + */ + public final Formula no() { + return apply(NO); + } + + /** + * Returns the formula 'one this'. The effect of this method is the same as calling + * this.apply(ONE). + * @return this.apply(ONE) + */ + public final Formula one() { + return apply(ONE); + } + + /** + * Returns the formula 'lone this'. The effect of this method is the same as calling + * this.apply(LONE). + * @return this.apply(LONE) + */ + public final Formula lone() { + return apply(LONE); + } + + /** + * Returns the formula that results from applying the specified multiplicity to + * this expression. The SET multiplicity is not allowed. + * @return {f: Formula | f.multiplicity = mult && f.expression = this} + * @throws IllegalArgumentException - mult = SET + */ + public final Formula apply(Multiplicity mult) { + return new MultiplicityFormula(mult, this); + } + + /** + * Returns the arity of this expression. + * @return this.arity + */ + public abstract int arity(); + + /** + * Accepts the given visitor and returns the result. + * @see kodkod.ast.Node#accept(kodkod.ast.visitor.ReturnVisitor) + */ + public abstract E accept(ReturnVisitor visitor); + } diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/Formula.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/Formula.java new file mode 100644 index 00000000..ad6603e2 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/Formula.java @@ -0,0 +1,265 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.ast; + +import static kodkod.ast.operator.FormulaOperator.AND; +import static kodkod.ast.operator.FormulaOperator.IFF; +import static kodkod.ast.operator.FormulaOperator.IMPLIES; +import static kodkod.ast.operator.FormulaOperator.OR; +import static kodkod.ast.operator.Quantifier.ALL; +import static kodkod.ast.operator.Quantifier.SOME; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; + +import kodkod.ast.operator.FormulaOperator; +import kodkod.ast.operator.Quantifier; +import kodkod.ast.visitor.ReturnVisitor; +import kodkod.util.collections.Containers; + +/** + * A first-order formula. Unless otherwise noted, + * all methods in this class throw a NullPointerException when given + * null arguments. + * @author Emina Torlak + */ +public abstract class Formula extends Node { + + /** Constant formula true */ + public static final Formula TRUE = new ConstantFormula(true) {}; + + /** Constant formula false */ + public static final Formula FALSE = new ConstantFormula(false) {}; + + Formula() {} + + /** + * Returns the constant formula with the given value. + * @return value ? TRUE : FALSE + */ + public static Formula constant(boolean value) { return value ? TRUE : FALSE; } + + /** + * Returns the conjunction of this and the specified formula. The effect + * of this method is the same as calling this.compose(AND, formula). + * @return this.compose(AND, formula) + */ + public final Formula and(Formula formula) { + return compose(AND,formula); + } + + /** + * Returns the conjunction of this and the specified formula. The effect + * of this method is the same as calling this.compose(OR, formula). + * @return this.compose(OR, formula) + */ + public final Formula or(Formula formula) { + return compose(OR,formula); + } + + /** + * Returns a formula that equates this and the specified formula. The effect + * of this method is the same as calling this.compose(IFF, formula). + * @return this.compose(IFF, formula) + */ + public final Formula iff(Formula formula) { + return compose(IFF,formula); + } + + /** + * Returns the implication of the specified formula by this. The effect + * of this method is the same as calling this.compose(IMPLIES, formula). + * @return this.compose(IMPLIES, formula) + */ + public final Formula implies(Formula formula) { + return compose(IMPLIES,formula); + } + + + /** + * Returns the composition of this and the specified formula using the + * given binary operator. + * @return {f: Formula | f.left = this and f.right = formula and f.op = op } + */ + public final Formula compose(FormulaOperator op, Formula formula) { + return new BinaryFormula(this, op, formula); + } + + /** + * Returns the conjunction of the given formulas. The effect of this method is the + * same as calling compose(AND, formulas). + * @return compose(AND, formulas) + */ + public static Formula and(Formula...formulas) { + return compose(AND, formulas); + } + + /** + * Returns the conjunction of the given formulas. The effect of this method is the + * same as calling compose(AND, formulas). + * @return compose(AND, formulas) + */ + public static Formula and(Collection formulas) { + return compose(AND, formulas); + } + + /** + * Returns the disjunction of the given formulas. The effect of this method is the + * same as calling compose(OR, formulas). + * @return compose(OR, formulas) + */ + public static Formula or(Formula...formulas) { + return compose(OR, formulas); + } + + /** + * Returns the disjunction of the given formulas. The effect of this method is the + * same as calling compose(OR, formulas). + * @return compose(OR, formulas) + */ + public static Formula or(Collection formulas) { + return compose(OR, formulas); + } + + /** + * Returns the composition of the given formulas using the given operator. + * @requires formulas.length != 2 => op.nary() + * @return + *
 
+     *  formulas.length = 0 => constant(op=AND) else
+     * 	formulas.length=1 => formulas[0] else 
+     *  {e: Formula | e.children = formulas and e.op = this }
+     * 
+ */ + public static Formula compose(FormulaOperator op, Formula...formulas) { + switch(formulas.length) { + case 0 : + switch(op) { + case AND : return TRUE; + case OR : return FALSE; + default : throw new IllegalArgumentException("Expected at least one argument: " + Arrays.toString(formulas)); + } + case 1 : return formulas[0]; + case 2 : return new BinaryFormula(formulas[0], op, formulas[1]); + default : return new NaryFormula(op, Containers.copy(formulas, new Formula[formulas.length])); + } + } + + /** + * Returns the composition of the given formulas using the given operator. + * @requires formulas.size() != 2 => op.nary() + * @return + *
 
+     *  formulas.size() = 0 => constant(op=AND) else
+     *  formulas.size() = 1 => formulas.iterator().next() else 
+     *  {e: Formula | e.children = formulas.toArray() and e.op = this }
+     * 
+ */ + public static Formula compose(FormulaOperator op, Collection formulas) { + switch(formulas.size()) { + case 0 : + switch(op) { + case AND : return TRUE; + case OR : return FALSE; + default : throw new IllegalArgumentException("Expected at least one argument: " + formulas); + } + case 1 : return formulas.iterator().next(); + case 2 : + final Iterator itr = formulas.iterator(); + return new BinaryFormula(itr.next(), op, itr.next()); + default : + return new NaryFormula(op, formulas.toArray(new Formula[formulas.size()])); + } + } + + /** + * Returns a formula that represents a universal quantification of this + * formula over the given declarations. The effect of this method is the same + * as calling this.quantify(ALL, decls). + * @return this.quantify(ALL, decls) + */ + public final Formula forAll(Decls decls) { + return quantify(ALL, decls); + } + + /** + * Returns a formula that represents an existential quantification of this + * formula over the given declarations. The effect of this method is the same + * as calling this.quantify(SOME, decls). + * @return this.quantify(SOME, decls) + */ + public final Formula forSome(Decls decls) { + return quantify(SOME, decls); + } + + /** + * Returns a quantification of this formula using the given quantifier over + * the specified declarations. + * @return {f: Formula | f.decls = decls and f.formula = this and f.quantifier = quantifer } + */ + public final Formula quantify(Quantifier quantifier, Decls decls) { + return new QuantifiedFormula(quantifier, decls, this); + } + + /** + * Returns the comprehension expression constructed from this formula and + * the given declarations. + * @requires all d: decls.decls[int] | decl.variable.arity = 1 and decl.multiplicity = ONE + * @return {e: Expression | e.decls = decls and e.formula = this } + */ + public final Expression comprehension(Decls decls) { + return new Comprehension(decls,this); + } + + /** + * Returns the if expression constructed from this formula and the + * specified then and else expressions. + * @return {e: Expression | e.condition = this and e.thenExpr = thenExpr and e.elseExpr = elseExpr} + */ + public final Expression thenElse(Expression thenExpr, Expression elseExpr) { + return new IfExpression(this, thenExpr, elseExpr); + } + + /** + * Returns the if expression constructed from this formula and the + * specified then and else integer expressions. + * @return {e: IntExpression | e.condition = this and e.thenExpr = thenExpr and e.elseExpr = elseExpr} + */ + public final IntExpression thenElse(IntExpression thenExpr, IntExpression elseExpr) { + return new IfIntExpression(this, thenExpr, elseExpr); + } + + /** + * Returns the negation of this formula. + * @return {f : NotFormula | f.formula = this } + */ + public final Formula not() { + return new NotFormula(this); + } + + /** + * Accepts the given visitor and returns the result. + * @see kodkod.ast.Node#accept(kodkod.ast.visitor.ReturnVisitor) + */ + public abstract F accept(ReturnVisitor visitor); +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/IfExpression.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/IfExpression.java new file mode 100644 index 00000000..72087b71 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/IfExpression.java @@ -0,0 +1,118 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.ast; + + +import kodkod.ast.visitor.ReturnVisitor; +import kodkod.ast.visitor.VoidVisitor; + + +/** + * An expression whose value depends on the truth of a given condition. + * + * @specfield condition: Formula + * @specfield thenExpr: Expression + * @specfield elseExpr: Expression + * @invariant children = 0->condition + 1->thenExpr + 2->elseExpr + * @author Greg Dennis (gdennis@mit.edu) + * @author Emina Torlak + */ +public final class IfExpression extends Expression { + + private final Formula condition; + private final Expression thenExpr, elseExpr; + private final int arity; + + /** + * @ensures this.condition' = condition && this.thenExpr' = thenExpr && + * this.elseExpr' = elseExpr + * @throws IllegalArgumentException - thenExpr.arity != elseExpr.arity + */ + IfExpression(Formula condition, Expression thenExpr, Expression elseExpr) { + if (thenExpr.arity() != elseExpr.arity()) { + throw new IllegalArgumentException("Arity mismatch: " + + thenExpr + "::" + thenExpr.arity() + " and " + + elseExpr + "::" + elseExpr.arity()); + } + this.condition = condition; + this.thenExpr = thenExpr; + this.elseExpr = elseExpr; + this.arity = thenExpr.arity(); + } + + /** + * Returns the if-condition. + * @return this.condition + */ + public Formula condition() { + return condition; + } + + /** + * Returns the then-expression. + * @return this.thenExpr + */ + public Expression thenExpr() { + return thenExpr; + } + + /** + * Returns the else-expression. + * @return this.elseExpr + */ + public Expression elseExpr() { + return elseExpr; + } + + /** + * Returns the arity of this. + * @return this.arity + */ + @Override + public int arity() { + return arity; + } + + /** + * {@inheritDoc} + * @see kodkod.ast.Expression#accept(kodkod.ast.visitor.ReturnVisitor) + */ + public E accept(ReturnVisitor visitor) { + return visitor.visit(this); + } + + /** + * {@inheritDoc} + * @see kodkod.ast.Node#accept(kodkod.ast.visitor.VoidVisitor) + */ + public void accept(VoidVisitor visitor) { + visitor.visit(this); + } + + /** + * {@inheritDoc} + * @see kodkod.ast.Node#toString() + */ public String toString() { + return "(if " + condition + " then " + thenExpr + " else " + elseExpr + ")"; + } + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/IfIntExpression.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/IfIntExpression.java new file mode 100644 index 00000000..9afba634 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/IfIntExpression.java @@ -0,0 +1,101 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.ast; + +import kodkod.ast.visitor.ReturnVisitor; +import kodkod.ast.visitor.VoidVisitor; + +/** + * An int expression whose value depends on the truth of a condition. + * + * @specfield condition: Formula + * @specfield thenExpr: IntExpression + * @specfield elseExpr: IntExpression + * @invariant children = 0->condition + 1->thenExpr + 2->elseExpr + * @author Emina Torlak + */ +public final class IfIntExpression extends IntExpression { + private final Formula condition; + private final IntExpression thenExpr, elseExpr; + + /** + * @ensures this.condition' = condition && this.thenExpr' = thenExpr && + * this.elseExpr' = elseExpr + */ + IfIntExpression(Formula condition, IntExpression thenExpr, + IntExpression elseExpr) { + this.condition = condition; + this.thenExpr = thenExpr; + this.elseExpr = elseExpr; + } + + /** + * Returns the if-condition. + * @return this.condition + */ + public Formula condition() { + return condition; + } + + /** + * Returns the then-expression. + * @return this.thenExpr + */ + public IntExpression thenExpr() { + return thenExpr; + } + + /** + * Returns the else-expression. + * @return this.elseExpr + */ + public IntExpression elseExpr() { + return elseExpr; + } + + /** + * {@inheritDoc} + * @see kodkod.ast.IntExpression#accept(kodkod.ast.visitor.ReturnVisitor) + */ + @Override + public I accept(ReturnVisitor visitor) { + return visitor.visit(this); + } + + /** + * {@inheritDoc} + * @see kodkod.ast.IntExpression#accept(kodkod.ast.visitor.VoidVisitor) + */ + @Override + public void accept(VoidVisitor visitor) { + visitor.visit(this); + } + + /** + * {@inheritDoc} + * @see kodkod.ast.Node#toString() + */ + public String toString() { + return "(if " + condition + " then " + thenExpr + " else " + elseExpr + ")"; + } + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/IntComparisonFormula.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/IntComparisonFormula.java new file mode 100644 index 00000000..1154c3f9 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/IntComparisonFormula.java @@ -0,0 +1,97 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.ast; + + + +import kodkod.ast.operator.IntCompOperator; +import kodkod.ast.visitor.ReturnVisitor; +import kodkod.ast.visitor.VoidVisitor; + +/** + * An integer comparison formula, e.g. x = y, x <= y, etc. + * + * @specfield left: IntExpression + * @specfield right: IntExpression + * @specfield op: IntCompOperator + * @invariant children = 0->left + 1->right + * @author Emina Torlak + */ +public final class IntComparisonFormula extends Formula { + private final IntCompOperator op; + private final IntExpression left, right; + + /** + * Constructs a new int comparison formula: left op right + * + * @ensures this.left' = left && this.right' = right && this.op' = op + * @throws NullPointerException - left = null || right = null || op = null + */ + IntComparisonFormula(final IntExpression left, final IntCompOperator op, final IntExpression right) { + this.left = left; + this.right = right; + this.op = op; + } + + /** + * Returns the left child of this. + * @return this.left + */ + public IntExpression left() {return left;} + + /** + * Returns the right child of this. + * @return this.right + */ + public IntExpression right() {return right;} + + /** + * Returns the operator of this. + * @return this.op + */ + public IntCompOperator op() {return op;} + + /** + * {@inheritDoc} + * @see kodkod.ast.Formula#accept(kodkod.ast.visitor.ReturnVisitor) + */ + public F accept(ReturnVisitor visitor) { + return visitor.visit(this); + } + + /** + * {@inheritDoc} + * @see kodkod.ast.Node#accept(kodkod.ast.visitor.VoidVisitor) + */ + public void accept(VoidVisitor visitor) { + visitor.visit(this); + } + + /** + * {@inheritDoc} + * @see kodkod.ast.Node#toString() + */ + public String toString() { + return "(" + left + " " + op + " " + right + ")"; + } + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/IntConstant.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/IntConstant.java new file mode 100644 index 00000000..9bd365c4 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/IntConstant.java @@ -0,0 +1,108 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.ast; + + +import kodkod.ast.visitor.ReturnVisitor; +import kodkod.ast.visitor.VoidVisitor; + +/** + * An integer constant (literal). + * @specfield value: int + * @invariant no children + * @author Emina Torlak + */ +public final class IntConstant extends IntExpression { + private final int value; + + /** + * Constructs an int constant. + * @ensures this.value' = value + */ + private IntConstant(int value) { + this.value = value; + } + + /** + * Returns an IntConstant corresponding to the given value. + * @return {c: IntConstant | c.value = value} + */ + public static IntConstant constant(int value) { + return new IntConstant(value); + } + + /** + * Returns this.value. + * @return this.value + */ + public int value() { + return value; + } + + /** + * Return true if o is an IntConstant with the same value as this. + * @return o in IntConstant && o.value = this.value + */ + public boolean equals(Object o) { + if (o==this) + return true; + else if (o instanceof IntConstant) + return value==((IntConstant) o).value; + else + return false; + } + + /** + * Return this.value + * @return this.value + */ + public int hashCode() { + return value; + } + + /** + * {@inheritDoc} + * @see kodkod.ast.IntExpression#accept(kodkod.ast.visitor.ReturnVisitor) + */ + @Override + public I accept(ReturnVisitor visitor) { + return visitor.visit(this); + } + + /** + * {@inheritDoc} + * @see kodkod.ast.IntExpression#accept(kodkod.ast.visitor.VoidVisitor) + */ + @Override + public void accept(VoidVisitor visitor) { + visitor.visit(this); + } + + /** + * {@inheritDoc} + * @see kodkod.ast.Node#toString() + */ + public String toString() { + return String.valueOf(value); + } + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/IntExpression.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/IntExpression.java new file mode 100644 index 00000000..92e49d8e --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/IntExpression.java @@ -0,0 +1,462 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.ast; + +import static kodkod.ast.operator.IntCastOperator.BITSETCAST; +import static kodkod.ast.operator.IntCastOperator.INTCAST; +import static kodkod.ast.operator.IntCompOperator.EQ; +import static kodkod.ast.operator.IntCompOperator.NEQ; +import static kodkod.ast.operator.IntCompOperator.GT; +import static kodkod.ast.operator.IntCompOperator.GTE; +import static kodkod.ast.operator.IntCompOperator.LT; +import static kodkod.ast.operator.IntCompOperator.LTE; +import static kodkod.ast.operator.IntOperator.ABS; +import static kodkod.ast.operator.IntOperator.AND; +import static kodkod.ast.operator.IntOperator.DIVIDE; +import static kodkod.ast.operator.IntOperator.MINUS; +import static kodkod.ast.operator.IntOperator.MODULO; +import static kodkod.ast.operator.IntOperator.MULTIPLY; +import static kodkod.ast.operator.IntOperator.NEG; +import static kodkod.ast.operator.IntOperator.NOT; +import static kodkod.ast.operator.IntOperator.OR; +import static kodkod.ast.operator.IntOperator.PLUS; +import static kodkod.ast.operator.IntOperator.SGN; +import static kodkod.ast.operator.IntOperator.SHA; +import static kodkod.ast.operator.IntOperator.SHL; +import static kodkod.ast.operator.IntOperator.SHR; +import static kodkod.ast.operator.IntOperator.XOR; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; + +import kodkod.ast.operator.IntCastOperator; +import kodkod.ast.operator.IntCompOperator; +import kodkod.ast.operator.IntOperator; +import kodkod.ast.visitor.ReturnVisitor; +import kodkod.ast.visitor.VoidVisitor; +import kodkod.util.collections.Containers; + +/** + * A Node whose value is an integer + * rather than a relational expression. + * + * @author Emina Torlak + */ +public abstract class IntExpression extends Node { + + /** + * Constructs an IntExpression. + */ + IntExpression() {} + + /** + * Returns a formula stating that the given int expression and + * this have the same value. The effect + * of this method is the same as calling this.compare(EQ, intExpr). + * @return this.compare(EQ, intExpr) + */ + public final Formula eq(IntExpression intExpr) { + return this.compare(EQ, intExpr); + } + + public final Formula neq(IntExpression intExpr) { + return this.compare(NEQ, intExpr); + } + + /** + * Returns a formula stating that the value of this int expression is less than the + * value of the given int expression The effect + * of this method is the same as calling this.compare(LT, intExpr). + * @return this.compare(LT, intExpr) + */ + public final Formula lt(IntExpression intExpr) { + return this.compare(LT, intExpr); + } + + /** + * Returns a formula stating that the value of this int expression is less than + * or equal to the value of the given int expression The effect + * of this method is the same as calling this.compare(LTE, intExpr). + * @return this.compare(LTE, intExpr) + */ + public final Formula lte(IntExpression intExpr) { + return this.compare(LTE, intExpr); + } + + /** + * Returns a formula stating that the value of this int expression is greater than the + * value of the given int expression The effect + * of this method is the same as calling this.compare(GT, intExpr). + * @return this.compare(GT, intExpr) + */ + public final Formula gt(IntExpression intExpr) { + return this.compare(GT, intExpr); + } + + /** + * Returns a formula stating that the value of this int expression is greater than + * or equal to the value of the given int expression The effect + * of this method is the same as calling this.compare(GTE, intExpr). + * @return this.compare(GTE, intExpr) + */ + public final Formula gte(IntExpression intExpr) { + return this.compare(GTE, intExpr); + } + + /** + * Returns a formula comparing this and the given integer expression using the + * specified operator. + * @return {f: Formula | f.left = this and f.right = intExpr and f.op = op } + */ + public Formula compare(IntCompOperator op, IntExpression intExpr) { + if (op==null || intExpr==null) + throw new NullPointerException(); + return new IntComparisonFormula(this, op, intExpr); + } + + /** + * Returns an integer expression that is the sum of all + * values that this integer expression can take given the + * provided declarations. + * @return {e: IntExpression | e.decls = decls and e.intExpr = this } + */ + public final IntExpression sum(Decls decls) { + return new SumExpression(decls, this); + } + + /** + * Returns an IntExpression that represents the sum of this and + * the given int node. The effect of this method is the same as calling + * this.compose(PLUS, intExpr). + * @return this.compose(PLUS, intExpr) + */ + public final IntExpression plus(IntExpression intExpr) { + return compose(PLUS, intExpr); + } + + /** + * Returns an IntExpression that represents the difference between this and + * the given int node. The effect of this method is the same as calling + * this.compose(MINUS, intExpr). + * @return this.compose(MINUS, intExpr) + */ + public final IntExpression minus(IntExpression intExpr) { + return compose(MINUS, intExpr); + } + + /** + * Returns an IntExpression that represents the product of this and + * the given int node. The effect of this method is the same as calling + * this.compose(MULTIPLY, intExpr). + * @return this.compose(MULTIPLY, intExpr) + */ + public final IntExpression multiply(IntExpression intExpr) { + return compose(MULTIPLY, intExpr); + } + + /** + * Returns an IntExpression that represents the quotient of the division + * between this and the given int node. The effect of this method is the same as calling + * this.compose(DIVIDE, intExpr). + * @return this.compose(DIVIDE, intExpr) + */ + public final IntExpression divide(IntExpression intExpr) { + return compose(DIVIDE, intExpr); + } + + /** + * Returns an IntExpression that represents the remainder of the division + * between this and the given int node. The effect of this method is the same as calling + * this.compose(MODULO, intExpr). + * @return this.compose(MODULO, intExpr) + */ + public final IntExpression modulo(IntExpression intExpr) { + return compose(MODULO, intExpr); + } + + /** + * Returns an IntExpression that represents the bitwise AND of this and + * the given int node. The effect of this method is the same as calling + * this.compose(AND, intExpr). + * @return this.compose(AND, intExpr) + */ + public final IntExpression and(IntExpression intExpr) { + return compose(AND, intExpr); + } + + /** + * Returns an IntExpression that represents the bitwise OR of this and + * the given int node. The effect of this method is the same as calling + * this.compose(OR, intExpr). + * @return this.compose(OR, intExpr) + */ + public final IntExpression or(IntExpression intExpr) { + return compose(OR, intExpr); + } + + /** + * Returns an IntExpression that represents the bitwise XOR of this and + * the given int node. The effect of this method is the same as calling + * this.compose(XOR, intExpr). + * @return this.compose(XOR, intExpr) + */ + public final IntExpression xor(IntExpression intExpr) { + return compose(XOR, intExpr); + } + + /** + * Returns an IntExpression that represents the left shift of this by + * the given int node. The effect of this method is the same as calling + * this.compose(SHL, intExpr). + * @return this.compose(SHL, intExpr) + */ + public final IntExpression shl(IntExpression intExpr) { + return compose(SHL, intExpr); + } + + /** + * Returns an IntExpression that represents the right shift of this and + * the given int node, with zero extension. The effect of this method is the same as calling + * this.compose(SHR, intExpr). + * @return this.compose(SHR, intExpr) + */ + public final IntExpression shr(IntExpression intExpr) { + return compose(SHR, intExpr); + } + + /** + * Returns an IntExpression that represents the right shift of this and + * the given int node, with sign extension. The effect of this method is the same as calling + * this.compose(SHA, intExpr). + * @return this.compose(SHA, intExpr) + */ + public final IntExpression sha(IntExpression intExpr) { + return compose(SHA, intExpr); + } + + /** + * Returns an expression that combines this and the given integer expression using the + * specified operator. + * @requires op.binary() + * @return {e: IntExpression | e.left = this and e.right = intExpr and e.op = op } + */ + public final IntExpression compose(IntOperator op, IntExpression intExpr) { + if (op==null || intExpr==null) + throw new NullPointerException(); + return new BinaryIntExpression(this, op, intExpr); + } + + /** + * Returns the sum of the given int expressions. The effect of this method is the + * same as calling compose(PLUS, intExprs). + * @return compose(PLUS, intExprs) + */ + public static IntExpression plus(IntExpression...intExprs) { + return compose(PLUS, intExprs); + } + + /** + * Returns the plus of the given int expressions. The effect of this method is the + * same as calling compose(PLUS, intExprs). + * @return compose(PLUS, intExprs) + */ + public static IntExpression plus(Collection intExprs) { + return compose(PLUS, intExprs); + } + + /** + * Returns the product of the given int expressions. The effect of this method is the + * same as calling compose(MULTIPLY, intExprs). + * @return compose(MULTIPLY, intExprs) + */ + public static IntExpression multiply(IntExpression...intExprs) { + return compose(MULTIPLY, intExprs); + } + + /** + * Returns the product of the given int expressions. The effect of this method is the + * same as calling compose(MULTIPLY, intExprs). + * @return compose(MULTIPLY, intExprs) + */ + public static IntExpression multiply(Collection intExprs) { + return compose(MULTIPLY, intExprs); + } + + /** + * Returns the bitwise and of the given int expressions. The effect of this method is the + * same as calling compose(AND, intExprs). + * @return compose(AND, intExprs) + */ + public static IntExpression and(IntExpression...intExprs) { + return compose(AND, intExprs); + } + + /** + * Returns the bitwise and of the given int expressions. The effect of this method is the + * same as calling compose(AND, intExprs). + * @return compose(AND, intExprs) + */ + public static IntExpression and(Collection intExprs) { + return compose(AND, intExprs); + } + + /** + * Returns the bitwise or of the given int expressions. The effect of this method is the + * same as calling compose(OR, intExprs). + * @return compose(OR, intExprs) + */ + public static IntExpression or(IntExpression...intExprs) { + return compose(OR, intExprs); + } + + /** + * Returns the bitwise or of the given int expressions. The effect of this method is the + * same as calling compose(OR, intExprs). + * @return compose(OR, intExprs) + */ + public static IntExpression or(Collection intExprs) { + return compose(OR, intExprs); + } + + /** + * Returns the composition of the given int expressions using the given operator. + * @requires intExprs.length = 2 => op.binary(), intExprs.length > 2 => op.nary() + * @return intExprs.length=1 => intExprs[0] else {e: IntExpression | e.children = intExprs and e.op = this } + */ + public static IntExpression compose(IntOperator op, IntExpression...intExprs) { + switch(intExprs.length) { + case 0 : throw new IllegalArgumentException("Expected at least one argument: " + Arrays.toString(intExprs)); + case 1 : return intExprs[0]; + case 2 : return new BinaryIntExpression(intExprs[0], op, intExprs[1]); + default : + return new NaryIntExpression(op, Containers.copy(intExprs, new IntExpression[intExprs.length])); + } + } + + /** + * Returns the composition of the given int expressions using the given operator. + * @requires intExprs.length = 2 => op.binary(), intExprs.length > 2 => op.nary() + * @return intExprs.size() = 1 => intExprs.iterator().next() else {e: IntExpression | e.children = intExprs.toArray() and e.op = this } + */ + public static IntExpression compose(IntOperator op, Collection intExprs) { + switch(intExprs.size()) { + case 0 : throw new IllegalArgumentException("Expected at least one argument: " + intExprs); + case 1 : return intExprs.iterator().next(); + case 2 : + final Iterator itr = intExprs.iterator(); + return new BinaryIntExpression(itr.next(), op, itr.next()); + default : + return new NaryIntExpression(op, intExprs.toArray(new IntExpression[intExprs.size()])); + } + } + + /** + * Returns an IntExpression that represents the negation of this int expression. + * The effect of this method is the same as calling this.apply(NEG). + * @return this.apply(NEG) + */ + public final IntExpression negate() { + return apply(NEG); + } + + /** + * Returns an IntExpression that represents the bitwise negation of this int expression. + * The effect of this method is the same as calling this.apply(NOT). + * @return this.apply(NOT) + */ + public final IntExpression not() { + return apply(NOT); + } + + /** + * Returns an IntExpression that represents the absolute value of this int expression. + * The effect of this method is the same as calling this.apply(ABS). + * @return this.apply(ABS) + */ + public final IntExpression abs() { + return apply(ABS); + } + + /** + * Returns an IntExpression that represents the sign of this int expression. + * The effect of this method is the same as calling this.apply(SGN). + * @return this.apply(SGN) + */ + public final IntExpression signum() { + return apply(SGN); + } + + /** + * Returns an expression that represents the application of the given unary + * operator to this integer expression. + * @requires op.unary() + * @return {e: IntExpression | e.op = op and e.intExpr = this } + */ + public final IntExpression apply(IntOperator op) { + return new UnaryIntExpression(op, this); + } + + /** + * Returns an expression whose meaning is the singleton set containing the atom + * that represents the integer given by this integer expression. + * The effect of this method is the same as calling this.cast(INTCAST). + * @return this.cast(INTCAST) + */ + public final Expression toExpression() { + return cast(INTCAST); + } + + /** + * Returns an expression whose meaning is the set containing the atoms + * that represent the powers of 2 (bits) present in this integer expression. + * The effect of this method is the same as calling this.cast(BITSETCAST). + * @return this.cast(BITSETCAST) + */ + public final Expression toBitset() { + return cast(BITSETCAST); + } + + /** + * Returns an expression that is the relational representation of this + * integer expression specified by the given operator. + * @return an expression that is the relational representation of this + * integer expression specified by the given operator. + */ + public final Expression cast(IntCastOperator op) { + if (op==null) throw new NullPointerException(); + return new IntToExprCast(this, op); + } + + /** + * {@inheritDoc} + * @see kodkod.ast.Node#accept(kodkod.ast.visitor.ReturnVisitor) + */ + public abstract I accept(ReturnVisitor visitor) ; + + /** + * {@inheritDoc} + * @see kodkod.ast.Node#accept(kodkod.ast.visitor.VoidVisitor) + */ + public abstract void accept(VoidVisitor visitor); + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/IntToExprCast.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/IntToExprCast.java new file mode 100644 index 00000000..cf65d17a --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/IntToExprCast.java @@ -0,0 +1,101 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.ast; + +import kodkod.ast.operator.IntCastOperator; +import kodkod.ast.visitor.ReturnVisitor; +import kodkod.ast.visitor.VoidVisitor; + +/** + * Represents the conversion from an {@link kodkod.ast.IntExpression int expression } + * to an {@link kodkod.ast.Expression expression}. The meaning of the resulting + * expression is a singleton set containing the atom that represents the integer + * given by the wrapped int expression, if the conversion operator is INTCAST. + * Otherwise, the meaning is the set of powers of 2 that make up the given integer expression. + * @specfield intExpr: IntExpression + * @specfield op: IntCastOperator + * @invariant children = 0->intExpr + * @invariant arity = 1 + * @author Emina Torlak + */ +public final class IntToExprCast extends Expression { + private final IntExpression intExpr; + private final IntCastOperator op; + /** + * Constructs a new IntToExprCast. + * @requires intExpr != null && op != null + * @ensures this.intexpr' = intExpr + */ + IntToExprCast(IntExpression intExpr, IntCastOperator op) { + this.intExpr = intExpr; + this.op = op; + } + + /** + * Returns 1. + * @return 1 + */ + @Override + public int arity() { + return 1; + } + + /** + * Returns this.intExpr. + * @return this.intExpr + */ + public IntExpression intExpr() { + return intExpr; + } + + /** + * Returns this.op + * @return this.op + */ + public final IntCastOperator op() { + return op; + } + + /** + * {@inheritDoc} + * @see kodkod.ast.Expression#accept(kodkod.ast.visitor.ReturnVisitor) + */ + public E accept(ReturnVisitor visitor) { + return visitor.visit(this); + } + + /** + * {@inheritDoc} + * @see kodkod.ast.Node#accept(kodkod.ast.visitor.VoidVisitor) + */ + public void accept(VoidVisitor visitor) { + visitor.visit(this); + } + + /** + * {@inheritDoc} + * @see kodkod.ast.Node#toString() + */ public String toString() { + return op + "[" + intExpr + "]"; + } + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/LeafExpression.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/LeafExpression.java new file mode 100644 index 00000000..539df1a1 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/LeafExpression.java @@ -0,0 +1,82 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.ast; + +/** + * An expression with no children. + * {@link kodkod.ast.Relation Relation} and {@link kodkod.ast.Variable Variable} + * are examples of leaf exressions. Two leaf expressions are equal + * if and only if they refer to the same object. That is, + * leaf1.eauls(leaf2) <=> leaf1 == leaf2. A leaf has a name, which is + * basically a comment for the purpose of printing, viewing, etc. The name + * has no meaning otherwise. + * + * @specfield name: String + * @specfield arity: int + * @specfield no children + * @author Emina Torlak + */ +public abstract class LeafExpression extends Expression { + + private final int arity; + private final String name; + + /** + * Constructs a leaf with the specified name and arity + * + * @ensures this.name' = name && this.arity' = arity + * @throws IllegalArgumentException - arity < 1 + */ + LeafExpression(String name, int arity) { + if (arity < 1) { + throw new IllegalArgumentException("Arity must be at least 1: " + arity); + } + this.name = name; + this.arity = arity; + } + + + /** + * Returns the arity of this leaf. + * @return this.arity + */ + public final int arity() { + return arity; + } + + /** + * Returns the name of this leaf. + * @return this.name + */ + public final String name() { + return name; + } + + /** + * {@inheritDoc} + * @see kodkod.ast.Node#toString() + */ + public String toString() { + return name; + } + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/MultiplicityFormula.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/MultiplicityFormula.java new file mode 100644 index 00000000..ba0e6ed7 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/MultiplicityFormula.java @@ -0,0 +1,91 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.ast; + + +import kodkod.ast.operator.Multiplicity; +import kodkod.ast.visitor.ReturnVisitor; +import kodkod.ast.visitor.VoidVisitor; + + +/** + * A multiplicity formula, e.g. some x + * + * @specfield expression: Expression + * @specfield multiplicity: (ONE + LONE + SOME + NO) + * @invariant children = 0->expression + * @author Emina Torlak + */ +public final class MultiplicityFormula extends Formula { + private final Expression expression; + private final Multiplicity multiplicity; + + /** + * Constructs a new multiplicity formula: multiplicity expression + * + * @ensures this.expression' = expression && this.multiplicity' = multiplicity + * @throws NullPointerException - multiplicity = null || expression = null + * @throws IllegalArgumentException - multiplicity = SET + */ + MultiplicityFormula(Multiplicity multiplicity, Expression expression) { + if (multiplicity==Multiplicity.SET) throw new IllegalArgumentException("invalid expression mulitplicity: SET"); + if (multiplicity== null || expression == null) throw new NullPointerException("null arg"); + this.multiplicity = multiplicity; + this.expression = expression; + } + + /** + * Returns the mulitplicity of this. + * @return this.multiplicity + */ + public Multiplicity multiplicity() { return multiplicity; } + + /** + * Returns the expression of this. + * @return this.expression + */ + public Expression expression() { return expression; } + + /** + * {@inheritDoc} + * @see kodkod.ast.Formula#accept(kodkod.ast.visitor.ReturnVisitor) + */ + public F accept(ReturnVisitor visitor) { + return visitor.visit(this); + } + + /** + * {@inheritDoc} + * @see kodkod.ast.Node#accept(kodkod.ast.visitor.VoidVisitor) + */ + public void accept(VoidVisitor visitor) { + visitor.visit(this); + } + + /** + * {@inheritDoc} + * @see kodkod.ast.Node#toString() + */ + public String toString() { + return multiplicity + " " + expression; + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/NaryExpression.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/NaryExpression.java new file mode 100644 index 00000000..fee17a89 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/NaryExpression.java @@ -0,0 +1,149 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.ast; + +import java.util.Iterator; + +import kodkod.ast.operator.ExprOperator; +import kodkod.ast.visitor.ReturnVisitor; +import kodkod.ast.visitor.VoidVisitor; +import kodkod.util.collections.Containers; + +/** + * A relational {@linkplain kodkod.ast.Expression expression} with more than two children, + * composed with an nary {@linkplain ExprOperator operator}. + * + * @specfield op: ExprOperator + * @invariant op.nary() + * @invariant #children > 2 + * @author Emina Torlak + */ +public final class NaryExpression extends Expression implements Iterable{ + private final ExprOperator op; + private final int arity; + private final Expression[] children; + + /** + * Constructs a new associative expression: op(children) + * @requires children array is not modified while in use by this associative expression + * @requires some op.op[children] + * @ensures this.children' = children && this.op' = op + */ + NaryExpression(ExprOperator op, Expression[] children) { + assert children.length>2; + if (!op.nary()) + throw new IllegalArgumentException("Cannot construct an nary expression using the non-nary operator " + op); + + this.op = op; + this.children = children; + + switch(op) { + case UNION : case INTERSECTION : case OVERRIDE : + this.arity = children[0].arity(); + for(int i = 1; i < children.length; i++) { + if (children[i].arity()!=arity) + throw new IllegalArgumentException("Incompatible arities: " + children[0] + " and " + children[i]); + } + break; + case PRODUCT : + int sum = 0; + for(Expression child : children) { sum += child.arity(); } + this.arity = sum; + break; + default : + throw new IllegalArgumentException("Unknown associative operator: " + op); + } + } + + /** + * Returns the arity of this associative expression. + * @return this.arity + * @see kodkod.ast.Expression#arity() + */ + public final int arity() { return arity; } + + /** + * Returns this.op. + * @return this.op + */ + public ExprOperator op() { return op ; } + + + /** + * Returns the number of children of this expression. + * @return #this.children + */ + public int size() { return children.length; } + + /** + * Returns the ith child of this associative expression. + * @requires 0 <= i < #this.children + * @return this.children[i] + */ + public Expression child(int i) { return children[i]; } + + /** + * Returns an iterator over this expression's children, + * in the increasing order of indices. + * @return an iterator over this expression's children, + * in the increasing order of indices. + */ + public Iterator iterator() { return Containers.iterate(children); } + + /** + * {@inheritDoc} + * @see kodkod.ast.Expression#accept(kodkod.ast.visitor.ReturnVisitor) + */ + @Override + public E accept(ReturnVisitor visitor) { + return visitor.visit(this); + } + + /** + * {@inheritDoc} + * @see kodkod.ast.Node#accept(kodkod.ast.visitor.VoidVisitor) + */ + @Override + public void accept(VoidVisitor visitor) { + visitor.visit(this); + } + + /** + * {@inheritDoc} + * @see kodkod.ast.Node#toString() + */ + @Override + public String toString() { + final StringBuilder s = new StringBuilder("("); + s.append(child(0)); + for(int i = 1, size = size(); i < size; i++) { + s.append(" "); + s.append(op); + s.append(" "); + s.append(child(i)); + } + s.append(")"); + return s.toString(); + } + + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/NaryFormula.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/NaryFormula.java new file mode 100644 index 00000000..53a3cce3 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/NaryFormula.java @@ -0,0 +1,122 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.ast; + +import java.util.Iterator; + +import kodkod.ast.operator.FormulaOperator; +import kodkod.ast.visitor.ReturnVisitor; +import kodkod.ast.visitor.VoidVisitor; +import kodkod.util.collections.Containers; + +/** + * A {@linkplain kodkod.ast.Formula formula} with more than two children, + * composed with an nary {@linkplain FormulaOperator operator}. + * + * @specfield op: FormulaOperator + * @invariant op.nary() + * @invariant #children > 2 + * @author Emina Torlak + */ +public final class NaryFormula extends Formula implements Iterable { + + private final FormulaOperator op; + private final Formula[] children; + + /** + * Constructs a new composite Formula: op(children) + * @requires children array is not modified while in use by this composite Formula + * @requires some op.op[children] + * @ensures this.children' = children && this.op' = op + */ + NaryFormula(FormulaOperator op, Formula[] children) { + assert children.length>2; + if (!op.nary()) + throw new IllegalArgumentException("Cannot construct an nary formula using the non-nary operator " + op); + this.op = op; + this.children = children; + } + + /** + * Returns the operator of this. + * @return this.op + */ + public FormulaOperator op() {return op;} + + /** + * Returns the number of children of this formula. + * @return #this.children + */ + public int size() { return children.length; } + + /** + * Returns the ith child of this formula. + * @requires 0 <= i < #this.children + * @return this.children[i] + */ + public Formula child(int i) { return children[i]; } + + /** + * Returns an iterator over this formula's children, + * in the increasing order of indices. + * @return an iterator over this formula's children, + * in the increasing order of indices. + */ + public Iterator iterator() { return Containers.iterate(children); } + + /** + * {@inheritDoc} + * @see kodkod.ast.Formula#accept(kodkod.ast.visitor.ReturnVisitor) + */ + @Override + public F accept(ReturnVisitor visitor) { + return visitor.visit(this); + } + + /** + * {@inheritDoc} + * @see kodkod.ast.Node#accept(kodkod.ast.visitor.VoidVisitor) + */ + @Override + public void accept(VoidVisitor visitor) { + visitor.visit(this); + } + + /** + * {@inheritDoc} + * @see kodkod.ast.Node#toString() + */ + @Override + public String toString() { + final StringBuilder s = new StringBuilder("("); + s.append(child(0)); + for(int i = 1, size = size(); i < size; i++) { + s.append(" "); + s.append(op); + s.append(" "); + s.append(child(i)); + } + s.append(")"); + return s.toString(); + } + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/NaryIntExpression.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/NaryIntExpression.java new file mode 100644 index 00000000..84f07df9 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/NaryIntExpression.java @@ -0,0 +1,123 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.ast; + +import java.util.Iterator; + +import kodkod.ast.operator.IntOperator; +import kodkod.ast.visitor.ReturnVisitor; +import kodkod.ast.visitor.VoidVisitor; +import kodkod.util.collections.Containers; + +/** + * A {@linkplain kodkod.ast.IntExpression int expression} with more than two children, + * composed with an nary {@linkplain IntOperator operator}. + * + * @specfield op: IntOperator + * @invariant op.nary() + * @invariant #children > 2 + * @author Emina Torlak + */ +public final class NaryIntExpression extends IntExpression implements Iterable { + + private final IntOperator op; + private final IntExpression[] children; + + /** + * Constructs a new composite IntExpression: op(children) + * @requires children array is not modified while in use by this composite IntExpression + * @requires some op.op[children] + * @ensures this.children' = children && this.op' = op + */ + NaryIntExpression(IntOperator op, IntExpression[] children) { + assert children.length>2; + if (!op.nary()) + throw new IllegalArgumentException("Cannot construct an nary int expression using the non-nary operator " + op); + this.op = op; + this.children = children; + } + + /** + * Returns the operator of this. + * @return this.op + */ + public IntOperator op() {return op;} + + /** + * Returns the number of children of this int expression. + * @return #this.children + */ + public int size() { return children.length; } + + /** + * Returns the ith child of this int expression. + * @requires 0 <= i < #this.children + * @return this.children[i] + */ + public IntExpression child(int i) { return children[i]; } + + /** + * Returns an iterator over this int expression's children, + * in the increasing order of indices. + * @return an iterator over this int expression's children, + * in the increasing order of indices. + */ + public Iterator iterator() { return Containers.iterate(children); } + + + /** + * {@inheritDoc} + * @see kodkod.ast.IntExpression#accept(kodkod.ast.visitor.ReturnVisitor) + */ + @Override + public I accept(ReturnVisitor visitor) { + return visitor.visit(this); + } + + /** + * {@inheritDoc} + * @see kodkod.ast.IntExpression#accept(kodkod.ast.visitor.VoidVisitor) + */ + @Override + public void accept(VoidVisitor visitor) { + visitor.visit(this); + } + + /** + * {@inheritDoc} + * @see kodkod.ast.Node#toString() + */ + @Override + public String toString() { + final StringBuilder s = new StringBuilder("("); + s.append(child(0)); + for(int i = 1, size = size(); i < size; i++) { + s.append(" "); + s.append(op); + s.append(" "); + s.append(child(i)); + } + s.append(")"); + return s.toString(); + } + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/Node.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/Node.java new file mode 100644 index 00000000..5e4aa3a6 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/Node.java @@ -0,0 +1,62 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.ast; + +import kodkod.ast.visitor.ReturnVisitor; +import kodkod.ast.visitor.VoidVisitor; + + +/** + * A node in the abstract syntax tree (DAG). A node + * can accept a ReturnVisitor and have a sequence of + * zero or more children. + * + * @specfield children: int ->lone Node + * @specfield components: set Node + * @invariant children.Node = { i: int | 0 <= i < #children } + * @invariant components= children[int] + * @author Emina Torlak + */ +public abstract class Node { + + /** + * Accepts the given visitor and returns the result + * of the visit (i.e. the result of the call visitor.visit(this)) + * @return the result of being visited by the given visitor + * @throws NullPointerException visitor = null + */ + public abstract Object accept(ReturnVisitor visitor); + + /** + * Accepts the given void visitor by calling visitor.visit(this). + * @throws NullPointerException visitor = null + */ + public abstract void accept(VoidVisitor visitor); + + /** + * Returns a string representation of this node. + * For a pretty-printed string, use {@linkplain kodkod.util.nodes.PrettyPrinter}. + * @return a string representation of this node + * @see kodkod.util.nodes.PrettyPrinter + */ + public abstract String toString(); +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/NotFormula.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/NotFormula.java new file mode 100644 index 00000000..a8cceba7 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/NotFormula.java @@ -0,0 +1,79 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.ast; + + +import kodkod.ast.visitor.ReturnVisitor; +import kodkod.ast.visitor.VoidVisitor; + + +/** + * Negation of a {@link kodkod.ast.Formula formula}. + * + * @specfield formula: Formula + * @invariant children = 0->formula + * @author Emina Torlak + */ +public final class NotFormula extends Formula { + private final Formula formula; + + /** + * Constructs a new formula: !formula + * + * @ensures this.formula' = formula + * @throws NullPointerException - formula = null + */ + NotFormula(Formula child) { + if (child == null) throw new NullPointerException("formula"); + this.formula = child; + } + + /** + * Returns this.formula. + * @return this.formula + */ + public Formula formula() { return formula; } + + /** + * {@inheritDoc} + * @see kodkod.ast.Formula#accept(kodkod.ast.visitor.ReturnVisitor) + */ + public F accept(ReturnVisitor visitor) { + return visitor.visit(this); + } + + /** + * {@inheritDoc} + * @see kodkod.ast.Node#accept(kodkod.ast.visitor.VoidVisitor) + */ + public void accept(VoidVisitor visitor) { + visitor.visit(this); + } + + /** + * {@inheritDoc} + * @see kodkod.ast.Node#toString() + */ + public String toString() { + return "!" + formula; + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/ProjectExpression.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/ProjectExpression.java new file mode 100644 index 00000000..d62da893 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/ProjectExpression.java @@ -0,0 +1,110 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.ast; + +import java.util.Arrays; +import java.util.Iterator; + +import kodkod.ast.visitor.ReturnVisitor; +import kodkod.ast.visitor.VoidVisitor; +import kodkod.util.collections.Containers; + +/** + * A general projection expression. For example, + * let [[e]] = {<a, b, c>, <d, e, f>, <d, g, f>}. Then, + * project(e, 1, 3) = {<a, c>, <d, f>} and project(e, 1, 1, 2) = {<a, a, b>, <d, d, e>, <d, d, g>}. + * + * @specfield expression: Expression + * @specfield columns: [0..arity) -> one IntExpression + * @invariant children = 0->expression + { i: int, e: IntExpression | columns[i-1] = e } + * @author Emina Torlak + */ +public final class ProjectExpression extends Expression { + private final Expression expr; + private final IntExpression[] columns; + + /** + * Constructs a new projection expression using the given + * expr and columns. + * @ensures this.expression' = expr && this.indices' = columns + */ + ProjectExpression(Expression expr, IntExpression... columns) { + if (columns.length==0) + throw new IllegalArgumentException("No columns specified for projection."); + this.expr = expr; + this.columns = new IntExpression[columns.length]; + System.arraycopy(columns, 0, this.columns, 0, columns.length); + } + + /** + * {@inheritDoc} + * @see kodkod.ast.Expression#arity() + */ + public int arity() { return columns.length; } + + /** + * Returns this.expression. + * @return this.expression + */ + public Expression expression() { return expr; } + + /** + * Returns an iterator over this.columns, in proper sequence. + * @return an iterator over this.columns, in proper sequence + */ + public Iterator columns() { + return Containers.iterate(columns); + } + + /** + * Returns the ith column. + * @requires 0 <= i < this.arity + * @return this.columns[i] + */ + public IntExpression column(int i) { return columns[i]; } + + /** + * {@inheritDoc} + * @see kodkod.ast.Expression#accept(kodkod.ast.visitor.ReturnVisitor) + */ + @Override + public E accept(ReturnVisitor visitor) { + return visitor.visit(this); + } + + /** + * {@inheritDoc} + * @see kodkod.ast.Node#accept(kodkod.ast.visitor.VoidVisitor) + */ + public void accept(VoidVisitor visitor) { + visitor.visit(this); + } + + /** + * {@inheritDoc} + * @see kodkod.ast.Node#toString() + */ + public String toString() { + return expr.toString() + Arrays.toString(columns); + } + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/QuantifiedFormula.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/QuantifiedFormula.java new file mode 100644 index 00000000..08910d08 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/QuantifiedFormula.java @@ -0,0 +1,102 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.ast; + + +import kodkod.ast.operator.Quantifier; +import kodkod.ast.visitor.ReturnVisitor; +import kodkod.ast.visitor.VoidVisitor; + + +/** + * A quantified formula. + * + * @specfield decls: Declarations + * @specfield formula: Formula + * @specfield quantifier: Quantifier + * @invariant children = 0->decls + 1->formula + * @author Emina Torlak + */ +public final class QuantifiedFormula extends Formula { + private final Quantifier quantifier; + private final Decls decls; + private final Formula formula; + + /** + * Constructs a new quantified formula: quantifier decls | formula + * + * @ensures this.quantifier' = quantifier && this.decls' = decls && + * this.formula' = formula + * @throws NullPointerException - quantifier = null || decls = null || formula = null + */ + QuantifiedFormula(Quantifier quantifier, Decls declarations, Formula formula) { + if (quantifier == null || declarations == null || formula == null) { + throw new NullPointerException("null arg"); + } + this.quantifier = quantifier; + this.decls = declarations; + this.formula = formula; + } + + /** + * Returns this.formula. + * @return this.formula + */ + public Formula formula() { return formula; } + + /** + * Returns this.decls. + * @return this.decls + */ + public Decls decls() { return decls;} + + /** + * Returns this.quantifier. + * @return this.quantifier + */ + public Quantifier quantifier() { return quantifier; } + + /** + * {@inheritDoc} + * @see kodkod.ast.Formula#accept(kodkod.ast.visitor.ReturnVisitor) + */ + public F accept(ReturnVisitor visitor) { + return visitor.visit(this); + } + + /** + * {@inheritDoc} + * @see kodkod.ast.Node#accept(kodkod.ast.visitor.VoidVisitor) + */ + public void accept(VoidVisitor visitor) { + visitor.visit(this); + } + + /** + * {@inheritDoc} + * @see kodkod.ast.Node#toString() + */ + public String toString() { + return "(" + quantifier + " " + decls + " | " + formula + ")"; + } + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/Relation.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/Relation.java new file mode 100644 index 00000000..c9f99979 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/Relation.java @@ -0,0 +1,164 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.ast; + + +import kodkod.ast.operator.Multiplicity; +import kodkod.ast.visitor.ReturnVisitor; +import kodkod.ast.visitor.VoidVisitor; + + + + +/** + * A relation is a leaf expression. + * Two relations are the same if and only if they + * refer to the same object. That is, r1.equals(r2) <=> r1 == r2. Each + * variable has a name, which is basically a comment for the purpose of + * printing, viewing, etc. The name has no meaning otherwise. + * + *

Four methods for creating commonly used predicates over binary relations + * are provided: {@link #function(Expression, Expression)}, {@link #partialFunction(Expression, Expression)}, + * {@link #acyclic()}, and {@link #totalOrder(Relation, Relation, Relation)}. Using + * these methods to generate desired predicates will result in faster constraint solving + * than creating the same predicates via other API calls.

+ * + * @specfield name: String + * @specfield arity: int + * @invariant no children + * @author Emina Torlak + */ +public class Relation extends LeafExpression { + /** + * Constructs a relation with the specified name and arity. + * @ensures this.name' = name && this.arity' = arity + * @throws IllegalArgumentException - arity < 1 + */ + private Relation(String name, int arity) { + super(name,arity); + } + + /** + * Returns a new relation with the given name and arity. + * @return {r: Relation | r.arity = arity && r.name = name } + * @throws IllegalArgumentException - arity < 1 + */ + public static Relation nary(String name, int arity) { + return new Relation(name,arity); + } + + /** + * Returns a new unary relation with the given name. + * The effect of this method is the same as calling Relation.nary(name,1). + * @return {r: Relation | r.arity = 1 && r.name = name } + */ + public static Relation unary(String name) { + return new Relation(name,1); + } + + /** + * Returns a new binary relation with the given name. + * The effect of this method is the same as calling Relation.nary(name,2). + * @return {r: Relation | r.arity = 2 && r.name = name } + */ + public static Relation binary(String name) { + return new Relation(name, 2); + } + + /** + * Returns a ternary relation with the specified name. + * @return {r: Relation | r.name = name && r.arity = 3} + */ + public static Relation ternary(String name) { + return new Relation(name,3); + } + + + /** + * {@inheritDoc} + * @see kodkod.ast.Expression#accept(kodkod.ast.visitor.ReturnVisitor) + */ + public E accept(ReturnVisitor visitor) { + return visitor.visit(this); + } + + /** + * {@inheritDoc} + * @see kodkod.ast.Node#accept(kodkod.ast.visitor.VoidVisitor) + */ + public void accept(VoidVisitor visitor) { + visitor.visit(this); + } + + + /** + * Returns a formula stating that this relation is acyclic. + * @return {f: Formula | f <=> no ^this & iden} + * @throws IllegalArgumentException - this.arity != 2 + */ + public Formula acyclic() { + return new RelationPredicate.Acyclic(this); + } + + /** + * Returns a formula stating that this relation is a total function + * with the specified domain and range. + * @return {f: Formula | f <=> this in domain->range && all v: domain | one v.this } + * @throws NullPointerException - domain = null || range = null + * @throws IllegalArgumentException - domain.arity != 1 || range.arity != 1 + * @throws IllegalArgumentException - this.arity != 2 + */ + public Formula function(Expression domain, Expression range) { + return new RelationPredicate.Function(this, domain, Multiplicity.ONE, range); + } + + /** + * Returns a formula stating that this relation is a partial function + * with the specified domain and range. + * @return {f: Formula | f <=> this in domain->range && all v: domain | lone v.this } + * @throws NullPointerException - domain = null || range = null + * @throws IllegalArgumentException - domain.arity != 1 || range.arity != 1 + * @throws IllegalArgumentException - this.arity != 2 + */ + public Formula partialFunction(Expression domain, Expression range) { + return new RelationPredicate.Function(this, domain, Multiplicity.LONE, range); + } + + /** + * Returns a formula stating that this relation imposes a total ordering + * over the atoms in the set ordered, and that thet first and + * last elements in the ordering are given by the relations first + * and last. + * @return {f: Formula | f <=> one first && one last && last in ordered && + * no this.first && no last.this && + * ordered = first.*this && + * all e: ordered - last | one e.this } + * @throws NullPointerException - any of the arguments are null + * @throws IllegalArgumentException - any of the argument relations' arities are greater than one + * @throws IllegalArgumentException - this.arity != 2 + */ + public Formula totalOrder(Relation ordered, Relation first, Relation last) { + return new RelationPredicate.TotalOrdering(this, ordered, first, last); + } + + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/RelationPredicate.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/RelationPredicate.java new file mode 100644 index 00000000..134f0af8 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/RelationPredicate.java @@ -0,0 +1,343 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.ast; + + +import kodkod.ast.operator.Multiplicity; +import kodkod.ast.visitor.ReturnVisitor; +import kodkod.ast.visitor.VoidVisitor; + +/** + * Represents common predicates on relations; e.g. + * predicates stating that a relation is a total function, + * partial function, acyclic, or a total ordering over + * a set of atoms. + * + * @specfield relation: Relation + * @specfield name: Name // name of the predicate + * @invariant relation.arity = 2 + * @author Emina Torlak + */ +public abstract class RelationPredicate extends Formula { + private final Relation relation; + + /** + * Constructs a new relation predicate for the given relation. + * @throws NullPointerException - relation = null + * @throws IllegalArgumentException - relation.arity != 2 + */ + private RelationPredicate(Relation relation) { + if (relation.arity() != 2) + throw new IllegalArgumentException("invalid arity: " + relation.arity()); + this.relation = relation; + } + + /** + * Returns the relation to which this predicate applies. + * @return this.relation + */ + public Relation relation() { + return relation; + } + + + /** + * Returns the name of this predicate. + * @return this.name + */ + public abstract Name name(); + + /** + * Turns this predicate into explicit constraiants. + * @return {f: Formula - RelationPredicate | f <=> this } + */ + public abstract Formula toConstraints(); + + /** + * {@inheritDoc} + * @see kodkod.ast.Formula#accept(kodkod.ast.visitor.ReturnVisitor) + */ + public F accept(ReturnVisitor visitor) { + return visitor.visit(this); + } + + /** + * {@inheritDoc} + * @see kodkod.ast.Node#accept(kodkod.ast.visitor.VoidVisitor) + */ + public void accept(VoidVisitor visitor) { + visitor.visit(this); + } + + + /** + * Enumerates built-in predicates. + */ + public static enum Name { + /** Function predicate. */ + FUNCTION, + /** Partial function predicate. */ + //PARTIAL_FUNCTION, + /** Acyclic predicate. */ + ACYCLIC, + /** Total ordering predicate. */ + TOTAL_ORDERING + } + + /** + * Represents the acyclic predicate. The predicate states that + * the given relation is acyclic. + * @specfield relation: Relation + * @invariant name = ACYCLIC + * @invariant children = 0->relation + * @author Emina Torlak + */ + public static final class Acyclic extends RelationPredicate { + /** + * Constructs a new acyclic predicate over the given relation. + * @ensures this.relation' = relation && this.name' = ACYCLIC + * @throws IllegalArgumentException - relation.arity != 2 + */ + Acyclic(Relation relation) { + super(relation); + } + + /** + * Returns the name of this predicate. + * @return ACYCLIC + */ + @Override + public Name name() { + return Name.ACYCLIC; + } + + /** + * {@inheritDoc} + * @see kodkod.ast.RelationPredicate#toConstraints() + */ + @Override + public Formula toConstraints() { + return relation().closure().intersection(Expression.IDEN).no(); + } + + + /** + * {@inheritDoc} + * @see kodkod.ast.Node#toString() + */ + public String toString() { + return name() + "(" + relation() + ")"; + } + } + + /** + * Represents the function predicate. The predicate states that the given + * relation is a total or partial function with the specified + * domain and range. + * + * @specfield relation: Relation + * @specfield domain, range: Expression + * @specfield targetMult: ONE + LONE + * @invariant name = FUNCTION + * @invariant domain.arity = range.arity = 1 + * @invariant children = 0->relation + 1->domain + 2->range + * @author Emina Torlak + */ + public static final class Function extends RelationPredicate { + private final Expression domain, range; + private final Multiplicity targetMult; + /** + * Constructs a new function predicate over the given relation and domain, + * with the specified target multiplicity. + * @ensures this.name' = FUNCTION && this.relation' = relation && this.domain' = domain && + * this.range' = range + * @throws IllegalArgumentException - relation.arity != 2 || domain.arity != 1 || range.arity != 1 || + * targetMult !in ONE + LONE + */ + Function(Relation relation, Expression domain, Multiplicity targetMult, Expression range) { + super(relation); + if (targetMult != Multiplicity.ONE && targetMult != Multiplicity.LONE) + throw new IllegalArgumentException("invalid target multiplicity for a function: " + targetMult); + if (domain.arity() != 1 || range.arity() != 1) + throw new IllegalArgumentException("invalid arity: " + domain + " or " + range); + this.targetMult = targetMult; + this.domain = domain; + this.range = range; + } + + /** + * Returns the name of this predicate. + * @return this.name + */ + public Name name() { + return Name.FUNCTION; + } + + + /** + * Returns the target multiplicity of the function represented + * by this.relation. + * @return this.targetMult + */ + public Multiplicity targetMult() { + return targetMult; + } + + /** + * Returns the domain of this.relation. + * @return this.domain + */ + public Expression domain() { + return domain; + } + + /** + * Returns the range of this.relation. + * @return this.range + */ + public Expression range() { + return range; + } + + /** + * {@inheritDoc} + * @see kodkod.ast.RelationPredicate#toConstraints() + */ + @Override + public Formula toConstraints() { + // relation in domain->range + final Formula domainConstraint = relation().in(domain.product(range)); + // all v: domain | targetMult v.relation + final Variable v = Variable.unary("v"+relation().name()); + final Formula funConstraint = v.join(relation()).apply(targetMult).forAll(v.oneOf(domain)); + // relation in domain->range && all v: domain | targetMult v.relation + return domainConstraint.and(funConstraint); + } + + + /** + * {@inheritDoc} + * @see kodkod.ast.Node#toString() + */ + public String toString() { + return name() + "(" + relation() + ", " + + domain + " ->" + targetMult + " " + range + ")"; + } + } + + /** + * Represents the total ordering predicate. The predicate states that the given + * relation imposes a total ordering over the set ordered, + * and that the smallest/largest elements resulting from the ordering are given + * by the first/last relations. + * + * @specfield relation: Relation + * @specfield ordered, first, last: Relation + * @invariant name = TOTAL_ORDERING + * @invariant ordered.arity = first.arity = last.arity = 1 + * @invariant children = 0->relation + 1->ordered + 2->first + 3->last + */ + public static final class TotalOrdering extends RelationPredicate { + private final Relation first, last, ordered; + + /** + * Constructs a new total ordering predicate. + * @ensures this.relation' = relation && this.first' = first && this.last' = last && + * this.name' = TOTAL_ORDERING + * @throws NullPointerException - any of the arguments are null + * @throws IllegalArgumentException - relation.arity != 2 || first.arity != 1 || last.arity != 1 + **/ + TotalOrdering(Relation relation, Relation ordered, Relation first, Relation last) { + super(relation); + if (first.arity() != 1 || last.arity() != 1 || ordered.arity() != 1) + throw new IllegalArgumentException("invalid arity: " + first + " or " + last + " or " + ordered); + this.first = first; + this.last = last; + this.ordered = ordered; + } + + /** + * Returns the name of this predicate. + * @return TOTAL_ORDERING + */ + public Name name() { + return Name.TOTAL_ORDERING; + } + + /** + * Returns the relation representing the first element + * in the ordering imposed by this.relation. + * @return this.first + */ + public Relation first() { + return first; + } + + /** + * Returns the relation representing the last element + * in the ordering imposed by this.relation. + * @return this.last + */ + public Relation last() { + return last; + } + + /** + * Returns the relation representing the atoms which + * are ordered by this.relation. + * @return this.ordered + */ + public Relation ordered() { + return ordered; + } + + /** + * {@inheritDoc} + * @see kodkod.ast.RelationPredicate#toConstraints() + */ + @Override + public Formula toConstraints() { + // one first && one last && last in ordered + final Formula f0 = first.one().and(last.one()).and(last.in(ordered)); + // ordered = first.*relation + final Formula f1 = ordered.eq(first.join(relation().reflexiveClosure())); + // no relation.first && no last.relation + final Formula f2 = relation().join(first).no().and(last.join(relation()).no()); + // all e: ordered - last | one e.this + final Variable e = Variable.unary("e"+relation().name()); + final Formula f3 = e.join(relation()).one().forAll(e.oneOf(ordered.difference(last))); + + return and(f0, f1, f2, f3); + } + + + /** + * {@inheritDoc} + * @see kodkod.ast.Node#toString() + */ + public String toString() { + return name() + "(" + relation() + ", " + ordered + ", " + first + ", " + last + ")"; + } + + } + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/SumExpression.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/SumExpression.java new file mode 100644 index 00000000..535c1795 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/SumExpression.java @@ -0,0 +1,98 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.ast; + +import kodkod.ast.operator.Multiplicity; +import kodkod.ast.visitor.ReturnVisitor; +import kodkod.ast.visitor.VoidVisitor; + +/** + * Denotes the integer obtained by summing the values of an iteger expression ie + * for all values of a scalar x drawn from a set e. + * @specfield intExpr: IntExpression + * @specfield decls: Decls + * @specfield children = 0->decls + 1->intExpr + * @invariant all d: decls.children[int] | d.multiplicity = 1 + * @author Emina Torlak + */ +public final class SumExpression extends IntExpression { + private final Decls decls; + private final IntExpression intExpr; + + /** + * Constructs a sum expression + * @ensures this.decls' = decls && this.intExpr' = intExpr + * @throws IllegalArgumentException - some d: decls.children | d.multiplicty != ONE + */ + SumExpression(Decls decls, IntExpression intExpr) { + for(Decl d : decls) { + if (d.multiplicity()!=Multiplicity.ONE) + throw new IllegalArgumentException(d + " is not a scalar declaration."); + } + this.decls = decls; + this.intExpr = intExpr; + } + + /** + * Returns this.decls. + * @return this.decls + */ + public final Decls decls() { + return decls; + } + + /** + * Returns this.intExpr. + * @return this.intExpr + */ + public final IntExpression intExpr() { + return intExpr; + } + + /** + * {@inheritDoc} + * @see kodkod.ast.IntExpression#accept(kodkod.ast.visitor.ReturnVisitor) + */ + @Override + public I accept(ReturnVisitor visitor) { + return visitor.visit(this); + } + + /** + * {@inheritDoc} + * @see kodkod.ast.IntExpression#accept(kodkod.ast.visitor.VoidVisitor) + */ + @Override + public void accept(VoidVisitor visitor) { + visitor.visit(this); + } + + /** + * {@inheritDoc} + * @see kodkod.ast.Node#toString() + */ + public String toString() { + return "(sum " + decls + " | " + intExpr + ")"; + } + + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/UnaryExpression.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/UnaryExpression.java new file mode 100644 index 00000000..1642f674 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/UnaryExpression.java @@ -0,0 +1,108 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.ast; + + +import kodkod.ast.operator.ExprOperator; +import kodkod.ast.visitor.ReturnVisitor; +import kodkod.ast.visitor.VoidVisitor; + +/** + * An {@link kodkod.ast.Expression expression} with one child. + * + * @specfield expression: Expression + * @specfield op: ExprOperator + * @invariant op.unary() + * @invariant children = 0->Expression + * @author Emina Torlak + */ +public final class UnaryExpression extends Expression { + private final Expression expression; + private final ExprOperator op; + private final int arity; + + /** + * Constructs a new unary expression: op expression + * + * @ensures this.expression' = expression && this.op' = op + * @throws NullPointerException - expression = null || op = null + * @throws IllegalArgumentException - op in {TRANSPOSE, CLOSURE, REFLEXIVE_CLOSURE} && child.arity != 2 + */ + UnaryExpression(ExprOperator op, Expression child) { + if (!op.unary()) { + throw new IllegalArgumentException("Not a unary operator: " + op); + } + if (child.arity()!=2) { + throw new IllegalArgumentException("Invalid arity: " + child + "::" + child.arity()); + } + this.expression = child; + this.op = op; + this.arity = 2; + } + + /** + * Returns the arity of this expression. + * @return this.arity + * @see kodkod.ast.Expression#arity() + */ + public int arity() { + return arity; + } + + /** + * Returns this.expression. + * @return this.expression + */ + public Expression expression() {return expression;} + + /** + * Returns this.op. + * @return this.op + */ + public ExprOperator op() {return op;} + + + /** + * {@inheritDoc} + * @see kodkod.ast.Expression#accept(kodkod.ast.visitor.ReturnVisitor) + */ + public E accept(ReturnVisitor visitor) { + return visitor.visit(this); + } + + /** + * {@inheritDoc} + * @see kodkod.ast.Node#accept(kodkod.ast.visitor.VoidVisitor) + */ + public void accept(VoidVisitor visitor) { + visitor.visit(this); + } + + /** + * {@inheritDoc} + * @see kodkod.ast.Node#toString() + */ + public String toString() { + return op.toString() + expression.toString(); + } + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/UnaryIntExpression.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/UnaryIntExpression.java new file mode 100644 index 00000000..e801075b --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/UnaryIntExpression.java @@ -0,0 +1,87 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.ast; + +import kodkod.ast.operator.IntOperator; +import kodkod.ast.visitor.ReturnVisitor; +import kodkod.ast.visitor.VoidVisitor; + +/** + * A unary integer intExpr, e.g. -x. + * @specfield intExpr: IntExpression + * @specfield op: IntOperator + * @invariant op.unary() + * @invariant children = 0->intExpr + * @author Emina Torlak + */ +public final class UnaryIntExpression extends IntExpression { + private final IntOperator op; + private final IntExpression intExpr; + + /** + * Constructs a new unary int formula: op intExpr + * @ensures this.op' = op && this.intExpr' = intExpr + */ + UnaryIntExpression(IntOperator op, IntExpression intExpr) { + if (!op.unary()) throw new IllegalArgumentException("Not a unary operator: " + op); + this.op = op; + this.intExpr = intExpr; + } + + /** + * Returns the operator of this. + * @return this.op + */ + public IntOperator op() {return op;} + + /** + * Returns this.intExpr. + * @return this.intExpr + */ + public IntExpression intExpr() {return intExpr;} + + /** + * {@inheritDoc} + * @see kodkod.ast.IntExpression#accept(kodkod.ast.visitor.ReturnVisitor) + */ + @Override + public I accept(ReturnVisitor visitor) { + return visitor.visit(this); + } + + /** + * {@inheritDoc} + * @see kodkod.ast.IntExpression#accept(kodkod.ast.visitor.VoidVisitor) + */ + @Override + public void accept(VoidVisitor visitor) { + visitor.visit(this); + } + + /** + * {@inheritDoc} + * @see kodkod.ast.Node#toString() + */ + public String toString() { + return (op==IntOperator.NEG||op==IntOperator.NOT) ? "(" + op + intExpr + ")" : op + "(" + intExpr + ")" ; + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/Variable.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/Variable.java new file mode 100644 index 00000000..79d312d9 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/Variable.java @@ -0,0 +1,151 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.ast; + +import kodkod.ast.operator.Multiplicity; +import kodkod.ast.visitor.ReturnVisitor; +import kodkod.ast.visitor.VoidVisitor; + +/** + * Represents a variable in a {@link QuantifiedFormula quantified formula}, + * a {@link Comprehension comprehension expression}, or a {@link SumExpression sum expression}. + * Two variables are the same if and only if they + * refer to the same object. That is, v1.eauls(v2) <=> v1 == v2. Each + * variable has a name, which is basically a comment for the purpose of + * printing, viewing, etc. The name has no meaning otherwise. The arity of + * a variable specifies the arity of expressions over which the variable can + * range. + * + * @specfield name: String + * @specfield arity: int + * @invariant no children + * @author Emina Torlak + */ +public final class Variable extends LeafExpression { + + /** + * Constructs a variable with the specified name and arity 1. + * @ensures this.name' = name && this.arity' = 1 + */ + private Variable(String name) { + super(name, 1); + } + + /** + * Constructs a variable with the specified name and arity. + * @ensures this.name' = name && this.arity' = arity + */ + private Variable(String name, int arity) { + super(name, arity); + } + + /** + * Returns a new variable with the specified name and arity 1. + * @ensures this.name' = name && this.arity' = 1 + */ + public static Variable unary(String name) { + return new Variable(name); + } + + /** + * Returns a new variable with the specified name and arity. + * @ensures this.name' = name && this.arity' = arity + * @throws IllegalArgumentException - arity < 1 + */ + public static Variable nary(String name, int arity) { + return new Variable(name, arity); + } + + /** + * Returns the declaration that constrains this variable to + * be bound to at most one element of the given expression: 'this: lone expr'. + * @return {d: Decl | d.variable = this && d.multiplicity = LONE && d.expression = expr } + * @throws NullPointerException - expr = null + * @throws IllegalArgumentException - this.arity != expr.arity || expr.arity != 1 + */ + public Decl loneOf(Expression expr) { + return new Decl(this, Multiplicity.LONE, expr); + } + + /** + * Returns the declaration that constrains this variable to + * be bound to exactly one element of the given expression: 'this: one expr'. + * @return {d: Decl | d.variable = this && d.multiplicity = ONE && d.expression = expr } + * @throws NullPointerException - expr = null + * @throws IllegalArgumentException - this.arity != expr.arity || expr.arity != 1 + */ + public Decl oneOf(Expression expr) { + return new Decl(this, Multiplicity.ONE, expr); + } + + /** + * Returns the declaration that constrains this variable to + * be bound to at least one element of the given expression: 'this: some expr'. + * @return {d: Decl | d.variable = this && d.multiplicity = SOME && d.expression = expr } + * @throws NullPointerException - expr = null + * @throws IllegalArgumentException - this.arity != expr.arity || expr.arity != 1 + */ + public Decl someOf(Expression expr) { + return new Decl(this, Multiplicity.SOME, expr); + } + + /** + * Returns the declaration that constrains this variable to + * be bound to a subset of the elements in the given expression: 'this: set expr'. + * @return {d: Decl | d.variable = this && d.multiplicity = SET && d.expression = expr } + * @throws NullPointerException - expr = null + * @throws IllegalArgumentException - this.arity != expr.arity + */ + public Decl setOf(Expression expr) { + return new Decl(this, Multiplicity.SET, expr); + } + + /** + * Returns the declaration that constrains this variable to + * be bound to the specified number of the elements in the given expression: 'this: mult expr'. + * @return {d: Decl | d.variable = this && d.multiplicity = mult && d.expression = expr } + * @throws NullPointerException - expression = null || mult = null + * @throws IllegalArgumentException - mult = NO + * @throws IllegalArgumentException - mult in ONE + LONE + SOME && expr.arity != 1 + * @throws IllegalArgumentException - this.arity != expr.arity + */ + public Decl declare(Multiplicity mult, Expression expr) { + return new Decl(this, mult, expr); + } + + /** + * {@inheritDoc} + * @see kodkod.ast.Expression#accept(kodkod.ast.visitor.ReturnVisitor) + */ + public E accept(ReturnVisitor visitor) { + return visitor.visit(this); + } + + /** + * {@inheritDoc} + * @see kodkod.ast.Node#accept(kodkod.ast.visitor.VoidVisitor) + */ + public void accept(VoidVisitor visitor) { + visitor.visit(this); + } + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/operator/ExprCastOperator.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/operator/ExprCastOperator.java new file mode 100644 index 00000000..9012c268 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/operator/ExprCastOperator.java @@ -0,0 +1,12 @@ +package kodkod.ast.operator; + +/** + * Enumerates expression 'cast' operators. + */ +public enum ExprCastOperator { + /** The cardinality operator (#). */ + CARDINALITY { public String toString() { return "#"; } }, + /** The sum operator. */ + SUM { public String toString() { return "sum"; } }; + +} \ No newline at end of file diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/operator/ExprCompOperator.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/operator/ExprCompOperator.java new file mode 100644 index 00000000..5548df76 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/operator/ExprCompOperator.java @@ -0,0 +1,11 @@ +package kodkod.ast.operator; + +/** + * Enumerates relational comparison operators. + */ +public enum ExprCompOperator { + /** Subset operator (in). */ + SUBSET { public String toString() { return "in"; } }, + /** Equality operator (=). */ + EQUALS { public String toString() { return "="; } }; +} \ No newline at end of file diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/operator/ExprOperator.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/operator/ExprOperator.java new file mode 100644 index 00000000..18a50f6c --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/operator/ExprOperator.java @@ -0,0 +1,58 @@ +package kodkod.ast.operator; + + + + +/** + * Enumerates unary (~, ^, *), binary (+, &, ++, ->, -, .) and nary (+, &, ++, ->) expression operators. + * @specfield op: (int->lone Expression) -> Expression + * @invariant all args: seq Expression, out: Expression | args->out in op => (out.children = args && out.op = this) + */ +public enum ExprOperator { + /** Relational union (+) operator. */ + UNION { public String toString() { return "+"; } }, + /** Relational intersection (&) operator. */ + INTERSECTION { public String toString() { return "&"; } }, + /** Relational override (++) operator. */ + OVERRIDE { public String toString() { return "++"; } }, + /** Relational product (->) operator. */ + PRODUCT { public String toString() { return "->"; } }, + /** Relational difference (-) operator. */ + DIFFERENCE { public String toString() { return "-"; } }, + /** Relational join (.) operator. */ + JOIN { public String toString() { return "."; } }, + /** Transpose (~) operator. */ + TRANSPOSE { public String toString() { return "~";} }, + /** Transitive closure (^) operator. */ + CLOSURE { public String toString() { return "^";} }, + /** Reflexive transitive closure (*) operator. */ + REFLEXIVE_CLOSURE { public String toString() { return "*";} }; + + + static final int unary = TRANSPOSE.index() | CLOSURE.index() | REFLEXIVE_CLOSURE.index(); + + static final int binary = ~unary; + + static final int nary = UNION.index() | INTERSECTION.index() | OVERRIDE.index() | PRODUCT.index(); + + private final int index() { return 1<, <=>) and nary (&&, ||) logical operators. + * @specfield op: (int->lone Formula) -> Formula + * @invariant all args: seq Formula, out: Formula | args->out in op => (out.children = args && out.op = this) + */ +public enum FormulaOperator { + /** Logical AND operator. */ + AND { public String toString() { return "&&"; } }, + /** Logical OR operator. */ + OR { public String toString() { return "||"; } }, + /** Logical bi-implication operator. */ + IFF { public String toString() { return "<=>"; } }, + /** Logical implication operator. */ + IMPLIES { public String toString() { return "=>"; } }; + + static final int nary = (1<, <=, >=. + */ +public enum IntCompOperator { + /** `=' operator */ + EQ { public String toString() { return "="; } }, + /** `!=' operator */ + NEQ { public String toString() { return "!="; } }, + /** `<' operator */ + LT { public String toString() { return "<"; } }, + /** `<=' operator */ + LTE { public String toString() { return "<="; } }, + /** `>' operator */ + GT { public String toString() { return ">"; } }, + /** `>=' operator */ + GTE { public String toString() { return ">="; } }; +} \ No newline at end of file diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/operator/IntOperator.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/operator/IntOperator.java new file mode 100644 index 00000000..ecfba9bd --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/operator/IntOperator.java @@ -0,0 +1,67 @@ +package kodkod.ast.operator; + + + +/** + * Enumerate unary (-, ~, abs, sgn), binary (+, *, &, |, -, /, %, >>, >>>, <<) and nary (+, *, &, |) operators on integer expressions. + * @specfield op: (int->lone IntExpression) -> IntExpression + * @invariant all args: seq IntExpression, out: IntExpression | args->out in op => (out.children = args && out.op = this) + */ +public enum IntOperator { + /** `+' operator */ + PLUS { public String toString() { return "+"; } }, + /** `*' operator */ + MULTIPLY { public String toString() { return "*"; } }, + /** `-' operator */ + MINUS { public String toString() { return "-"; } }, + /** `/' operator */ + DIVIDE { public String toString() { return "/"; } }, + /** `%' operator */ + MODULO { public String toString() { return "%"; } }, + /** Bitwise AND operator */ + AND { public String toString() { return "&"; } }, + /** Bitwise OR operator */ + OR { public String toString() { return "|"; } }, + /** Bitwise XOR operator */ + XOR { public String toString() { return "^"; } }, + /** Left shift operator */ + SHL { public String toString() { return "<<"; } }, + /** Right shift operator with zero extension */ + SHR { public String toString() { return ">>>"; } }, + /** Right shift operator with sign extension */ + SHA { public String toString() { return ">>"; } }, + /** unary negation (`-') operator */ + NEG { public String toString() { return "-"; } }, + /** bit negation (`~') operator */ + NOT { public String toString() { return "~"; } }, + /** absolute value function */ + ABS { public String toString() { return "abs";} }, + /** signum function */ + SGN { public String toString() { return "sgn"; } }; + + static final int unary = NEG.index() | NOT.index() | ABS.index() | SGN.index(); + + static final int binary = ~unary; + + static final int nary = PLUS.index() | MULTIPLY.index() | AND.index() | OR.index(); + + private final int index() { return 1<no expr: expr contains no elements. The 'no' multiplicity can only be used in a multiplicity formula. */ + NO { public String toString() { return "no"; }}, + /** lone expr: expr contains at most one element. */ + LONE { public String toString() { return "lone"; }}, + /** one expr: expr contains exactly one element. */ + ONE { public String toString() { return "one"; }}, + /** some expr: expr contains at least one element. */ + SOME { public String toString() { return "some"; }}, + /** v: set expr: v is a subset of expr. The 'set' multiplicity can only be used in a declaration. */ + SET { public String toString() { return "set"; }} +} \ No newline at end of file diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/operator/Quantifier.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/operator/Quantifier.java new file mode 100644 index 00000000..d4a408a3 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/operator/Quantifier.java @@ -0,0 +1,11 @@ +package kodkod.ast.operator; + +/** + * Enumerates logical quantifiers. + */ +public enum Quantifier { + /** Universal quantifier. */ + ALL { public String toString() { return "all"; }}, + /** Existential quantifier. */ + SOME { public String toString() { return "some"; }} +} \ No newline at end of file diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/operator/package.html b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/operator/package.html new file mode 100644 index 00000000..50cb245b --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/operator/package.html @@ -0,0 +1,32 @@ + + + + + + + + +Contains operators for Kodkod formulas, expressions, and integer expressions. + +

Package Specification

+ +

Contains operators for Kodkod formulas, expressions, and integer expressions.

+ + + + diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/package.html b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/package.html new file mode 100644 index 00000000..6f886408 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/package.html @@ -0,0 +1,48 @@ + + + + + + + + +Contains classes for creating Kodkod formulas, expressions, +and integer expressions. + +

Package Specification

+ +

Contains the classes for creating a Kodkod abstract syntax +tree (AST). An object such as a quantified formula or a union expression is called a node. +The {@linkplain kodkod.ast.Node} class is the root of the Kodkod syntax hierarchy.

+ +

All classes in this package are immutable. Their instances +are created by calling factory methods of the classes {@linkplain kodkod.ast.Relation}, {@linkplain kodkod.ast.Variable}, +{@linkplain kodkod.ast.Expression}, {@linkplain kodkod.ast.IntExpression}, and {@linkplain kodkod.ast.Formula}. Nodes can be freely shared between +multiple parents (so a Kodkod AST is actually a directed acyclic graph).

+ +

Related Documentation

+ +@see kodkod.ast.Relation +@see kodkod.ast.Variable +@see kodkod.ast.Expression +@see kodkod.ast.IntExpression +@see kodkod.ast.Formula +@see kodkod.ast.Node + + + diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/visitor/AbstractCollector.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/visitor/AbstractCollector.java new file mode 100644 index 00000000..120b1608 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/visitor/AbstractCollector.java @@ -0,0 +1,591 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.ast.visitor; + +import java.util.Collections; +import java.util.IdentityHashMap; +import java.util.Map; +import java.util.Set; + +import kodkod.ast.BinaryExpression; +import kodkod.ast.BinaryFormula; +import kodkod.ast.BinaryIntExpression; +import kodkod.ast.ComparisonFormula; +import kodkod.ast.Comprehension; +import kodkod.ast.ConstantExpression; +import kodkod.ast.ConstantFormula; +import kodkod.ast.Decl; +import kodkod.ast.Decls; +import kodkod.ast.ExprToIntCast; +import kodkod.ast.Expression; +import kodkod.ast.Formula; +import kodkod.ast.IfExpression; +import kodkod.ast.IfIntExpression; +import kodkod.ast.IntComparisonFormula; +import kodkod.ast.IntConstant; +import kodkod.ast.IntExpression; +import kodkod.ast.IntToExprCast; +import kodkod.ast.MultiplicityFormula; +import kodkod.ast.NaryExpression; +import kodkod.ast.NaryFormula; +import kodkod.ast.NaryIntExpression; +import kodkod.ast.Node; +import kodkod.ast.NotFormula; +import kodkod.ast.ProjectExpression; +import kodkod.ast.QuantifiedFormula; +import kodkod.ast.Relation; +import kodkod.ast.RelationPredicate; +import kodkod.ast.SumExpression; +import kodkod.ast.UnaryExpression; +import kodkod.ast.UnaryIntExpression; +import kodkod.ast.Variable; + +/** + *

A depth first collector. Subclasses should override the + * methods in which detection is performed to return the appropriate set. + * For example, a Variable collector could be implemented + * simply by subclassing this implementation and overriding + * the {@link #visit(Variable) } method to return a singleton set containing + * the input argument.

+ * + * @specfield cached: set Node // result of visiting these nodes will be cached + * @specfield cache: Node -> lone Set + * @specfield cached in cache.Node + * @author Emina Torlak + */ +public abstract class AbstractCollector implements + ReturnVisitor, Set, Set, Set> { + protected final Map> cache; + protected final Set cached; + + /** + * Constructs a depth first collector which will cache the results + * of visiting the given nodes and re-use them on subsequent visits. + * @ensures this.cached' = cached && no this.cache + */ + protected AbstractCollector(Set cached) { + this.cached = cached; + this.cache = new IdentityHashMap>(cached.size()); + } + + /** + * Constructs a depth-first collectior which will cache + * the results of visiting the given nodes in the given map, + * and re-use them on subsequent visits. + * @ensures this.cached' = cached && this.cache' = cache + */ + protected AbstractCollector(Set cached, Map> cache) { + this.cached = cached; + this.cache = cache; + } + /** + * If n has been visited and a value for it cached, + * the cached value is returned. Otherwise null is returned. + * @return this.cache[n] + */ + protected Set lookup(Node n) { + return cache.get(n); + } + + /** + * Caches the given value for the specified node, if + * this is a caching visitor, and returns it. + * @ensures n in this.cached => this.cache' = this.cache ++ n->reduce(val), this.cache' = this.cache + * @return val + */ + protected Set cache(Node n, Set val) { + if (cached.contains(n)) { + cache.put(n, reduce(val)); + } + return val; + } + + /** + * Returns the set that has the same contents as val, but that may + * be more efficiently implemented than val. + * @return val.size()=0 => Collections.EMPTY_SET, + * val.size()=1 => Collections.singleton(val.iterator().next()), + * val + */ + protected Set reduce(Set val) { + switch(val.size()) { + case 0 : return Collections.emptySet(); + case 1 : return Collections.singleton(val.iterator().next()); + default : return val; + } + } + + /** + * Returns a new, empty, modifiable set. + * @return a new, empty, modifiable set. + */ + protected abstract Set newSet() ; + + /** + * Calls lookup(decls) and returns the cached value, if any. + * If no cached value exists, visits each child, caches the + * union of the sets returned by the children and returns it. + * @return let x = lookup(decls) | + * x != null => x, + * cache(decls, decls.declarations[0].accept(this) +...+ decls.declarations[decls.size-1].accept(this)) + */ + public Set visit(Decls decls) { + Set ret = lookup(decls); + if (ret!=null) return ret; + ret = newSet(); + for(Decl d: decls) { + ret.addAll(d.accept(this)); + } + return cache(decls, ret); + } + + /** + * Calls lookup(decl) and returns the cached value, if any. + * If no cached value exists, visits each child, caches the + * union of the sets returned by the children and returns it. + * @return let x = lookup(decl) | + * x != null => x, + * cache(decls, decl.variable.accept(this) + decl.expression.accept(this)) + */ + public Set visit(Decl decl) { + Set ret = lookup(decl); + if (ret!=null) return ret; + ret = newSet(); + ret.addAll(decl.variable().accept(this)); + ret.addAll(decl.expression().accept(this)); + return cache(decl,ret); + } + + /** + * Returns Collections.EMPTY_SET + * @return Collections.EMPTY_SET + */ + @SuppressWarnings("unchecked") + public Set visit(Relation relation) { + return Collections.EMPTY_SET; + } + + /** + * Returns Collections.EMPTY_SET + * @return Collections.EMPTY_SET + */ + @SuppressWarnings("unchecked") + public Set visit(Variable variable) { + return Collections.EMPTY_SET; + } + + /** + * Returns Collections.EMPTY_SET + * @return Collections.EMPTY_SET + */ + @SuppressWarnings("unchecked") + public Set visit(ConstantExpression constExpr) { + return Collections.EMPTY_SET; + } + + /** + * Calls lookup(expr) and returns the cached value, if any. + * If no cached value exists, visits each child, caches the + * union of the children's return values and returns it. + * @return let x = lookup(expr) | + * x != null => x, + * cache(expr, expr.child(0).accept(this) + .. + expr.child(expr.size()-1).accept(this)) + */ + public Set visit(NaryExpression expr) { + Set ret = lookup(expr); + if (ret!=null) return ret; + ret = newSet(); + for(Expression child : expr) { + ret.addAll(child.accept(this)); + } + return cache(expr, ret); + } + + /** + * Calls lookup(binExpr) and returns the cached value, if any. + * If no cached value exists, visits each child, caches the + * union of the children's return values and returns it. + * @return let x = lookup(binExpr) | + * x != null => x, + * cache(binExpr, binExpr.left.accept(this) + binExpr.right.accept(this)) + */ + public Set visit(BinaryExpression binExpr) { + Set ret = lookup(binExpr); + if (ret!=null) return ret; + ret = newSet(); + ret.addAll(binExpr.left().accept(this)); + ret.addAll(binExpr.right().accept(this)); + return cache(binExpr, ret); + } + + /** + * Calls lookup(unaryExpr) and returns the cached value, if any. + * If no cached value exists, visits the child, caches its return value and returns it. + * @return let x = lookup(unaryExpr) | + * x != null => x, + * cache(unaryExpr, unaryExpr.expression.accept(this)) + */ + public Set visit(UnaryExpression unaryExpr) { + Set ret = lookup(unaryExpr); + if (ret!=null) return ret; + ret = newSet(); + ret.addAll(unaryExpr.expression().accept(this)); + return cache(unaryExpr,ret); + } + + /** + * Calls lookup(comprehension) and returns the cached value, if any. + * If no cached value exists, visits each child, caches the + * union of the children's return values and returns it. + * @return let x = lookup(comprehension) | + * x != null => x, + * cache(comprehension, comprehension.declarations.accept(this) + comprehension.formula.accept(this)) + */ + public Set visit(Comprehension comprehension) { + Set ret = lookup(comprehension); + if (ret!=null) return ret; + ret = newSet(); + ret.addAll(comprehension.decls().accept(this)); + ret.addAll(comprehension.formula().accept(this)); + return cache(comprehension, ret); + } + + /** + * Calls lookup(ifExpr) and returns the cached value, if any. + * If no cached value exists, visits each child, caches the + * union of the children's return values and returns it. + * @return let x = lookup(ifExpr) | + * x != null => x, + * cache(ifExpr, ifExpr.condition.accept(this) + ifExpr.thenExpr.accept(this) + ifExpr.elseExpr.accept(this)) + */ + public Set visit(IfExpression ifExpr) { + Set ret = lookup(ifExpr); + if (ret!=null) return ret; + ret = newSet(); + ret.addAll(ifExpr.condition().accept(this)); + ret.addAll(ifExpr.thenExpr().accept(this)); + ret.addAll(ifExpr.elseExpr().accept(this)); + return cache(ifExpr, ret); + } + + /** + * Calls lookup(project) and returns the cached value, if any. If no cached + * value exists, visits each child, caches the union of the children's return + * values and returns it. + * @return let x = lookup(project) | + * x != null => x, + * cache(project, project.expression.accept(this) + project.columns[int].accept(this)) + */ + public Set visit(ProjectExpression project) { + Set ret = lookup(project); + if (ret!=null) return ret; + ret = newSet(); + ret.addAll(project.expression().accept(this)); + for(int i = 0, arity = project.arity(); i < arity; i++) { + ret.addAll(project.column(i).accept(this)); + } + return cache(project,ret); + } + + /** + * Calls lookup(castExpr) and returns the cached value, if any. + * If no cached value exists, visits the child, caches its return value and returns it. + * @return let x = lookup(castExpr) | + * x != null => x, + * cache(intExpr, castExpr.intExpr.accept(this)) + */ + public Set visit(IntToExprCast castExpr) { + Set ret = lookup(castExpr); + if (ret!=null) return ret; + ret = newSet(); + ret.addAll(castExpr.intExpr().accept(this)); + return cache(castExpr,ret); + } + + /** + * Returns Collections.EMPTY_SET + * @return Collections.EMPTY_SET + */ + @SuppressWarnings("unchecked") + public Set visit(IntConstant intConst) { + return Collections.EMPTY_SET; + } + + /** + * Calls lookup(intExpr) and returns the cached value, if any. + * If no cached value exists, visits each child, caches the + * union of the children's return values and returns it. + * @return let x = lookup(intExpr) | + * x != null => x, + * cache(intExpr, intExpr.condition.accept(this) + intExpr.thenExpr.accept(this) + intExpr.elseExpr.accept(this)) + */ + public Set visit(IfIntExpression intExpr) { + Set ret = lookup(intExpr); + if (ret!=null) return ret; + ret = newSet(); + ret.addAll(intExpr.condition().accept(this)); + ret.addAll(intExpr.thenExpr().accept(this)); + ret.addAll(intExpr.elseExpr().accept(this)); + return cache(intExpr, ret); + } + + /** + * Calls lookup(intExpr) and returns the cached value, if any. + * If no cached value exists, visits the child, caches its return value and returns it. + * @return let x = lookup(intExpr) | + * x != null => x, + * cache(intExpr, intExpr.expression.accept(this)) + */ + public Set visit(ExprToIntCast intExpr) { + Set ret = lookup(intExpr); + if (ret!=null) return ret; + ret = newSet(); + ret.addAll(intExpr.expression().accept(this)); + return cache(intExpr,ret); + } + + /** + * Calls lookup(intExpr) and returns the cached value, if any. + * If no cached value exists, visits each child, caches the + * union of the children's return values and returns it. + * @return let x = lookup(intExpr) | + * x != null => x, + * cache(intExpr, intExpr.child(0).accept(this) + .. + intExpr.child(intExpr.size()-1).accept(this)) + */ + public Set visit(NaryIntExpression intExpr) { + Set ret = lookup(intExpr); + if (ret!=null) return ret; + ret = newSet(); + for(IntExpression child : intExpr) { + ret.addAll(child.accept(this)); + } + return cache(intExpr, ret); + } + + /** + * Calls lookup(intExpr) and returns the cached value, if any. + * If no cached value exists, visits each child, caches the + * union of the children's return values and returns it. + * @return let x = lookup(intExpr) | + * x != null => x, + * cache(intExpr, intExpr.left.accept(this) + intExpr.right.accept(this)) + */ + public Set visit(BinaryIntExpression intExpr) { + Set ret = lookup(intExpr); + if (ret!=null) return ret; + ret = newSet(); + ret.addAll(intExpr.left().accept(this)); + ret.addAll(intExpr.right().accept(this)); + return cache(intExpr, ret); + } + + /** + * Calls lookup(intExpr) and returns the cached value, if any. + * If no cached value exists, visits the child, caches its return value and returns it. + * @return let x = lookup(intExpr) | + * x != null => x, + * cache(intExpr, intExpr.expression.accept(this)) + */ + public Set visit(UnaryIntExpression intExpr) { + Set ret = lookup(intExpr); + if (ret!=null) return ret; + ret = newSet(); + ret.addAll(intExpr.intExpr().accept(this)); + return cache(intExpr,ret); + } + + /** + * Calls lookup(intExpr) and returns the cached value, if any. + * If no cached value exists, visits each child, caches the + * union of the children's return values and returns it. + * @return let x = lookup(intExpr) | + * x != null => x, + * cache(intExpr, intExpr.decls.accept(this) + intExpr.intExpr.accept(this)) + */ + public Set visit(SumExpression intExpr) { + Set ret = lookup(intExpr); + if (ret!=null) return ret; + ret = newSet(); + ret.addAll(intExpr.decls().accept(this)); + ret.addAll(intExpr.intExpr().accept(this)); + return cache(intExpr, ret); + } + + /** + * Calls lookup(intComp) and returns the cached value, if any. + * If no cached value exists, visits each child, caches the + * union of the children's return values and returns it. + * @return let x = lookup(intComp) | + * x != null => x, + * cache(intComp, intComp.left.accept(this) + intComp.right.accept(this)) + */ + public Set visit(IntComparisonFormula intComp) { + Set ret = lookup(intComp); + if (ret!=null) return ret; + ret = newSet(); + ret.addAll(intComp.left().accept(this)); + ret.addAll(intComp.right().accept(this)); + return cache(intComp, ret); + } + + /** + * Calls lookup(quantFormula) and returns the cached value, if any. + * If no cached value exists, visits each child, caches the + * union of the children's return values and returns it. + * @return let x = lookup(quantFormula) | + * x != null => x, + * cache(quantFormula, quantFormula.declarations.accept(this) + quantFormula.formula.accept(this)) + */ + public Set visit(QuantifiedFormula quantFormula) { + Set ret = lookup(quantFormula); + if (ret!=null) return ret; + ret = newSet(); + ret.addAll(quantFormula.decls().accept(this)); + ret.addAll(quantFormula.formula().accept(this)); + return cache(quantFormula, ret); + } + + /** + * Calls lookup(formula) and returns the cached value, if any. + * If no cached value exists, visits each child, caches the + * union of the children's return values and returns it. + * @return let x = lookup(formula) | + * x != null => x, + * cache(formula, formula.child(0).accept(this) + .. + formula.child(formula.size()-1).accept(this)) + */ + public Set visit(NaryFormula formula) { + Set ret = lookup(formula); + if (ret!=null) return ret; + ret = newSet(); + for(Formula child : formula) { + ret.addAll(child.accept(this)); + } + return cache(formula, ret); + } + + /** + * Calls lookup(binFormula) and returns the cached value, if any. + * If no cached value exists, visits each child, caches the + * union of the children's return values and returns it. + * @return let x = lookup(binFormula) | + * x != null => x, + * cache(binFormula, binFormula.left.accept(this) + binFormula.right.accept(this)) + */ + public Set visit(BinaryFormula binFormula) { + Set ret = lookup(binFormula); + if (ret!=null) return ret; + ret = newSet(); + ret.addAll(binFormula.left().accept(this)); + ret.addAll(binFormula.right().accept(this)); + return cache(binFormula, ret); + } + + /** + * Calls lookup(not) and returns the cached value, if any. + * If no cached value exists, visits the child, caches its return value and returns it. + * @return let x = lookup(not) | + * x != null => x, + * cache(not, not.formula.accept(this)) + */ + public Set visit(NotFormula not) { + Set ret = lookup(not); + if (ret!=null) return ret; + ret = newSet(); + ret.addAll(not.formula().accept(this)); + return cache(not, ret); + } + + /** + * Returns Collections.EMPTY_SET + * @return Collections.EMPTY_SET + */ + @SuppressWarnings("unchecked") + public Set visit(ConstantFormula constant) { + return Collections.EMPTY_SET; + } + + /** + * Calls lookup(compFormula) and returns the cached value, if any. + * If no cached value exists, visits each child, caches the + * union of the children's return values and returns it. + * @return let x = lookup(compFormula) | + * x != null => x, + * cache(compFormula,compFormula.left.accept(this) + compFormula.right.accept(this)) + */ + public Set visit(ComparisonFormula compFormula) { + Set ret = lookup(compFormula); + if (ret!=null) return ret; + ret = newSet(); + ret.addAll(compFormula.left().accept(this)); + ret.addAll(compFormula.right().accept(this)); + return cache(compFormula, ret); + } + + /** + * Calls lookup(multFormula) and returns the cached value, if any. + * If no cached value exists, visits the child, caches its return value and returns it. + * @return let x = lookup(multFormula) | + * x != null => x, + * cache(multFormula, multFormula.expression.accept(this)) + */ + public Set visit(MultiplicityFormula multFormula) { + Set ret = lookup(multFormula); + if (ret!=null) return ret; + ret = newSet(); + ret.addAll(multFormula.expression().accept(this)); + return cache(multFormula, ret); + } + + /** + * Calls lookup(predicate) and returns the cached value, if any. + * If no cached value exists, visits each child, caches the + * disjunction of the children's return values and returns it. + * @return let x = lookup(predicate) | + * x != null => x, + * cache(predicate, some n: predicate.children | n.accept(this)) + */ + public Set visit(RelationPredicate pred) { + Set ret = lookup(pred); + if (ret!=null) return ret; + ret = newSet(); + ret.addAll(pred.relation().accept(this)); + switch(pred.name()) { + case ACYCLIC : + break; + case FUNCTION : + final RelationPredicate.Function fp = (RelationPredicate.Function) pred; + ret.addAll(fp.domain().accept(this)); + ret.addAll(fp.range().accept(this)); + break; + case TOTAL_ORDERING : + final RelationPredicate.TotalOrdering tp = (RelationPredicate.TotalOrdering) pred; + ret.addAll(tp.ordered().accept(this)); + ret.addAll(tp.first().accept(this)); + ret.addAll(tp.last().accept(this)); + break; + default : + throw new IllegalArgumentException("unknown relation predicate: " + pred.name()); + } + return cache(pred,ret); + } + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/visitor/AbstractDetector.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/visitor/AbstractDetector.java new file mode 100644 index 00000000..1a039cae --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/visitor/AbstractDetector.java @@ -0,0 +1,480 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.ast.visitor; + +import java.util.IdentityHashMap; +import java.util.Map; +import java.util.Set; + +import kodkod.ast.BinaryExpression; +import kodkod.ast.BinaryFormula; +import kodkod.ast.BinaryIntExpression; +import kodkod.ast.ComparisonFormula; +import kodkod.ast.Comprehension; +import kodkod.ast.ConstantExpression; +import kodkod.ast.ConstantFormula; +import kodkod.ast.Decl; +import kodkod.ast.Decls; +import kodkod.ast.ExprToIntCast; +import kodkod.ast.Expression; +import kodkod.ast.Formula; +import kodkod.ast.IfExpression; +import kodkod.ast.IfIntExpression; +import kodkod.ast.IntComparisonFormula; +import kodkod.ast.IntConstant; +import kodkod.ast.IntExpression; +import kodkod.ast.IntToExprCast; +import kodkod.ast.MultiplicityFormula; +import kodkod.ast.NaryExpression; +import kodkod.ast.NaryFormula; +import kodkod.ast.NaryIntExpression; +import kodkod.ast.Node; +import kodkod.ast.NotFormula; +import kodkod.ast.ProjectExpression; +import kodkod.ast.QuantifiedFormula; +import kodkod.ast.Relation; +import kodkod.ast.RelationPredicate; +import kodkod.ast.SumExpression; +import kodkod.ast.UnaryExpression; +import kodkod.ast.UnaryIntExpression; +import kodkod.ast.Variable; + +/** + *

A depth first detector. Subclasses should override the + * methods in which detection is performed to return TRUE. + * For example, a Variable detector could be implemented + * simply by subclassing this implementation and overriding + * the {@link #visit(Variable) } method to return TRUE.

+ * + * @specfield cached: set Node // result of visiting these nodes will be cached + * @specfield cache: Node -> lone Boolean + * @specfield cached in cache.Node + * @author Emina Torlak + */ +public abstract class AbstractDetector implements ReturnVisitor { + protected final Map cache; + protected final Set cached; + + /** + * Constructs a depth first detector which will cache the results + * of visiting the given nodes and re-use them on subsequent visits. + * @ensures this.cached' = cached && no this.cache + */ + protected AbstractDetector(Set cached) { + this.cached = cached; + this.cache = new IdentityHashMap(cached.size()); + } + + /** + * Constructs a depth-first detector which will cache + * the results of visiting the given nodes in the given map, + * and re-use them on subsequent visits. + * @ensures this.cached' = cached && this.cache' = cache + */ + protected AbstractDetector(Set cached, Map cache) { + this.cached = cached; + this.cache = cache; + } + + + /** + * If n has been visited and a value for it cached, + * the cached value is returned. Otherwise null is returned. + * @return this.cache[n] + */ + protected Boolean lookup(Node n) { + return cache.get(n); + } + + /** + * Caches the given value for the specified node, if + * this is a caching visitor, and returns Boolean.valueOf(val). + * @ensures n in this.cached => this.cache' = this.cache ++ n->Boolean.valueOf(val), this.cache' = this.cache + * @return Boolean.valueOf(val) + */ + protected Boolean cache(Node n, boolean val) { + final Boolean ret = Boolean.valueOf(val); + if (cached.contains(n)) + cache.put(n, ret); + return ret; + } + + /** + * Calls lookup(decls) and returns the cached value, if any. + * If no cached value exists, visits each child, caches the + * disjunction of the children's return values and returns it. + * @return let x = lookup(decls) | + * x != null => x, + * cache(decls, some d: decls.declarations | d.accept(this)) + */ + public Boolean visit(Decls decls) { + final Boolean ret = lookup(decls); + if (ret!=null) return ret; + for(Decl d : decls) { + if (visit(d)) + return cache(decls, true); + } + return cache(decls, false); + } + + /** + * Calls lookup(decl) and returns the cached value, if any. + * If no cached value exists, visits each child, caches the + * disjunction of the children's return values and returns it. + * @return let x = lookup(decl) | + * x != null => x, + * cache(decl, decl.variable.accept(this) || decl.expression.accept(this)) + */ + public Boolean visit(Decl decl) { + final Boolean ret = lookup(decl); + return (ret!=null) ? ret : cache(decl, decl.variable().accept(this) || decl.expression().accept(this)); + } + + /** + * Returns FALSE. + * @return FALSE + */ + public Boolean visit(Relation relation) { return Boolean.FALSE; } + + /** + * Returns FALSE. + * @return FALSE + */ + public Boolean visit(Variable variable) { return Boolean.FALSE; } + + /** + * Returns FALSE. + * @return FALSE + */ + public Boolean visit(ConstantExpression expr) { return Boolean.FALSE; } + + /** + * Calls lookup(expr) and returns the cached value, if any. + * If no cached value exists, visits each child, caches the + * disjunction of the children's return values and returns it. + * @return let x = lookup(expr) | + * x != null => x, + * cache(expr, expr.child(0).accept(this) || ... || expr.child(expr.size()-1).accept(this)) + */ + public Boolean visit(NaryExpression expr) { + final Boolean ret = lookup(expr); + if (ret!=null) return ret; + for(Expression child : expr) { + if (child.accept(this)) + return cache(expr, true); + } + return cache(expr, false); + } + + /** + * Calls lookup(binExpr) and returns the cached value, if any. + * If no cached value exists, visits each child, caches the + * disjunction of the children's return values and returns it. + * @return let x = lookup(binExpr) | + * x != null => x, + * cache(binExpr, binExpr.left.accept(this) || binExpr.right.accept(this)) + */ + public Boolean visit(BinaryExpression binExpr) { + final Boolean ret = lookup(binExpr); + return (ret!=null) ? ret : cache(binExpr, binExpr.left().accept(this) || binExpr.right().accept(this)); + } + + /** + * Calls lookup(expr) and returns the cached value, if any. + * If no cached value exists, visits the child, caches its return value and returns it. + * @return let x = lookup(expr) | + * x != null => x, + * cache(expr, expr.expression.accept(this)) + */ + public Boolean visit(UnaryExpression expr) { + final Boolean ret = lookup(expr); + return (ret!=null) ? ret : cache(expr, expr.expression().accept(this)); + } + + /** + * Calls lookup(expr) and returns the cached value, if any. + * If no cached value exists, visits each child, caches the + * disjunction of the children's return values and returns it. + * @return let x = lookup(expr) | + * x != null => x, + * cache(expr, expr.decls.accept(this) || expr.formula.accept(this)) + */ + public Boolean visit(Comprehension expr) { + final Boolean ret = lookup(expr); + return (ret!=null) ? ret : cache(expr, expr.decls().accept(this) || expr.formula().accept(this)); + } + + /** + * Calls lookup(expr) and returns the cached value, if any. + * If no cached value exists, visits each child, caches the + * disjunction of the children's return values and returns it. + * @return let x = lookup(expr) | + * x != null => x, + * cache(expr, expr.condition.accept(this) || expr.thenExpr.accept(this) || expr.elseExpr.accept(this)) + */ + public Boolean visit(IfExpression expr) { + final Boolean ret = lookup(expr); + return (ret!=null) ? ret : cache(expr, expr.condition().accept(this) || expr.thenExpr().accept(this) || expr.elseExpr().accept(this)); + } + + /** + * Calls lookup(project) and returns the cached value, if any. If no cached + * value exists, visits each child, caches the disjunction of the children's return + * values and returns it. + * @return let x = lookup(project) | + * x != null => x, + * cache(project, project.expression.accept(this) || project.columns[int].accept(this)) + */ + public Boolean visit(ProjectExpression project) { + final Boolean ret = lookup(project); + if (ret!=null) return ret; + if (project.expression().accept(this)) + return cache(project, true); + for(int i = 0, arity = project.arity(); i < arity; i++) { + if (project.column(i).accept(this)) + return cache(project, true); + } + return cache(project, false); + } + + /** + * Calls lookup(castExpr) and returns the cached value, if any. + * If no cached value exists, visits the child, caches its return value and returns it. + * @return let x = lookup(castExpr) | + * x != null => x, + * cache(intExpr, castExpr.intExpr.accept(this)) + */ + public Boolean visit(IntToExprCast castExpr) { + final Boolean ret = lookup(castExpr); + return (ret!=null) ? ret : cache(castExpr, castExpr.intExpr().accept(this)); + } + + /** + * Returns FALSE. + * @return FALSE + */ + public Boolean visit(IntConstant intConst) { return Boolean.FALSE; } + + /** + * Calls lookup(intExpr) and returns the cached value, if any. + * If no cached value exists, visits each child, caches the + * disjunction of the children's return values and returns it. + * @return let x = lookup(intExpr) | + * x != null => x, + * cache(intExpr, intExpr.condition.accept(this) || intExpr.thenExpr.accept(this) || intExpr.elseExpr.accept(this)) + */ + public Boolean visit(IfIntExpression intExpr) { + final Boolean ret = lookup(intExpr); + return (ret!=null) ? ret : cache(intExpr, intExpr.condition().accept(this) || intExpr.thenExpr().accept(this) || intExpr.elseExpr().accept(this)); + } + + /** + * Calls lookup(intExpr) and returns the cached value, if any. + * If no cached value exists, visits the child, caches its return value and returns it. + * @return let x = lookup(intExpr) | + * x != null => x, + * cache(intExpr, intExpr.expression.accept(this)) + */ + public Boolean visit(ExprToIntCast intExpr) { + final Boolean ret = lookup(intExpr); + return (ret!=null) ? ret : cache(intExpr, intExpr.expression().accept(this)); + } + + /** + * Calls lookup(intExpr) and returns the cached value, if any. + * If no cached value exists, visits each child, caches the + * disjunction of the children's return values and returns it. + * @return let x = lookup(intExpr) | + * x != null => x, + * cache(intExpr, intExpr.child(0).accept(this) || ... || intExpr.child(intExpr.size()-1).accept(this)) + */ + public Boolean visit(NaryIntExpression intExpr) { + final Boolean ret = lookup(intExpr); + if (ret!=null) return ret; + for(IntExpression child : intExpr) { + if (child.accept(this)) + return cache(intExpr, true); + } + return cache(intExpr, false); + } + + /** + * Calls lookup(intExpr) and returns the cached value, if any. + * If no cached value exists, visits each child, caches the + * disjunction of the children's return values and returns it. + * @return let x = lookup(intExpr) | + * x != null => x, + * cache(intExpr, intExpr.left.accept(this) || intExpr.right.accept(this)) + */ + public Boolean visit(BinaryIntExpression intExpr) { + final Boolean ret = lookup(intExpr); + return (ret!=null) ? ret : cache(intExpr, intExpr.left().accept(this) || intExpr.right().accept(this)); + } + + /** + * Calls lookup(intExpr) and returns the cached value, if any. + * If no cached value exists, visits the child, caches its return value and returns it. + * @return let x = lookup(intExpr) | + * x != null => x, + * cache(intExpr, intExpr.expression.accept(this)) + */ + public Boolean visit(UnaryIntExpression intExpr) { + final Boolean ret = lookup(intExpr); + return (ret!=null) ? ret : cache(intExpr, intExpr.intExpr().accept(this)); + } + + /** + * Calls lookup(intExpr) and returns the cached value, if any. + * If no cached value exists, visits each child, caches the + * disjunction of the children's return values and returns it. + * @return let x = lookup(intExpr) | + * x != null => x, + * cache(intExpr, intExpr.decls.accept(this) || intExpr.intExpr.accept(this)) + */ + public Boolean visit(SumExpression intExpr) { + final Boolean ret = lookup(intExpr); + return (ret!=null) ? ret : cache(intExpr, intExpr.decls().accept(this) || intExpr.intExpr().accept(this)); + } + + /** + * Calls lookup(intComp) and returns the cached value, if any. + * If no cached value exists, visits each child, caches the + * disjunction of the children's return values and returns it. + * @return let x = lookup(intComp) | + * x != null => x, + * cache(intComp, intComp.left.accept(this) || intComp.right.accept(this)) + */ + public Boolean visit(IntComparisonFormula intComp) { + final Boolean ret = lookup(intComp); + return (ret!=null) ? ret : cache(intComp, intComp.left().accept(this) || intComp.right().accept(this)); + } + + /** + * Calls lookup(quantFormula) and returns the cached value, if any. + * If no cached value exists, visits each child, caches the + * disjunction of the children's return values and returns it. + * @return let x = lookup(quantFormula) | + * x != null => x, + * cache(quantFormula, quantFormula.declarations.accept(this) ||quantFormula.formula.accept(this)) + */ + public Boolean visit(QuantifiedFormula quantFormula) { + final Boolean ret = lookup(quantFormula); + return (ret!=null) ? ret : cache(quantFormula, quantFormula.decls().accept(this) || quantFormula.formula().accept(this)); + } + + /** + * Calls lookup(formula) and returns the cached value, if any. + * If no cached value exists, visits each child, caches the + * disjunction of the children's return values and returns it. + * @return let x = lookup(formula) | + * x != null => x, + * cache(formula, formula.child(0).accept(this) || ... || formula.child(formula.size()-1).accept(this)) + */ + public Boolean visit(NaryFormula formula) { + final Boolean ret = lookup(formula); + if (ret!=null) return ret; + for(Formula child : formula) { + if (child.accept(this)) + return cache(formula, true); + } + return cache(formula, false); + } + + /** + * Calls lookup(binFormula) and returns the cached value, if any. + * If no cached value exists, visits each child, caches the + * disjunction of the children's return values and returns it. + * @return let x = lookup(binFormula) | + * x != null => x, + * cache(binFormula, binFormula.left.accept(this) || binFormula.right.accept(this)) + */ + public Boolean visit(BinaryFormula binFormula) { + final Boolean ret = lookup(binFormula); + return (ret!=null) ? ret : cache(binFormula, binFormula.left().accept(this) || binFormula.right().accept(this)); + } + + /** + * Calls lookup(not) and returns the cached value, if any. + * If no cached value exists, visits the child, caches its return value and returns it. + * @return let x = lookup(not) | + * x != null => x, + * cache(not, not.formula.accept(this)) + */ + public Boolean visit(NotFormula not) { + final Boolean ret = lookup(not); + return (ret!=null) ? ret : cache(not, not.formula().accept(this)); + } + + /** + * Returns FALSE. + * @return FALSE + */ + public Boolean visit(ConstantFormula constant) { return Boolean.FALSE; } + + /** + * Calls lookup(exprComp) and returns the cached value, if any. + * If no cached value exists, visits each child, caches the + * disjunction of the children's return values and returns it. + * @return let x = lookup(exprComp) | + * x != null => x, + * cache(exprComp,exprComp.left.accept(this) || exprComp.right.accept(this)) + */ + public Boolean visit(ComparisonFormula exprComp) { + final Boolean ret = lookup(exprComp); + return (ret!=null) ? ret : cache(exprComp, exprComp.left().accept(this) || exprComp.right().accept(this)); + } + + /** + * Calls lookup(multFormula) and returns the cached value, if any. + * If no cached value exists, visits the child, caches its return value and returns it. + * @return let x = lookup(multFormula) | + * x != null => x, + * cache(multFormula, multFormula.expression.accept(this)) + */ + public Boolean visit(MultiplicityFormula multFormula) { + final Boolean ret = lookup(multFormula); + return (ret!=null) ? ret : cache(multFormula, multFormula.expression().accept(this)); + } + + /** + * Calls lookup(predicate) and returns the cached value, if any. + * If no cached value exists, visits each child, caches the + * disjunction of the children's return values and returns it. + * @return let x = lookup(predicate) | + * x != null => x, + * cache(predicate, some n: predicate.children | n.accept(this)) + */ + public Boolean visit(RelationPredicate predicate) { + final Boolean ret = lookup(predicate); + if (ret!=null) return ret; + if (predicate.relation().accept(this)) + return cache(predicate, true); + if (predicate.name()==RelationPredicate.Name.FUNCTION) { + final RelationPredicate.Function fp = (RelationPredicate.Function) predicate; + return cache(predicate, fp.domain().accept(this) || fp.range().accept(this)); + } else if (predicate.name()==RelationPredicate.Name.TOTAL_ORDERING) { + final RelationPredicate.TotalOrdering tp = (RelationPredicate.TotalOrdering) predicate; + return cache(predicate, tp.ordered().accept(this) || tp.first().accept(this) || tp.last().accept(this)); + } + return cache(predicate, false); + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/visitor/AbstractReplacer.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/visitor/AbstractReplacer.java new file mode 100644 index 00000000..a2b1aba9 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/visitor/AbstractReplacer.java @@ -0,0 +1,649 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.ast.visitor; + +import java.util.IdentityHashMap; +import java.util.Map; +import java.util.Set; + +import kodkod.ast.BinaryExpression; +import kodkod.ast.BinaryFormula; +import kodkod.ast.BinaryIntExpression; +import kodkod.ast.ComparisonFormula; +import kodkod.ast.Comprehension; +import kodkod.ast.ConstantExpression; +import kodkod.ast.ConstantFormula; +import kodkod.ast.Decl; +import kodkod.ast.Decls; +import kodkod.ast.ExprToIntCast; +import kodkod.ast.Expression; +import kodkod.ast.Formula; +import kodkod.ast.IfExpression; +import kodkod.ast.IfIntExpression; +import kodkod.ast.IntComparisonFormula; +import kodkod.ast.IntConstant; +import kodkod.ast.IntExpression; +import kodkod.ast.IntToExprCast; +import kodkod.ast.MultiplicityFormula; +import kodkod.ast.NaryExpression; +import kodkod.ast.NaryFormula; +import kodkod.ast.NaryIntExpression; +import kodkod.ast.Node; +import kodkod.ast.NotFormula; +import kodkod.ast.ProjectExpression; +import kodkod.ast.QuantifiedFormula; +import kodkod.ast.Relation; +import kodkod.ast.RelationPredicate; +import kodkod.ast.SumExpression; +import kodkod.ast.UnaryExpression; +import kodkod.ast.UnaryIntExpression; +import kodkod.ast.Variable; +import kodkod.ast.operator.Multiplicity; + +/** + * A depth first replacer. The default implementation + * returns the tree to which it is applied. Reference + * equality is used to determine if two nodes are the same. + * + * @specfield cached: set Node // result of visiting these nodes will be cached + * @specfield cache: Node ->lone Node + * @invariant cached in cache.Node + * @author Emina Torlak + */ +public abstract class AbstractReplacer implements ReturnVisitor { + protected final Map cache; + protected final Set cached; + + /** + * Constructs a depth-first replaces which will cache + * the results of visiting the given nodes and re-use them + * on subsequent visits. + * @ensures this.cached' = cached && no this.cache' + */ + protected AbstractReplacer(Set cached) { + this.cached = cached; + this.cache = new IdentityHashMap(cached.size()); + } + + /** + * Constructs a depth-first replacer which will cache + * the results of visiting the given nodes in the given map, + * and re-use them on subsequent visits. + * @ensures this.cached' = cached && this.cache' = cache + */ + protected AbstractReplacer(Set cached, Map cache) { + this.cached = cached; + this.cache = cache; + } + + + /** + * If the given node has already been visited and its replacement + * cached, the cached value is returned. Otherwise, null is returned. + * @return this.cache[node] + */ + @SuppressWarnings("unchecked") + protected N lookup(N node) { + return (N) cache.get(node); + } + + /** + * Caches the given replacement for the specified node, if this is + * a caching visitor. Otherwise does nothing. The method returns + * the replacement node. + * @ensures node in this.cached => this.cache' = this.cache ++ node->replacement, + * this.cache' = this.cache + * @return replacement + */ + protected N cache(N node, N replacement) { + if (cached.contains(node)) { + cache.put(node, replacement); + } + return replacement; + } + + /** + * Calls lookup(decls) and returns the cached value, if any. + * If a replacement has not been cached, visits each of the children's + * variable and expression. If nothing changes, the argument is cached and + * returned, otherwise a replacement Decls object is cached and returned. + * @return { d: Decls | d.size = decls.size && + * all i: [0..d.size) | d.declarations[i] = decls.declarations[i].accept(this) } + */ + public Decls visit(Decls decls) { + Decls ret = lookup(decls); + if (ret!=null) return ret; + + Decls visitedDecls = null; + boolean allSame = true; + for(Decl decl : decls) { + Decls newDecl = visit(decl); + if (newDecl != decl) + allSame = false; + visitedDecls = (visitedDecls==null) ? newDecl : visitedDecls.and(newDecl); + } + ret = allSame ? decls : visitedDecls; + return cache(decls, ret); + } + + /** + * Calls lookup(decl) and returns the cached value, if any. + * If a replacement has not been cached, visits the declaration's + * variable and expression. If nothing changes, the argument is cached and + * returned, otherwise a replacement Decl object is cached and returned. + * @return { d: Declaration | d.variable = declaration.variable.accept(this) && + * d.multiplicity = decl.multiplicity && + * d.expression = declaration.expression.accept(this) + */ + public Decl visit(Decl decl) { + Decl ret = lookup(decl); + if (ret!=null) return ret; + + final Variable variable = (Variable) decl.variable().accept(this); + final Expression expression = decl.expression().accept(this); + ret = (variable==decl.variable() && expression==decl.expression()) ? + decl : variable.declare(decl.multiplicity(), expression); + return cache(decl,ret); + } + + /** + * Calls lookup(relation) and returns the cached value, if any. + * If a replacement has not been cached, the relation is cached and + * returned. + * @return relation + */ + public Expression visit(Relation relation) { + final Expression ret = lookup(relation); + return ret==null ? cache(relation,relation) : ret; + } + + /** + * Calls lookup(variable) and returns the cached value, if any. + * If a replacement has not been cached, the variable is cached and + * returned. + * @return variable + */ + public Expression visit(Variable variable) { + final Expression ret = lookup(variable); + return ret==null ? cache(variable,variable) : variable; + } + + /** + * Calls lookup(constExpr) and returns the cached value, if any. + * If a replacement has not been cached, the constExpr is cached and + * returned. + * @return constExpr + */ + public Expression visit(ConstantExpression constExpr) { + final Expression ret = lookup(constExpr); + return ret==null ? cache(constExpr,constExpr) : constExpr; + } + + /** + * Calls lookup(expr) and returns the cached value, if any. + * If a replacement has not been cached, visits the expr's + * children. If nothing changes, the argument is cached and + * returned, otherwise a replacement expr is cached and returned. + * @return { e: Expression | e.op = expr.op && #e.children = #expr.children && all i: [0..expr.children) | e.child(i) = expr.child(i).accept(this) } + */ + public Expression visit(NaryExpression expr) { + Expression ret = lookup(expr); + if (ret!=null) return ret; + + final Expression[] visited = new Expression[expr.size()]; + boolean allSame = true; + for(int i = 0 ; i < visited.length; i++) { + final Expression child = expr.child(i); + visited[i] = child.accept(this); + allSame = allSame && visited[i]==child; + } + + ret = allSame ? expr : Expression.compose(expr.op(), visited); + return cache(expr,ret); + } + + /** + * Calls lookup(binExpr) and returns the cached value, if any. + * If a replacement has not been cached, visits the expression's + * children. If nothing changes, the argument is cached and + * returned, otherwise a replacement expression is cached and returned. + * @return { b: BinaryExpression | b.left = binExpr.left.accept(this) && + * b.right = binExpr.right.accept(this) && b.op = binExpr.op } + */ + public Expression visit(BinaryExpression binExpr) { + Expression ret = lookup(binExpr); + if (ret!=null) return ret; + + final Expression left = binExpr.left().accept(this); + final Expression right = binExpr.right().accept(this); + ret = (left==binExpr.left() && right==binExpr.right()) ? + binExpr : left.compose(binExpr.op(), right); + return cache(binExpr,ret); + } + + /** + * Calls lookup(unaryExpr) and returns the cached value, if any. + * If a replacement has not been cached, visits the expression's + * child. If nothing changes, the argument is cached and + * returned, otherwise a replacement expression is cached and returned. + * @return { u: UnaryExpression | u.left = unaryExpr.expression.accept(this) && u.op = unaryExpr.op } + */ + public Expression visit(UnaryExpression unaryExpr) { + Expression ret = lookup(unaryExpr); + if (ret!=null) return ret; + + final Expression child = unaryExpr.expression().accept(this); + ret = (child==unaryExpr.expression()) ? + unaryExpr : child.apply(unaryExpr.op()); + return cache(unaryExpr,ret); + } + + /** + * Calls lookup(comprehension) and returns the cached value, if any. + * If a replacement has not been cached, visits the expression's + * children. If nothing changes, the argument is cached and + * returned, otherwise a replacement expression is cached and returned. + * @return { c: Comprehension | c.declarations = comprehension.declarations.accept(this) && + * c.formula = comprehension.formula.accept(this) } + */ + public Expression visit(Comprehension comprehension) { + Expression ret = lookup(comprehension); + if (ret!=null) return ret; + + final Decls decls = (Decls)comprehension.decls().accept(this); + final Formula formula = comprehension.formula().accept(this); + ret = (decls==comprehension.decls() && formula==comprehension.formula()) ? + comprehension : formula.comprehension(decls); + return cache(comprehension,ret); + } + + + /** + * Calls lookup(ifExpr) and returns the cached value, if any. + * If a replacement has not been cached, visits the expression's + * children. If nothing changes, the argument is cached and + * returned, otherwise a replacement expression is cached and returned. + * @return { i: IfExpression | i.condition = ifExpr.condition.accept(this) && + * i.thenExpr = ifExpr.thenExpr.accept(this) && + * i.elseExpr = ifExpr.elseExpr.accept(this) } + */ + public Expression visit(IfExpression ifExpr) { + Expression ret = lookup(ifExpr); + if (ret!=null) return ret; + + final Formula condition = ifExpr.condition().accept(this); + final Expression thenExpr = ifExpr.thenExpr().accept(this); + final Expression elseExpr = ifExpr.elseExpr().accept(this); + ret = (condition==ifExpr.condition() && thenExpr==ifExpr.thenExpr() && + elseExpr==ifExpr.elseExpr()) ? + ifExpr : condition.thenElse(thenExpr, elseExpr); + return cache(ifExpr,ret); + } + + /** + * Calls lookup(decls) and returns the cached value, if any. + * If a replacement has not been cached, visits each of the children's + * variable and expression. If nothing changes, the argument is cached and + * returned, otherwise a replacement Decls object is cached and returned. + * @return { d: Decls | d.size = decls.size && + * all i: [0..d.size) | d.declarations[i] = decls.declarations[i].accept(this) } + */ + public Expression visit(ProjectExpression project) { + Expression ret = lookup(project); + if (ret!=null) return ret; + + final Expression expr = project.expression().accept(this); + final IntExpression[] cols = new IntExpression[project.arity()]; + boolean allSame = expr==project.expression(); + for(int i = 0, arity = project.arity(); i < arity; i++) { + cols[i] = project.column(i).accept(this); + allSame = allSame && (cols[i]==project.column(i)); + } + ret = allSame ? project : expr.project(cols); + return cache(project, ret); + } + /** + * Calls lookup(castExpr) and returns the cached value, if any. If a replacement + * has not been cached, visits the expression's child. If nothing changes, the argument + * is cached and returned, otherwise a replacement expression is cached and returned. + * @return { e: Expression | e = castExpr.intExpr.accept(this).toExpression() } + */ + public Expression visit(IntToExprCast castExpr) { + Expression ret = lookup(castExpr); + if (ret!=null) return ret; + + final IntExpression intExpr = castExpr.intExpr().accept(this); + ret = (intExpr==castExpr.intExpr()) ? castExpr : intExpr.cast(castExpr.op()); + return cache(castExpr, ret); + } + + /** + * Calls lookup(intconst) and returns the cached value, if any. + * If a replacement has not been cached, the constant is cached and returned. + * @return intconst + */ + public IntExpression visit(IntConstant intconst) { + IntExpression ret = lookup(intconst); + return ret==null ? cache(intconst, intconst) : intconst; + } + + /** + * Calls lookup(intExpr) and returns the cached value, if any. + * If a replacement has not been cached, visits the expression's + * children. If nothing changes, the argument is cached and + * returned, otherwise a replacement expression is cached and returned. + * @return { i: IfIntExpression | i.condition = intExpr.condition.accept(this) && + * i.thenExpr = intExpr.thenExpr.accept(this) && + * i.elseExpr = intExpr.elseExpr.accept(this) } + */ + public IntExpression visit(IfIntExpression intExpr) { + IntExpression ret = lookup(intExpr); + if (ret!=null) return ret; + + final Formula condition = intExpr.condition().accept(this); + final IntExpression thenExpr = intExpr.thenExpr().accept(this); + final IntExpression elseExpr = intExpr.elseExpr().accept(this); + ret = (condition==intExpr.condition() && thenExpr==intExpr.thenExpr() && + elseExpr==intExpr.elseExpr()) ? + intExpr : condition.thenElse(thenExpr, elseExpr); + return cache(intExpr,ret); + } + + /** + * Calls lookup(intExpr) and returns the cached value, if any. + * If a replacement has not been cached, visits the expression's + * child. If nothing changes, the argument is cached and + * returned, otherwise a replacement expression is cached and returned. + * @return { i: ExprToIntCast | i.expression = intExpr.expression.accept(this) && i.op = intExpr.op} + */ + public IntExpression visit(ExprToIntCast intExpr) { + IntExpression ret = lookup(intExpr); + if (ret!=null) return ret; + + final Expression expr = intExpr.expression().accept(this); + ret = expr==intExpr.expression() ? intExpr : expr.apply(intExpr.op()); + return cache(intExpr, ret); + } + + /** + * Calls lookup(intExpr) and returns the cached value, if any. + * If a replacement has not been cached, visits the intExpr's + * children. If nothing changes, the argument is cached and + * returned, otherwise a replacement intExpr is cached and returned. + * @return { e: IntExpression | e.op = intExpr.op && #e.children = #intExpr.children && all i: [0..intExpr.children) | e.child(i) = intExpr.child(i).accept(this) } + */ + public IntExpression visit(NaryIntExpression intExpr) { + IntExpression ret = lookup(intExpr); + if (ret!=null) return ret; + + final IntExpression[] visited = new IntExpression[intExpr.size()]; + boolean allSame = true; + for(int i = 0 ; i < visited.length; i++) { + final IntExpression child = intExpr.child(i); + visited[i] = child.accept(this); + allSame = allSame && visited[i]==child; + } + + ret = allSame ? intExpr : IntExpression.compose(intExpr.op(), visited); + return cache(intExpr,ret); + } + /** + * Calls lookup(intExpr) and returns the cached value, if any. + * If a replacement has not been cached, visits the expression's + * children. If nothing changes, the argument is cached and + * returned, otherwise a replacement expression is cached and returned. + * @return { c: IntExpression | [[c]] = intExpr.left.accept(this) op intExpr.right.accept(this) } + */ + public IntExpression visit(BinaryIntExpression intExpr) { + IntExpression ret = lookup(intExpr); + if (ret!=null) return ret; + + final IntExpression left = intExpr.left().accept(this); + final IntExpression right = intExpr.right().accept(this); + ret = (left==intExpr.left() && right==intExpr.right()) ? + intExpr : left.compose(intExpr.op(), right); + return cache(intExpr,ret); + } + + /** + * Calls lookup(intExpr) and returns the cached value, if any. + * If a replacement has not been cached, visits the expression's + * child. If nothing changes, the argument is cached and + * returned, otherwise a replacement expression is cached and returned. + * @return { u: UnaryIntExpression | u.expression = intExpr.expression.accept(this) && u.op = intExpr.op } + */ + public IntExpression visit(UnaryIntExpression intExpr) { + IntExpression ret = lookup(intExpr); + if (ret!=null) return ret; + + final IntExpression child = intExpr.intExpr().accept(this); + ret = (child==intExpr.intExpr()) ? intExpr : child.apply(intExpr.op()); + return cache(intExpr,ret); + } + + /** + * Calls lookup(intExpr) and returns the cached value, if any. + * If a replacement has not been cached, visits the expression's + * children. If nothing changes, the argument is cached and + * returned, otherwise a replacement expression is cached and returned. + * @return { c: IntExpression | [[c]] = sum intExpr.decls.accept(this) | intExpr.intExpr.accept(this) } + */ + public IntExpression visit(SumExpression intExpr) { + IntExpression ret = lookup(intExpr); + if (ret!=null) return ret; + + final Decls decls = intExpr.decls().accept(this); + final IntExpression expr = intExpr.intExpr().accept(this); + ret = (decls==intExpr.decls() && expr==intExpr.intExpr()) ? + intExpr : expr.sum(decls); + return cache(intExpr,ret); + } + + /** + * Calls lookup(intComp) and returns the cached value, if any. + * If a replacement has not been cached, visits the formula's + * children. If nothing changes, the argument is cached and + * returned, otherwise a replacement formula is cached and returned. + * @return { c: Formula | [[c]] = intComp.left.accept(this) op intComp.right.accept(this) } + */ + public Formula visit(IntComparisonFormula intComp) { + Formula ret = lookup(intComp); + if (ret!=null) return ret; + + final IntExpression left = intComp.left().accept(this); + final IntExpression right = intComp.right().accept(this); + ret = (left==intComp.left() && right==intComp.right()) ? + intComp : left.compare(intComp.op(), right); + return cache(intComp,ret); + } + + /** + * Calls lookup(constant) and returns the cached value, if any. + * If a replacement has not been cached, the constant is cached and + * returned. + * @return constant + */ + public Formula visit(ConstantFormula constant) { + final Formula ret = lookup(constant); + return ret==null ? cache(constant,constant) : constant; + } + + /** + * Calls lookup(quantFormula) and returns the cached value, if any. + * If a replacement has not been cached, visits the formula's + * children. If nothing changes, the argument is cached and + * returned, otherwise a replacement formula is cached and returned. + * @return { q: QuantifiedFormula | q.declarations = quantFormula.declarations.accept(this) && + * q.formula = quantFormula.formula.accept(this) } + */ + public Formula visit(QuantifiedFormula quantFormula) { + Formula ret = lookup(quantFormula); + if (ret!=null) return ret; + + final Decls decls = (Decls)quantFormula.decls().accept(this); + final Formula formula = quantFormula.formula().accept(this); + ret = (decls==quantFormula.decls() && formula==quantFormula.formula()) ? + quantFormula : formula.quantify(quantFormula.quantifier(), decls); + return cache(quantFormula,ret); + } + + /** + * Calls lookup(formula) and returns the cached value, if any. + * If a replacement has not been cached, visits the formula's + * children. If nothing changes, the argument is cached and + * returned, otherwise a replacement formula is cached and returned. + * @return { e: Expression | e.op = formula.op && #e.children = #formula.children && all i: [0..formula.children) | e.child(i) = formula.child(i).accept(this) } + */ + public Formula visit(NaryFormula formula) { + Formula ret = lookup(formula); + if (ret!=null) return ret; + + final Formula[] visited = new Formula[formula.size()]; + boolean allSame = true; + for(int i = 0 ; i < visited.length; i++) { + final Formula child = formula.child(i); + visited[i] = child.accept(this); + allSame = allSame && visited[i]==child; + } + + ret = allSame ? formula : Formula.compose(formula.op(), visited); + return cache(formula,ret); + } + + /** + * Calls lookup(binFormula) and returns the cached value, if any. + * If a replacement has not been cached, visits the formula's + * children. If nothing changes, the argument is cached and + * returned, otherwise a replacement formula is cached and returned. + * @return { b: BinaryFormula | b.left = binExpr.left.accept(this) && + * b.right = binExpr.right.accept(this) && b.op = binExpr.op } + */ + public Formula visit(BinaryFormula binFormula) { + Formula ret = lookup(binFormula); + if (ret!=null) return ret; + + final Formula left = binFormula.left().accept(this); + final Formula right = binFormula.right().accept(this); + ret = (left==binFormula.left() && right==binFormula.right()) ? + binFormula : left.compose(binFormula.op(), right); + return cache(binFormula,ret); + } + + /** + * Calls lookup(binFormula) and returns the cached value, if any. + * If a replacement has not been cached, visits the formula's + * child. If nothing changes, the argument is cached and + * returned, otherwise a replacement formula is cached and returned. + * @return { n: NotFormula | n.child = not.child.accept(this) } + */ + public Formula visit(NotFormula not) { + Formula ret = lookup(not); + if (ret!=null) return ret; + + final Formula child = not.formula().accept(this); + ret = (child==not.formula()) ? not : child.not(); + return cache(not,ret); + } + + /** + * Calls lookup(compFormula) and returns the cached value, if any. + * If a replacement has not been cached, visits the formula's + * children. If nothing changes, the argument is cached and + * returned, otherwise a replacement formula is cached and returned. + * @return { c: ComparisonFormula | c.left = compFormula.left.accept(this) && + * c.right = compFormula.right.accept(this) && + * c.op = compFormula.op } + */ + public Formula visit(ComparisonFormula compFormula) { + Formula ret = lookup(compFormula); + if (ret!=null) return ret; + + final Expression left = compFormula.left().accept(this); + final Expression right = compFormula.right().accept(this); + ret = (left==compFormula.left() && right==compFormula.right()) ? + compFormula : left.compare(compFormula.op(), right); + return cache(compFormula,ret); + } + + /** + * Calls lookup(multFormula) and returns the cached value, if any. + * If a replacement has not been cached, visits the formula's + * children. If nothing changes, the argument is cached and + * returned, otherwise a replacement formula is cached and returned. + * @return { m: MultiplicityFormula | m.multiplicity = multFormula.multiplicity && + * m.expression = multFormula.expression.accept(this) } + */ + public Formula visit(MultiplicityFormula multFormula) { + Formula ret = lookup(multFormula); + if (ret!=null) return ret; + + final Expression expression = multFormula.expression().accept(this); + ret = (expression==multFormula.expression()) ? + multFormula : expression.apply(multFormula.multiplicity()); + return cache(multFormula,ret); + } + + /** + * Calls lookup(pred) and returns the cached value, if any. + * If a replacement has not been cached, visits the formula's + * children. If nothing changes, the argument is cached and + * returned, otherwise a replacement formula is cached and returned. + * @return { p: RelationPredicate | p.name = pred.name && p.relation = pred.relation.accept(this) && + * p.name = FUNCTION => p.targetMult = pred.targetMult && + * p.domain = pred.domain.accept(this) && + * p.range = pred.range.accept(this), + * p.name = TOTAL_ORDERING => p.ordered = pred.ordered.accept(this) && + * p.first = pred.first.accept(this) && + * p.last = pred.last.accept(this) } + */ + public Formula visit(RelationPredicate pred) { + Formula ret = lookup(pred); + if (ret!=null) return ret; + + final Relation r = (Relation)pred.relation().accept(this); + switch(pred.name()) { + case ACYCLIC : + ret = (r==pred.relation()) ? pred : r.acyclic(); + break; + case FUNCTION : + final RelationPredicate.Function fp = (RelationPredicate.Function) pred; + final Expression domain = fp.domain().accept(this); + final Expression range = fp.range().accept(this); + ret = (r==fp.relation() && domain==fp.domain() && range==fp.range()) ? + fp : + (fp.targetMult()==Multiplicity.ONE ? r.function(domain, range) : r.partialFunction(domain,range)); + break; + case TOTAL_ORDERING : + final RelationPredicate.TotalOrdering tp = (RelationPredicate.TotalOrdering) pred; + final Relation ordered = (Relation) tp.ordered().accept(this); + final Relation first = (Relation)tp.first().accept(this); + final Relation last = (Relation)tp.last().accept(this); + ret = (r==tp.relation() && ordered==tp.ordered() && first==tp.first() && last==tp.last()) ? + tp : r.totalOrder(ordered, first, last); + break; + default : + throw new IllegalArgumentException("unknown relation predicate: " + pred.name()); + } + return cache(pred,ret); + } + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/visitor/AbstractVoidVisitor.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/visitor/AbstractVoidVisitor.java new file mode 100644 index 00000000..95be3e97 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/visitor/AbstractVoidVisitor.java @@ -0,0 +1,364 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.ast.visitor; + +import kodkod.ast.BinaryExpression; +import kodkod.ast.BinaryFormula; +import kodkod.ast.BinaryIntExpression; +import kodkod.ast.ComparisonFormula; +import kodkod.ast.Comprehension; +import kodkod.ast.ConstantExpression; +import kodkod.ast.ConstantFormula; +import kodkod.ast.Decl; +import kodkod.ast.Decls; +import kodkod.ast.ExprToIntCast; +import kodkod.ast.Expression; +import kodkod.ast.Formula; +import kodkod.ast.IfExpression; +import kodkod.ast.IfIntExpression; +import kodkod.ast.IntComparisonFormula; +import kodkod.ast.IntConstant; +import kodkod.ast.IntExpression; +import kodkod.ast.IntToExprCast; +import kodkod.ast.MultiplicityFormula; +import kodkod.ast.NaryExpression; +import kodkod.ast.NaryFormula; +import kodkod.ast.NaryIntExpression; +import kodkod.ast.Node; +import kodkod.ast.NotFormula; +import kodkod.ast.ProjectExpression; +import kodkod.ast.QuantifiedFormula; +import kodkod.ast.Relation; +import kodkod.ast.RelationPredicate; +import kodkod.ast.SumExpression; +import kodkod.ast.UnaryExpression; +import kodkod.ast.UnaryIntExpression; +import kodkod.ast.Variable; + +/** + * Implements a depth first traversal of the kodkod AST. + * + * @author Emina Torlak + */ +public abstract class AbstractVoidVisitor implements VoidVisitor { + + protected AbstractVoidVisitor() {} + + /** + * Returns true if this node has already been visited. + * Otherwise returns false. + * @return true if this node has already been visited. + */ + protected abstract boolean visited(Node n) ; + + /** + * Visits all the children of the given declarations node if + * this.visited(decls) returns false. Otherwise does nothing. + * @ensures all d: declarations.declarations | d.variable.accept(this) && d.expression.accept(this) + */ + public void visit(Decls decls) { + if (visited(decls)) return; + for (Decl decl : decls) { + decl.accept(this); + } + } + + /** + * Visits the variable and expression of this decl if + * this.visited(decl) returns false. Otherwise does nothing. + * @ensures decl.variable.accept(this) && decl.expression.accept(this) + */ + public void visit(Decl decl) { + if (visited(decl)) return; + decl.variable().accept(this); + decl.expression().accept(this); + } + + /** + * Does nothing. + */ + public void visit(Relation relation) {} + + /** + * Does nothing. + */ + public void visit(Variable variable) {} + + /** + * Does nothing. + */ + public void visit(ConstantExpression constExpr) {} + + /** + * Visits the children if this.visited(expr) returns false. Otherwise does nothing. + * @ensures all i: [0..#expr.children) | expr.child(i).accept(this) + */ + public void visit(NaryExpression expr) { + if (visited(expr)) return; + for(Expression child : expr) { + child.accept(this); + } + } + + /** + * Visits the left and right subexpressions if + * this.visited(binExpr) returns false. Otherwise does nothing. + * @ensures binExpr.left.accept(this) && binExpr.right.accept(this) + */ + public void visit(BinaryExpression binExpr) { + if (visited(binExpr)) return; + binExpr.left().accept(this); + binExpr.right().accept(this); + } + + /** + * Visits the subexpression if + * this.visited(unaryExpr) returns false. Otherwise does nothing. + * @ensures unaryExpr.expression.accept(this) + */ + public void visit(UnaryExpression unaryExpr) { + if (visited(unaryExpr)) return; + unaryExpr.expression().accept(this); + } + + /** + * Visits the declarations and the formula if + * this.visited(comprehension) returns false. Otherwise does nothing. + * @ensures comprehension.declarations.accept(this) && comprehension.formula.accept(this) + */ + public void visit(Comprehension comprehension) { + if (visited(comprehension)) return; + comprehension.decls().accept(this); + comprehension.formula().accept(this); + } + + /** + * Visits the if-condition, the then-expression, and the else-expression if + * this.visited(ifExpr) returns false. Otherwise does nothing. + * @ensures ifExpr.condition.accept(this) && ifExpr.thenExpr.accept(this) && + * ifExpr.elseExpr.accept(this) + */ + public void visit(IfExpression ifExpr) { + if (visited(ifExpr)) return; + ifExpr.condition().accept(this); + ifExpr.thenExpr().accept(this); + ifExpr.elseExpr().accept(this); + } + + /** + * Visits project.expression and project.columns if this.visited(project) returns false. + * Otherwise does nothing. + * @ensures project.expression.accept(this) && all i: project.arity | project.columns[i].accept(this) + */ + public void visit(ProjectExpression project) { + if (visited(project)) return; + project.expression().accept(this); + for(int i = 0, arity = project.arity(); i < arity; i++) { + project.column(i).accept(this); + } + } + + /** + * Visits castExpr.intExpr if + * this.visited(castExpr) returns false. Otherwise does nothing. + * @ensures castExpr.expression.accept(this) + */ + public void visit(IntToExprCast castExpr) { + if (visited(castExpr)) return; + castExpr.intExpr().accept(this); + } + + /** + * Does nothing. + */ + public void visit(IntConstant intConst) {} + + /** + * Visits the if-condition, the then-expression, and the else-expression if + * this.visited(intExpr) returns false. Otherwise does nothing. + * @ensures intExpr.condition.accept(this) && intExpr.thenExpr.accept(this) && + * intExpr.elseExpr.accept(this) + */ + public void visit(IfIntExpression intExpr) { + if (visited(intExpr)) return; + intExpr.condition().accept(this); + intExpr.thenExpr().accept(this); + intExpr.elseExpr().accept(this); + } + + /** + * Visits intExpr.expression if + * this.visited(intExpr) returns false. Otherwise does nothing. + * @ensures intExpr.expression.accept(this) + */ + public void visit(ExprToIntCast intExpr) { + if (visited(intExpr)) return; + intExpr.expression().accept(this); + } + + /** + * Visits the children if this.visited(intExpr) returns false. Otherwise does nothing. + * @ensures all i: [0..#intExpr.children) | intExpr.child(i).accept(this) + */ + public void visit(NaryIntExpression intExpr) { + if (visited(intExpr)) return; + for(IntExpression child : intExpr) { + child.accept(this); + } + } + + /** + * Visits the children of the given integer expression if + * this.visited(intExpr) returns false. Otherwise does nothing. + * @ensures intExpr.left.accept(this) && intExpr.right.accept(this) + */ + public void visit(BinaryIntExpression intExpr) { + if (visited(intExpr)) return; + intExpr.left().accept(this); + intExpr.right().accept(this); + } + + /** + * Visits the subexpression if + * this.visited(intExpr) returns false. Otherwise does nothing. + * @ensures unaryExpr.expression.accept(this) + */ + public void visit(UnaryIntExpression intExpr) { + if (visited(intExpr)) return; + intExpr.intExpr().accept(this); + } + + /** + * Visits the children of the given sum expression if + * this.visited(intExpr) returns false. Otherwise does nothing. + * @ensures intExpr.decls.accept(this) && intExpr.intExpr.accept(this) + */ + public void visit(SumExpression intExpr) { + if (visited(intExpr)) return; + intExpr.decls().accept(this); + intExpr.intExpr().accept(this); + } + + /** + * Visits the children of the given integer comparison formula if + * this.visited(intComp) returns false. Otherwise does nothing. + * @ensures intComp.left.accept(this) && intComp.right.accept(this) + */ + public void visit(IntComparisonFormula intComp) { + if (visited(intComp)) return; + intComp.left().accept(this); + intComp.right().accept(this); + } + + /** + * Visits the declarations and the formula if + * this.visited(quantFormula) returns false. Otherwise does nothing. + * @ensures quantFormula.declarations.accept(this) && quantFormula.formula.accept(this) + */ + public void visit(QuantifiedFormula quantFormula) { + if (visited(quantFormula)) return; + quantFormula.decls().accept(this); + quantFormula.formula().accept(this); + } + + /** + * Visits the children if this.visited(formula) returns false. Otherwise does nothing. + * @ensures all i: [0..#formula.children) | formula.child(i).accept(this) + */ + public void visit(NaryFormula formula) { + if (visited(formula)) return; + for(Formula child : formula) { + child.accept(this); + } + } + + /** + * Visits the left and right children if + * this.visited(binFormula) returns false. Otherwise does nothing. + * @ensures binFormula.left.accept(this) && binFormula.right.accept(this) + */ + public void visit(BinaryFormula binFormula) { + if (visited(binFormula)) return; + binFormula.left().accept(this); + binFormula.right().accept(this); + } + + /** + * Visits the subformula if + * this.visited(not) returns false. Otherwise does nothing. + * @ensures not.formula.accept(this) + */ + public void visit(NotFormula not) { + if (visited(not)) return; + not.formula().accept(this); + } + + /** + * Does nothing. + */ + public void visit(ConstantFormula constant) {} + + /** + * Visits the left and right children if + * this.visited(compFormula) returns false. Otherwise does nothing. + * @ensures compFormula.left.accept(this) && compFormula.right.accept(this) + */ + public void visit(ComparisonFormula compFormula) { + if (visited(compFormula)) return; + compFormula.left().accept(this); + compFormula.right().accept(this); + } + + /** + * Visits the formula if + * this.visited(multFormula) returns false. Otherwise does nothing. + * @ensures multFormula.expression.accept(this) + */ + public void visit(MultiplicityFormula multFormula) { + if (visited(multFormula)) return; + multFormula.expression().accept(this); + } + + /** + * Visits the children of the predicate if + * this.visited(pred) returns false. Otherwise does nothing. + * @ensures pred.relation.accept(this) && + * (pred.name = FUNCTION => pred.domain.accept(this) && pred.range.accept(this)) && + * (pred.name = TOTAL_ORDERING => + * pred.ordered.accept(this) && pred.first.accept(this) && pred.last.accept(this) ) + */ + public void visit(RelationPredicate pred) { + if (visited(pred)) return; + pred.relation().accept(this); + if (pred.name()==RelationPredicate.Name.FUNCTION) { + final RelationPredicate.Function fp = (RelationPredicate.Function) pred; + fp.domain().accept(this); + fp.range().accept(this); + } else if (pred.name()==RelationPredicate.Name.TOTAL_ORDERING) { + final RelationPredicate.TotalOrdering tp = (RelationPredicate.TotalOrdering) pred; + tp.ordered().accept(this); + tp.first().accept(this); + tp.last().accept(this); + } + } + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/visitor/ReturnVisitor.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/visitor/ReturnVisitor.java new file mode 100644 index 00000000..76b4562e --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/visitor/ReturnVisitor.java @@ -0,0 +1,213 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.ast.visitor; + +import kodkod.ast.BinaryExpression; +import kodkod.ast.BinaryFormula; +import kodkod.ast.BinaryIntExpression; +import kodkod.ast.ComparisonFormula; +import kodkod.ast.Comprehension; +import kodkod.ast.ConstantExpression; +import kodkod.ast.ConstantFormula; +import kodkod.ast.Decl; +import kodkod.ast.Decls; +import kodkod.ast.ExprToIntCast; +import kodkod.ast.IfExpression; +import kodkod.ast.IfIntExpression; +import kodkod.ast.IntComparisonFormula; +import kodkod.ast.IntConstant; +import kodkod.ast.IntToExprCast; +import kodkod.ast.MultiplicityFormula; +import kodkod.ast.NaryExpression; +import kodkod.ast.NaryFormula; +import kodkod.ast.NaryIntExpression; +import kodkod.ast.NotFormula; +import kodkod.ast.ProjectExpression; +import kodkod.ast.QuantifiedFormula; +import kodkod.ast.Relation; +import kodkod.ast.RelationPredicate; +import kodkod.ast.SumExpression; +import kodkod.ast.UnaryExpression; +import kodkod.ast.UnaryIntExpression; +import kodkod.ast.Variable; + + +/** + * A visitor that visits every node in the AST, returning some value for each. + * The methods that visit an {@link kodkod.ast.Expression expression}, + * {@link kodkod.ast.Formula formula}, {@link kodkod.ast.Decls declarations}, + * and {@link kodkod.ast.IntExpression integer expression} and + * return values of types E, F, D, and I respectively. + * + * @author Emina Torlak + */ +public interface ReturnVisitor { + + /** + * Visits the given sequence of declarations and returns the result. + * @return the result of visiting decls + **/ + public D visit(Decls decls); + /** + * Visits the given declaration and returns the result. + * @return the result of visiting decl + **/ + public D visit(Decl decl); + + /** + * Visits the given relation and returns the result. + * @return the result of visiting relation + **/ + public E visit(Relation relation); + /** + * Visits the given variable and returns the result. + * @return the result of visiting variable + **/ + public E visit(Variable variable); + /** + * Visits the given constant expression and returns the result. + * @return the result of visiting constExpr + **/ + public E visit(ConstantExpression constExpr); + + /** + * Visits the given unary expression and returns the result. + * @return the result of visiting unaryExpr + **/ + public E visit(UnaryExpression unaryExpr); + /** + * Visits the given binary expression and returns the result. + * @return the result of visiting binExpr + **/ + public E visit(BinaryExpression binExpr); + /** + * Visits the given nary expression and returns the result. + * @return the result of visiting expr + **/ + public E visit(NaryExpression expr); + /** + * Visits the given comprehension and returns the result. + * @return the result of visiting comprehension + **/ + public E visit(Comprehension comprehension); + /** + * Visits the given if-then expression and returns the result. + * @return the result of visiting ifExpr + **/ + public E visit(IfExpression ifExpr); + /** + * Visits the given projection expression and returns the result. + * @return the result of visiting project + */ + public E visit(ProjectExpression project); + + /** + * Visits the given cast expression expression and returns the result. + * @return the result of visiting castExpr + **/ + public E visit(IntToExprCast castExpr); + + /** + * Visits the given integer constant and returns the result. + * @return the result of visiting intConst + */ + public I visit(IntConstant intConst); + /** + * Visits the given if-int-expression and returns the result. + * @return the result of visiting intExpr + */ + public I visit(IfIntExpression intExpr); + /** + * Visits the given unary integer expression and returns the result. + * @return the result of visiting intExpr + */ + public I visit(ExprToIntCast intExpr); + /** + * Visits the given nary expression and returns the result. + * @return the result of visiting intExpr + **/ + public I visit(NaryIntExpression intExpr); + /** + * Visits the given binary integer expression and returns the result. + * @return the result of visiting intExpr + */ + public I visit(BinaryIntExpression intExpr); + /** + * Visits the given unary integer expression and returns the result. + * @return the result of visiting intExpr + */ + public I visit(UnaryIntExpression intExpr); + /** + * Visits the given sum expression and returns the result. + * @return the result of visiting intExpr + */ + public I visit(SumExpression intExpr); + /** + * Visits the given integer comparison formula and returns the result. + * @return the result of visiting intcomp + */ + public F visit(IntComparisonFormula intComp); + + /** + * Visits the given quantified formula and returns the result. + * @return the result of visiting quantFormula + **/ + public F visit(QuantifiedFormula quantFormula); + /** + * Visits the given nary formula and returns the result. + * @return the result of visiting formula + **/ + public F visit(NaryFormula formula); + /** + * Visits the given binary formula and returns the result. + * @return the result of visiting binFormula + **/ + public F visit(BinaryFormula binFormula); + /** + * Visits the given negation and returns the result. + * @return the result of visiting not + **/ + public F visit(NotFormula not); + /** + * Visits the given constant formula and returns the result. + * @return the result of visiting constant + **/ + public F visit(ConstantFormula constant); + + /** + * Visits the given comparison formula and returns the result. + * @return the result of visiting compFormula + **/ + public F visit(ComparisonFormula compFormula); + /** + * Visits the given multiplicity formula and returns the result. + * @return the result of visiting multFormula + **/ + public F visit(MultiplicityFormula multFormula); + /** + * Visits the given relation predicate and returns the result. + * @return the result of visiting predicate + */ + public F visit(RelationPredicate predicate); + + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/visitor/VoidVisitor.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/visitor/VoidVisitor.java new file mode 100644 index 00000000..15757600 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/visitor/VoidVisitor.java @@ -0,0 +1,183 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.ast.visitor; + +import kodkod.ast.BinaryExpression; +import kodkod.ast.BinaryFormula; +import kodkod.ast.BinaryIntExpression; +import kodkod.ast.ComparisonFormula; +import kodkod.ast.Comprehension; +import kodkod.ast.ConstantExpression; +import kodkod.ast.ConstantFormula; +import kodkod.ast.Decl; +import kodkod.ast.Decls; +import kodkod.ast.ExprToIntCast; +import kodkod.ast.IfExpression; +import kodkod.ast.IfIntExpression; +import kodkod.ast.IntComparisonFormula; +import kodkod.ast.IntConstant; +import kodkod.ast.IntToExprCast; +import kodkod.ast.MultiplicityFormula; +import kodkod.ast.NaryExpression; +import kodkod.ast.NaryFormula; +import kodkod.ast.NaryIntExpression; +import kodkod.ast.NotFormula; +import kodkod.ast.ProjectExpression; +import kodkod.ast.QuantifiedFormula; +import kodkod.ast.Relation; +import kodkod.ast.RelationPredicate; +import kodkod.ast.SumExpression; +import kodkod.ast.UnaryExpression; +import kodkod.ast.UnaryIntExpression; +import kodkod.ast.Variable; + +/** + * A visitor that visits every node in the AST. + * + * @author Emina Torlak + */ +public interface VoidVisitor { + + /** + * Visits the given sequence of declarations. + **/ + public void visit(Decls decls); + /** + * Visits the given declaration. + **/ + public void visit(Decl decl); + + /** + * Visits the given relation. + **/ + public void visit(Relation relation); + /** + * Visits the given variable. + **/ + public void visit(Variable variable); + /** + * Visits the given constant expression. + **/ + public void visit(ConstantExpression constExpr); + + /** + * Visits the given unary expression. + **/ + public void visit(UnaryExpression unaryExpr); + + /** + * Visits the given binary expression. + **/ + public void visit(BinaryExpression binExpr); + + /** + * Visits the given nary expression. + **/ + public void visit(NaryExpression expr); + + + /** + * Visits the given comprehension. + **/ + public void visit(Comprehension comprehension); + /** + * Visits the given if-then expression. + **/ + public void visit(IfExpression ifExpr); + /** + * Visits the given projection expression. + */ + public void visit(ProjectExpression project); + + + /** + * Visits the given integer cast expression. + */ + public void visit(IntToExprCast castExpr); + /** + * Visits the given integer constant. + */ + public void visit(IntConstant intConst); + /** + * Visits the given unary integer expression. + */ + public void visit(ExprToIntCast intExpr); + /** + * Visits the given if-int-expression. + */ + public void visit(IfIntExpression intExpr); + /** + * Visits the given nary int expression. + **/ + public void visit(NaryIntExpression intExpr); + /** + * Visits the given binary integer expression. + */ + public void visit(BinaryIntExpression intExpr); + /** + * Visits the given unary integer expression. + */ + public void visit(UnaryIntExpression intExpr); + /** + * Visits the given sum expression. + */ + public void visit(SumExpression intExpr); + /** + * Visits the given integer comparison formula. + */ + public void visit(IntComparisonFormula intComp); + + /** + * Visits the given quantified formula. + **/ + public void visit(QuantifiedFormula quantFormula); + /** + * Visits the given nary formula. + **/ + public void visit(NaryFormula formula); + /** + * Visits the given binary formula. + **/ + public void visit(BinaryFormula binFormula); + /** + * Visits the given negation. + **/ + public void visit(NotFormula not); + /** + * Visits the given constant formula. + **/ + public void visit(ConstantFormula constant); + + /** + * Visits the given comparison formula. + **/ + public void visit(ComparisonFormula compFormula); + /** + * Visits the given multiplicity formula. + **/ + public void visit(MultiplicityFormula multFormula); + /** + * Visits the given relation predicate. + */ + public void visit(RelationPredicate predicate); + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/visitor/package.html b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/visitor/package.html new file mode 100644 index 00000000..5698cc13 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/ast/visitor/package.html @@ -0,0 +1,45 @@ + + + + + + + + +Contains visitors for Kodkod formulas, expressions, and integer expressions. + +

Package Specification

+ +

Provides two interfaces for traversing the Kodkod AST using +the visitor pattern. A {@linkplain kodkod.ast.visitor.VoidVisitor} visits the nodes but returns no values. A +{@linkplain kodkod.ast.visitor.ReturnVisitor} can be parametrized to return values of specific types for +{@linkplain kodkod.ast.Decls}, {@linkplain kodkod.ast.Expression}, {@linkplain kodkod.ast.IntExpression}, +and {@linkplain kodkod.ast.Formula} nodes.

+ +

Several skeletal implementations of the VoidVisitor and ReturnVisitor interfaces +are also provided. These traverse the AST in a depth-first manner and optionally cache +the results of visiting specified nodes. The caching functionality makes it convenient +to implement visitors that visit shared nodes only once.

+ +

Related Documentation

+ +@see kodkod.ast.visitor.VoidVisitor +@see kodkod.ast.visitor.ReturnVisitor + + + diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/AbortedException.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/AbortedException.java new file mode 100644 index 00000000..12a49de3 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/AbortedException.java @@ -0,0 +1,59 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.engine; + +/** + * Indicates that a solving or evaluation task has been aborted + * by calling Thread.interrupt on the solving (evaluation) thread. + * @author Emina Torlak + */ +public final class AbortedException extends RuntimeException { + + private static final long serialVersionUID = 201522560152091247L; + + /** + * Constructs an aborted exception with no message. + */ + AbortedException() {} + + /** + * Constructs an aborted exception with the given message. + */ + AbortedException(String message) { + super(message); + } + + /** + * Constructs an aborted exception with the given cause. + */ + AbortedException(Throwable cause) { + super(cause); + } + + /** + * Constructs an aborted exception with the given message and cause. + */ + AbortedException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/CapacityExceededException.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/CapacityExceededException.java new file mode 100644 index 00000000..92ef7287 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/CapacityExceededException.java @@ -0,0 +1,75 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.engine; + +import kodkod.util.ints.IntVector; + +/** + * Indicates that a problem construction or translation task failed because + * the capacity of the index representation was exceeded. + * @specfield dims: IntVector // contains a vector of dimensions which exceed the representation capacity + * @author Emina Torlak + */ +public final class CapacityExceededException extends RuntimeException { + + private static final long serialVersionUID = -8098615204149641969L; + private final IntVector dims; + + /** + * Constructs a CapacityExceededException from the given dimensions. + */ + public CapacityExceededException(IntVector dims) { + this.dims = dims; + } + + /** + * Constructs a CapacityExceededException with the given message and dimensions. + */ + public CapacityExceededException(String arg0, IntVector dims) { + super(arg0); + this.dims = dims; + } + + /** + * Constructs a CapacityExceededException with the given cause. + */ + public CapacityExceededException(Throwable arg0, IntVector dims) { + super(arg0); + this.dims = dims; + } + + /** + * Constructs a CapacityExceededException with the given message and cause. + */ + public CapacityExceededException(String arg0, Throwable arg1, IntVector dims) { + super(arg0, arg1); + this.dims = dims; + } + + /** + * Returns the vector of dimensions which, when multiplied together, exceed + * the representation capacity. + * @return this.dims + */ + public final IntVector dims() { return dims; } + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/Cost.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/Cost.java new file mode 100644 index 00000000..8cb0c7b1 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/Cost.java @@ -0,0 +1,46 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.engine; + +import kodkod.ast.Formula; +import kodkod.ast.Relation; +import kodkod.instance.Bounds; + +/** + * A cost function to be minimized during {@link kodkod.engine.Solver#solve(Formula, Bounds, Cost) solving}. + * In particular, each Cost is a total function from a set of {@link kodkod.ast.Relation relations} + * to a non-negative integer which represents the weight of one edge in the relation's value. (Hence, the + * total cost of a relation r in a given instance is s is #s.tuples(r) * edgeCost(r).) + * @specfield relations: set Relation // the domain of this cost function + * @specfield cost: relations -> one [0..) + * @author Emina Torlak + */ +public interface Cost { + + /** + * Returns the cost of one edge in the relational value of the given + * {@link Relation Relation instance}. + * @return this.cost[relation] + * @throws IllegalArgumentException - relation !in this.relations + */ + public abstract int edgeCost(Relation relation); +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/Evaluator.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/Evaluator.java new file mode 100644 index 00000000..d02f83b5 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/Evaluator.java @@ -0,0 +1,149 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.engine; + +import kodkod.ast.Expression; +import kodkod.ast.Formula; +import kodkod.ast.IntExpression; +import kodkod.engine.bool.BooleanMatrix; +import kodkod.engine.bool.Int; +import kodkod.engine.config.Options; +import kodkod.engine.fol2sat.Translator; +import kodkod.instance.Instance; +import kodkod.instance.TupleSet; + +/** + * An evaluator for relational formulas and expressions with + * respect to a given {@link kodkod.instance.Instance instance} + * and {@link kodkod.engine.config.Options options}. + * + *

Note: you may observe surprising (though correct) + * evaluator behavior if you do not use the same set of integer + * options (i.e. {@link kodkod.engine.config.Options#intEncoding() intEncoding} and {@link kodkod.engine.config.Options#bitwidth() bitwidth} + * when evaluating and solving a formula. For example, suppose that + * that an Instance i is a solution to a formula f found using options o. + * If you create an evaluator e such that e.instance = i, but e.options + * is an Options object with different integer settings than o, + * e.evalate(f) may return false.

+ * + * @specfield options: Options + * @specfield instance: Instance + * @author Emina Torlak + */ +public final class Evaluator { + private final Instance instance; + private final Options options; + private boolean wasOverflow; // [AM] was overflow detected during evaluation + + /** + * Constructs a new Evaluator for the given instance, using a + * default Options object. + * @ensures this.instance' = instance && this.options' = new Options() + * @throws NullPointerException - instance = null + */ + public Evaluator(Instance instance) { + this(instance, new Options()); + } + + /** + * Constructs a new Evaluator for the given instance and options + * @ensures this.instance' = instance && this.options' = options + * @throws NullPointerException - instance = null || options = null + */ + public Evaluator(Instance instance, Options options) { + if (instance==null || options==null) throw new NullPointerException(); + this.instance = instance; + this.options = options; + } + + /** + * Returns the Options object used by this evaluator. + * @return this.options + */ + public Options options() { return options; } + + /** + * Returns this.instance. Any modifications to the returned object + * will be reflected in the behavior of the evaluate methods. + * + * @return this.instance + */ + public Instance instance() { return instance; } + + /** + * Evaluates the specified formula with respect to the relation-tuple mappings + * given by this.instance and using this.options. + * @return true if formula is true with respect to this.instance and this.options; + * otherwise returns false + * @throws kodkod.engine.fol2sat.HigherOrderDeclException - the formula contains a higher order declaration + * @throws kodkod.engine.fol2sat.UnboundLeafException - the formula contains an undeclared variable or + * a relation not mapped by this.instance + */ + public boolean evaluate(Formula formula){ + if (formula == null) throw new NullPointerException("formula"); + return (Translator.evaluate(formula, instance, options)).booleanValue(); + } + + /** + * Evaluates the specified expession with respect to the relation-tuple mappings + * given by this.instance and using this.options. + * @return {@link kodkod.instance.TupleSet set} of tuples to which the expression evaluates given the + * mappings in this.instance and the options in this.options. + * @throws kodkod.engine.fol2sat.HigherOrderDeclException - the expression contains a higher order declaration + * @throws kodkod.engine.fol2sat.UnboundLeafException - the expression contains an undeclared variable or + * a relation not mapped by this.instance + */ + public TupleSet evaluate(Expression expression){ + if (expression == null) throw new NullPointerException("expression"); + final BooleanMatrix sol = Translator.evaluate(expression,instance,options); + return instance.universe().factory().setOf(expression.arity(), sol.denseIndices()); + } + + /** + * Evaluates the specified int expession with respect to the relation-tuple mappings + * given by this.instance and using this.options. + * @return the integer to which the expression evaluates given the + * mappings in this.instance and the options in this.options. + * @throws kodkod.engine.fol2sat.HigherOrderDeclException - intExpr contains a higher order declaration + * @throws kodkod.engine.fol2sat.UnboundLeafException - intExpr contains an undeclared variable or + * a relation not mapped by this.instance + */ + public int evaluate(IntExpression intExpr) { + if (intExpr == null) throw new NullPointerException("intexpression"); + final Int sol = Translator.evaluate(intExpr, instance, options); + this.wasOverflow = sol.defCond().isOverflowFlag(); + return sol.value(); + } + + /** Returns whether overflow was detected during evaluation */ //[AM] + public boolean wasOverflow() { + return wasOverflow; + } + + /** + * {@inheritDoc} + * @see java.lang.Object#toString() + */ + public String toString() { + return options + "\n" + instance; + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/Proof.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/Proof.java new file mode 100644 index 00000000..76e87f4e --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/Proof.java @@ -0,0 +1,129 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.engine; + +import java.util.Iterator; +import java.util.Map; + +import kodkod.ast.Formula; +import kodkod.ast.Node; +import kodkod.engine.config.Options; +import kodkod.engine.fol2sat.TranslationLog; +import kodkod.engine.fol2sat.TranslationRecord; +import kodkod.engine.satlab.ReductionStrategy; +import kodkod.util.nodes.Nodes; + +/** + * Contains a proof of unsatisfiability of a + * given FOL formula. + * + * @specfield log: TranslationLog // log of the translation of this.formula with respect to this.bounds + */ +public abstract class Proof { + private final TranslationLog log; + + /** + * Constructs a new Proof of unsatisfiability for log.formula. + * @ensures this.log = log + */ + Proof(TranslationLog log) { + this.log = log; + } + + /** + * Minimizes the proof of this.log.formula's unsatisfiability + * using the specified proof reduction strategy. The strategy + * argument is ignored (it can be null) if this.formula is + * trivially unsatisfiable with respect to this.bounds. In that + * case, the core is reduced using the trivial strategy + * that does one of the following: (1) if there is a + * root that simplified to FALSE, sets the minimal core + * to that root; or (2) if not, then there must be two + * roots that translated to x and -x, where x is a boolean + * literal, so we pick those two as the minimal core. + * + *

Note: the core minimization is performed at the level of the + * transformed formula, not the original formula if the problem was translated + * with a non-zero {@linkplain Options#coreGranularity() core granularity} setting. + * In other words, after this method has been called, {@linkplain #highLevelCore() highLevelCore()} + * returns a map M such that M.keySet() is a minimal core with respect to this.log.bounds. In contrast, + * {@linkplain Nodes#allRoots(Formula, java.util.Collection) Nodes.roots(this.log.originalFormula, M.values())} is + * unsatisfiable with respect this.log.originalBounds. These formulas, however, do not necessarily form a + * minimal core of this.log.originalFormula if the original problem was translated + * with a non-zero {@linkplain Options#coreGranularity() core granularity} setting.

+ * + * @ensures minimizes the proof of this.log.formula's unsatisfiability + * using the specified proof reduction strategy (or the trivial + * strategy if this.formula is trivially unsatisfiable with respect + * to this.bounds). + * + * @see kodkod.engine.satlab.ReductionStrategy + */ + public abstract void minimize(ReductionStrategy strategy); + + /** + * Returns an iterator over the {@link TranslationRecord log records} for the nodes + * that are in the unsatisfiable core of this.log.formula. The record objects returned by the iterator are not + * guaranteed to be immutable. In particular, the state of a record object + * returned by next() is guaranteed to remain the same only until the + * subsequent call to next(). + * @return an iterator over the {@link TranslationRecord log records} for the nodes + * that are in the unsatisfiable core of this.log.formula. + */ + public abstract Iterator core() ; + + /** + * Returns a map whose key set is the unsatisfiable subset of the top-level conjunctions of this.log.formula + * as given by {@linkplain #core() this.core()}. Each formula in the key set is mapped to the descendant of this.log.originalFormula + * from which it was derived by skolemization or by some other optimization. + * @return a map whose key set is the unsatisfiable subset of the top-level conjunctions of this.log.formula, + * as given by {@linkplain #core() this.core()}. Each formula in the key set is mapped to the descendant of this.log.originalFormula + * from which it was derived by skolemization or by some other optimization. + * @see #minimize(ReductionStrategy) + */ + public abstract Map highLevelCore() ; + + /** + * Returns the log of the translation that resulted + * in this proof. + * @return log of the translation that resulted in this proof + */ + public final TranslationLog log() { + return log; + } + + /** + * Returns a string representation of this proof. + * @return a string representation of this proof. + * @see java.lang.Object#toString() + */ +// public String toString() { +// final StringBuilder ret = new StringBuilder(); +// for(Formula f : highLevelCore()) { +// ret.append(" "); +// ret.append(f); +// ret.append("\n"); +// } +// return ret.toString(); +// } + +} \ No newline at end of file diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/ResolutionBasedProof.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/ResolutionBasedProof.java new file mode 100644 index 00000000..d9e423b5 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/ResolutionBasedProof.java @@ -0,0 +1,150 @@ +package kodkod.engine; + +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; + +import kodkod.ast.Formula; +import kodkod.ast.Node; +import kodkod.ast.Variable; +import kodkod.ast.visitor.AbstractVoidVisitor; +import kodkod.engine.fol2sat.RecordFilter; +import kodkod.engine.fol2sat.TranslationLog; +import kodkod.engine.fol2sat.TranslationRecord; +import kodkod.engine.satlab.ReductionStrategy; +import kodkod.engine.satlab.ResolutionTrace; +import kodkod.engine.satlab.SATProver; +import kodkod.engine.ucore.StrategyUtils; +import kodkod.instance.TupleSet; +import kodkod.util.collections.IdentityHashSet; +import kodkod.util.ints.IntSet; +import kodkod.util.ints.IntTreeSet; + +/** + * A proof of unsatisfiability based on a {@linkplain ResolutionTrace resolution trace} produced + * by a {@linkplain SATProver SATProver}. + * + * @author Emina Torlak + */ +final class ResolutionBasedProof extends Proof { + private SATProver solver; + private RecordFilter coreFilter; + private Map coreRoots; + + /** + * Constructs a new ResolutionRefutation that will extract the + * unsatisfiable core for log.formula from the given solver. + * @requires solver.solve() has been called and it returned false. + * @requires log.formula is the formula whose translation + * resulted in the given SATProver + * @ensures this.formula' = log.formula + */ + ResolutionBasedProof(SATProver solver, TranslationLog log) { + super(log); + this.solver = solver; + this.coreFilter = null; + this.coreRoots = null; + } + + /** + * Returns the connected core based on the given set of + * core variables. + * @requires coreVar = StrategyUtils.coreVars(solver.proof()); + * @return let formulas = (this.log.records[int] & literal.{i: int | abs(i) in coreVars}).formula | + * connected = {f: formulas | some s: set coreNodes | f + this.log.formula in s and (s - this.log.formula).~components in s } + */ + private Set connectedCore(final IntSet coreVars) { + final Set coreNodes = new IdentityHashSet(); + final RecordFilter filter = new RecordFilter() { + public boolean accept(Node node, Formula translated, int literal, Map env) { + return coreVars.contains(StrictMath.abs(literal)); + } + }; + for(Iterator itr = log().replay(filter); itr.hasNext(); ) { + coreNodes.add(itr.next().translated()); + } + final Set connected = new IdentityHashSet(); + final AbstractVoidVisitor traverser = new AbstractVoidVisitor() { + final Set visited = new IdentityHashSet(); + /** + * Returns true if the given node has been visited before or if + * it is not contained in this.nodes set. Otherwise adds + * the node to the connected set and returns false. + * @ensures this.visited' = this.visited + n + * @ensures n !in this.visited && n in coreNodes => + * connected' = connected + n else connected' = connected + * @return n in visited || n !in coreNodes + */ + protected boolean visited(Node n) { + if (visited.add(n) && coreNodes.contains(n)) { + connected.add((Formula)n); + return false; + } + return true; + } + }; + for(Formula root: log().roots()) { + root.accept(traverser); + } + return connected; + } + + /** + * {@inheritDoc} + * @see kodkod.engine.Proof#core() + */ + public final Iterator core() { + if (coreFilter == null) { + coreFilter = new RecordFilter() { + final IntSet coreVariables = StrategyUtils.coreVars(solver.proof()); + final Set coreNodes = connectedCore(coreVariables); + public boolean accept(Node node, Formula translated, int literal, Map env) { + return coreNodes.contains(translated) && coreVariables.contains(StrictMath.abs(literal)); + } + }; + } + return log().replay(coreFilter); + } + + /** + * {@inheritDoc} + * @see kodkod.engine.Proof#highLevelCore() + */ + public final Map highLevelCore() { + if (coreRoots == null) { + final RecordFilter unitFilter = new RecordFilter() { + final IntSet coreUnits = StrategyUtils.coreUnits(solver.proof()); + final Set roots = log().roots(); + public boolean accept(Node node, Formula translated, int literal, Map env) { + return roots.contains(translated) && coreUnits.contains(Math.abs(literal)); + } + + }; + coreRoots = new LinkedHashMap(); + final IntSet seenUnits = new IntTreeSet(); + for(Iterator itr = log().replay(unitFilter); itr.hasNext(); ) { + // it is possible that two top-level formulas have identical meaning, + // and are represented with the same core unit; in that case, we want only + // one of them in the core. + final TranslationRecord rec = itr.next(); + if (seenUnits.add(rec.literal())) { + coreRoots.put(rec.translated(), rec.node()); + } + } + coreRoots = Collections.unmodifiableMap(coreRoots); + } + return coreRoots; + } + + /** + * {@inheritDoc} + * @see kodkod.engine.Proof#minimize(kodkod.engine.satlab.ReductionStrategy) + */ + public void minimize(ReductionStrategy strategy) { + solver.reduce(strategy); + coreFilter = null; + coreRoots = null; + } +} \ No newline at end of file diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/Solution.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/Solution.java new file mode 100644 index 00000000..d21594d6 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/Solution.java @@ -0,0 +1,188 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.engine; + + +import kodkod.instance.Instance; + +/** + * Represents the full solution to a formula: an + * instance if the formula is satisfiable or a + * proof of unsatisfiability if not. + * + * @specfield formula: Formula // the formula being solved + * @specfield bounds: Bounds // the bounds on the formula + * @author Emina Torlak + */ +public final class Solution { + private final Outcome outcome; + private final Statistics stats; + private final Instance instance; + private final Proof proof; + + + /** + * Constructs a Solution from the given values. + * @requires outcome != null && stats != null + * @requires outcome = SATISFIABLE || TRIVIALLY_SATISFIABLE => instance != null + */ + private Solution(Outcome outcome, Statistics stats, Instance instance, Proof proof) { + assert outcome != null && stats != null; + this.outcome = outcome; + this.stats = stats; + this.instance = instance; + this.proof = proof; + } + + /** + * Returns a new Solution with a SATISFIABLE outcome, given stats and instance. + * @return {s: Solution | s.outcome() = SATISFIABLE && s.stats() = stats && s.instance() = instance } + */ + static Solution satisfiable(Statistics stats, Instance instance) { + return new Solution(Outcome.SATISFIABLE, stats, instance, null); + } + + /** + * Returns a new Solution with a TRIVIALLY_SATISFIABLE outcome, given stats and instance. + * @return {s: Solution | s.outcome() = TRIVIALLY_SATISFIABLE && s.stats() = stats && s.instance() = instance } + */ + static Solution triviallySatisfiable(Statistics stats, Instance instance) { + return new Solution(Outcome.TRIVIALLY_SATISFIABLE, stats, instance, null); + } + + /** + * Returns a new Solution with a UNSATISFIABLE outcome, given stats and proof. + * @return {s: Solution | s.outcome() = UNSATISFIABLE && s.stats() = stats && s.proof() = proof } + */ + static Solution unsatisfiable(Statistics stats, Proof proof) { + return new Solution(Outcome.UNSATISFIABLE, stats, null, proof); + } + + /** + * Returns a new Solution with a TRIVIALLY_UNSATISFIABLE outcome, given stats and proof. + * @return {s: Solution | s.outcome() = TRIVIALLY_UNSATISFIABLE && s.stats() = stats && s.proof() = proof } + */ + static Solution triviallyUnsatisfiable(Statistics stats, Proof proof) { + return new Solution(Outcome.TRIVIALLY_UNSATISFIABLE, stats, null, proof); + } + + /** + * Returns the outcome of the attempt to find + * a model for this.formula. If the outcome is + * SATISFIABLE or TRIVIALLY_SATISFIABLE, a satisfying + * instance can be obtained by calling {@link #instance()}. + * If the formula is UNSATISFIABLE, a proof of unsatisfiability + * can be obtained by calling {@link #proof()} provided that + * translation logging was enabled and the unsatisfiability was + * determined using a core extracting + * {@link kodkod.engine.satlab.SATSolver sat solver}. + * Lastly, if the returned Outcome is + * or TRIVIALLY_UNSATISFIABLE, a proof of unsatisfiability can + * be obtained by calling {@link #proof()} provided that + * translation logging was enabled. + * @return an Outcome instance designating the + * satisfiability of this.formula with respect to this.bounds + */ + public Outcome outcome() { + return outcome; + } + + /** + * Returns a satisfiying instance for this.formula, if the + * value returned by {@link #outcome() this.outcome()} is either + * SATISFIABLE or TRIVIALLY_SATISFIABLE. Otherwise returns null. + * @return a satisfying instance for this.formula, if one exists. + */ + public Instance instance() { + return instance; + } + + /** + * Returns a proof of this.formula's unsatisfiability if the value + * returned by {@link #outcome() this.outcome()} is UNSATISFIABLE or + * TRIVIALLY_UNSATISFIABLE, translation logging was enabled during solving, + * and a core extracting {@link kodkod.engine.satlab.SATProver sat solver} (if any) + * was used to determine unsatisfiability. + * Otherwise, null is returned. + * @return a proof of this.formula's unsatisfiability, if one is available. + */ + public Proof proof() { + return proof; + } + + /** + * Returns the statistics gathered while solving + * this.formula. + * @return the statistics gathered while solving + * this.formula. + */ + public Statistics stats() { + return stats; + } + + /** + * Returns a string representation of this Solution. + * @return a string representation of this Solution. + */ + public String toString() { + final StringBuilder b = new StringBuilder(); + b.append("---OUTCOME---\n"); + b.append(outcome); + b.append("\n"); + if (instance!=null) { + b.append("\n---INSTANCE---\n"); + b.append(instance); + b.append("\n"); + } + if (proof!=null) { + b.append("\n---PROOF---\n"); + b.append(proof); + b.append("\n"); + } + b.append("\n---STATS---\n"); + b.append(stats); + b.append("\n"); + return b.toString(); + } + + /** + * Enumerates the possible outcomes of an attempt + * to find a model for a FOL formula. + */ + public static enum Outcome { + /** The formula is satisfiable with respect to the specified bounds. */ + SATISFIABLE, + /** The formula is unsatisfiable with respect to the specified bounds. */ + UNSATISFIABLE, + /** + * The formula is trivially satisfiable with respect to the specified bounds: + * a series of simple transformations reduces the formula to the constant TRUE. + **/ + TRIVIALLY_SATISFIABLE, + /** + * The formula is trivially unsatisfiable with respect to the specified bounds: + * a series of simple transformations reduces the formula to the constant FALSE. + */ + TRIVIALLY_UNSATISFIABLE + } + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/Solver.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/Solver.java new file mode 100644 index 00000000..6ec2f0d5 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/Solver.java @@ -0,0 +1,515 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.engine; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.OutputStream; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; + +import kodkod.ast.Formula; +import kodkod.ast.Relation; +import kodkod.engine.config.Options; +import kodkod.engine.fol2sat.HigherOrderDeclException; +import kodkod.engine.fol2sat.Translation; +import kodkod.engine.fol2sat.TranslationLog; +import kodkod.engine.fol2sat.Translator; +import kodkod.engine.fol2sat.TrivialFormulaException; +import kodkod.engine.fol2sat.UnboundLeafException; +import kodkod.engine.satlab.SATAbortedException; +import kodkod.engine.satlab.SATMinSolver; +import kodkod.engine.satlab.SATProver; +import kodkod.engine.satlab.SATSolver; +import kodkod.instance.Bounds; +import kodkod.instance.Instance; +import kodkod.instance.TupleSet; +import kodkod.util.ints.IntIterator; +import kodkod.util.ints.IntSet; +import kodkod.util.nodes.PrettyPrinter; + + +/** + * A computational engine for solving relational formulae. + * A {@link kodkod.ast.Formula formula} is solved with respect to given + * {@link kodkod.instance.Bounds bounds} and {@link kodkod.engine.config.Options options}. + * + * @specfield options: Options + * @author Emina Torlak + */ +public final class Solver { + private final Options options; + + /** + * Constructs a new Solver with the default options. + * @ensures this.options' = new Options() + */ + public Solver() { + this.options = new Options(); + } + + /** + * Constructs a new Solver with the given options. + * @ensures this.options' = options + * @throws NullPointerException - options = null + */ + public Solver(Options options) { + if (options == null) + throw new NullPointerException(); + this.options = options; + } + + /** + * Returns the Options object used by this Solver + * to guide translation of formulas from first-order + * logic to cnf. + * @return this.options + */ + public Options options() { + return options; + } + + + + /** + * Attempts to satisfy the given formula with respect to the specified bounds, while + * minimizing the specified cost function. + * If the operation is successful, the method returns a Solution that contains either a minimal-cost + * instance of the formula or a proof of unsatisfiability. The latter is generated iff + * the SAT solver generated by this.options.solver() is a {@link SATProver SATProver} in addition + * to being a {@link kodkod.engine.satlab.SATMinSolver SATMinSolver}. + * + * @requires this.options.logTranslation==0 && this.options.solver.minimizer + * @return Solution to the formula with respect to the given bounds and cost + * @throws NullPointerException - formula = null || bounds = null || cost = null + * @throws kodkod.engine.fol2sat.UnboundLeafException - the formula contains an undeclared variable or + * a relation not mapped by the given bounds + * @throws kodkod.engine.fol2sat.HigherOrderDeclException - the formula contains a higher order declaration that cannot + * be skolemized, or it can be skolemized but this.options.skolemize is false. + * @throws AbortedException - this solving task was interrupted with a call to Thread.interrupt on this thread + * @throws IllegalArgumentException - some (formula.^children & Relation) - cost.relations + * @throws IllegalStateException - !this.options.solver.minimizer || this.options.logTranslation + * @see Solution + * @see Options + * @see Cost + */ + public Solution solve(Formula formula, Bounds bounds, Cost cost) + throws HigherOrderDeclException, UnboundLeafException, AbortedException { + + if (options.logTranslation()>0 || !options.solver().minimizer()) + throw new IllegalStateException(); + + final long startTransl = System.currentTimeMillis(); + try { + + final Translation translation = Translator.translate(formula, bounds, options); + final long endTransl = System.currentTimeMillis(); + + final SATMinSolver cnf = (SATMinSolver)translation.cnf(); + for(Relation r : bounds.relations()) { + IntSet vars = translation.primaryVariables(r); + if (vars != null) { + int rcost = cost.edgeCost(r); + for(IntIterator iter = vars.iterator(); iter.hasNext(); ) { + cnf.setCost(iter.next(), rcost); + } + } + } + + options.reporter().solvingCNF(0, cnf.numberOfVariables(), cnf.numberOfClauses()); + final long startSolve = System.currentTimeMillis(); + final boolean isSat = cnf.solve(); + final long endSolve = System.currentTimeMillis(); + + final Statistics stats = new Statistics(translation, endTransl - startTransl, endSolve - startSolve); + + return isSat ? sat(bounds, translation, stats) : unsat(translation, stats); + } catch (TrivialFormulaException trivial) { + final long endTransl = System.currentTimeMillis(); + return trivial(bounds, trivial, endTransl - startTransl); + } catch (SATAbortedException sae) { + throw new AbortedException(sae); + } + } + + /** + * Attempts to satisfy the given formula with respect to the specified bounds or + * prove the formula's unsatisfiability. + * If the operation is successful, the method returns a Solution that contains either + * an instance of the formula or an unsatisfiability proof. Note that an unsatisfiability + * proof will be constructed iff this.options specifies the use of a core extracting SATSolver. + * Additionally, the CNF variables in the proof can be related back to the nodes in the given formula + * iff this.options has translation logging enabled. Translation logging also requires that + * there are no subnodes in the given formula that are both syntactically shared and contain free variables. + * + * @return Solution to the formula with respect to the given bounds + * @throws NullPointerException - formula = null || bounds = null + * @throws kodkod.engine.fol2sat.UnboundLeafException - the formula contains an undeclared variable or + * a relation not mapped by the given bounds + * @throws kodkod.engine.fol2sat.HigherOrderDeclException - the formula contains a higher order declaration that cannot + * be skolemized, or it can be skolemized but this.options.skolemize is false. + * @throws AbortedException - this solving task was interrupted with a call to Thread.interrupt on this thread + * @see Solution + * @see Options + * @see Proof + */ + public Solution solve(Formula formula, Bounds bounds) + throws HigherOrderDeclException, UnboundLeafException, AbortedException { + + final long startTransl = System.currentTimeMillis(); + + try { + + final Translation translation = Translator.translate(formula, bounds, options); + final long endTransl = System.currentTimeMillis(); + + final SATSolver cnf = translation.cnf(); + + options.reporter().solvingCNF(translation.numPrimaryVariables(), cnf.numberOfVariables(), cnf.numberOfClauses()); + final long startSolve = System.currentTimeMillis(); + final boolean isSat = cnf.solve(); + final long endSolve = System.currentTimeMillis(); + + final Statistics stats = new Statistics(translation, endTransl - startTransl, endSolve - startSolve); + return isSat ? sat(bounds, translation, stats) : unsat(translation, stats); + + } catch (TrivialFormulaException trivial) { + final long endTransl = System.currentTimeMillis(); + return trivial(bounds, trivial, endTransl - startTransl); + } catch (SATAbortedException sae) { + throw new AbortedException(sae); + } + } + + /** + * Attempts to find all solutions to the given formula with respect to the specified bounds or + * to prove the formula's unsatisfiability. + * If the operation is successful, the method returns an iterator over n Solution objects. The outcome + * of the first n-1 solutions is SAT or trivially SAT, and the outcome of the nth solution is UNSAT + * or tirivally UNSAT. Note that an unsatisfiability + * proof will be constructed for the last solution iff this.options specifies the use of a core extracting SATSolver. + * Additionally, the CNF variables in the proof can be related back to the nodes in the given formula + * iff this.options has variable tracking enabled. Translation logging also requires that + * there are no subnodes in the given formula that are both syntactically shared and contain free variables. + * + * @return an iterator over all the Solutions to the formula with respect to the given bounds + * @throws NullPointerException - formula = null || bounds = null + * @throws kodkod.engine.fol2sat.UnboundLeafException - the formula contains an undeclared variable or + * a relation not mapped by the given bounds + * @throws kodkod.engine.fol2sat.HigherOrderDeclException - the formula contains a higher order declaration that cannot + * be skolemized, or it can be skolemized but this.options.skolemize is false. + * @throws AbortedException - this solving task was interrupted with a call to Thread.interrupt on this thread + * @throws IllegalStateException - !this.options.solver().incremental() + * @see Solution + * @see Options + * @see Proof + */ + public Iterator solveAll(final Formula formula, final Bounds bounds) + throws HigherOrderDeclException, UnboundLeafException, AbortedException { + + if (Options.isDebug()) flushFormula(formula, bounds); //[AM] + + if (!options.solver().incremental()) + throw new IllegalArgumentException("cannot enumerate solutions without an incremental solver."); + + return new SolutionIterator(formula, bounds, options); + } + + //[AM] + private void flushFormula(Formula formula, Bounds bounds) { + try { + File f = new File(System.getProperty("java.io.tmpdir"), "kk.txt"); + OutputStream os = new BufferedOutputStream(new FileOutputStream(f)); + os.write(PrettyPrinter.print(formula, 2).getBytes()); + os.write("\n================\n".getBytes()); + os.write(bounds.toString().getBytes()); + os.flush(); + os.close(); + } catch (Exception e) { + } + } + + /** + * {@inheritDoc} + * @see java.lang.Object#toString() + */ + public String toString() { + return options.toString(); + } + + /** + * Returns the result of solving a sat formula. + * @param bounds Bounds with which solve() was called + * @param translation the translation + * @param stats translation / solving stats + * @return the result of solving a sat formula. + */ + private static Solution sat(Bounds bounds, Translation translation, Statistics stats) { + final Solution sol = Solution.satisfiable(stats, padInstance(translation.interpret(), bounds)); + translation.cnf().free(); + return sol; + } + + /** + * Returns the result of solving an unsat formula. + * @param translation the translation + * @param stats translation / solving stats + * @return the result of solving an unsat formula. + */ + private static Solution unsat(Translation translation, Statistics stats) { + final SATSolver cnf = translation.cnf(); + final TranslationLog log = translation.log(); + if (cnf instanceof SATProver && log != null) { + return Solution.unsatisfiable(stats, new ResolutionBasedProof((SATProver) cnf, log)); + } else { // can free memory + final Solution sol = Solution.unsatisfiable(stats, null); + cnf.free(); + return sol; + } + } + + /** + * @return the number of primary variables needed to encode the unknown tuples in the given bounds. + */ + private static int trivialPrimaries(Bounds bounds) { + int prim = 0; + for(Relation r : bounds.relations()) { + prim += bounds.upperBound(r).size() - bounds.lowerBound(r).size(); + } + return prim; + } + + /** + * Returns the result of solving a trivially (un)sat formula. + * @param bounds Bounds with which solve() was called + * @param desc TrivialFormulaException thrown as the result of the formula simplifying to a constant + * @param translTime translation time + * @return the result of solving a trivially (un)sat formula. + */ + private static Solution trivial(Bounds bounds, TrivialFormulaException desc, long translTime) { + final Statistics stats = new Statistics(trivialPrimaries(desc.bounds()), 0, 0, translTime, 0); + if (desc.value().booleanValue()) { + return Solution.triviallySatisfiable(stats, padInstance(toInstance(desc.bounds()), bounds)); + } else { + return Solution.triviallyUnsatisfiable(stats, trivialProof(desc.log())); + } + } + + /** + * Returns a proof for the trivially unsatisfiable log.formula, + * provided that log is non-null. Otherwise returns null. + * @requires log != null => log.formula is trivially unsatisfiable + * @return a proof for the trivially unsatisfiable log.formula, + * provided that log is non-null. Otherwise returns null. + */ + private static Proof trivialProof(TranslationLog log) { + return log==null ? null : new TrivialProof(log); + } + + /** + * "Pads" the argument instance with the mappings that occur in bounds.lowerBound + * but not in the instance. + * @requires instance.relations in bounds.relations + * @ensures instance.relations' = bounds.relations' && + * instance.tuples' = bounds.lowerBound ++ instance.tuples + * @return instance + */ + private static Instance padInstance(Instance instance, Bounds bounds) { + for (Relation r : bounds.relations()) { + if (!instance.contains(r)) { + instance.add(r, bounds.lowerBound(r)); + } + } + for (IntIterator iter = bounds.ints().iterator(); iter.hasNext();) { + int i = iter.next(); + instance.add(i, bounds.exactBound(i)); + } + return instance; + } + + /** + * Creates an instance from the given Bounds. The instance + * is simply the mapping bounds.lowerBound. + * @return the instance corresponding to bounds.lowerBound + */ + private static Instance toInstance(Bounds bounds) { + final Instance instance = new Instance(bounds.universe()); + for (Relation r : bounds.relations()) { + instance.add(r, bounds.lowerBound(r)); + } + for (IntIterator iter = bounds.ints().iterator(); iter.hasNext();) { + int i = iter.next(); + instance.add(i, bounds.exactBound(i)); + } + return instance; + } + + /** + * An iterator over all solutions of a model. + * @author Emina Torlak + */ + private static final class SolutionIterator implements Iterator { + private final Options options; + private Formula formula; + private Bounds bounds; + private Translation translation; + private long translTime; + private int trivial; + + /** + * Constructs a solution iterator for the given formula, bounds, and options. + */ + SolutionIterator(Formula formula, Bounds bounds, Options options) { + this.formula = formula; + this.bounds = bounds; + this.options = options; + this.translation = null; + this.trivial = 0; + } + + /** + * Returns true if there is another solution. + * @see java.util.Iterator#hasNext() + */ + public boolean hasNext() { + return formula != null; + } + /** + * Solves translation.cnf and adds the negation of the + * found model to the set of clauses. + * @requires this.translation != null + * @ensures this.translation.cnf is modified to eliminate + * the current solution from the set of possible solutions + * @return current solution + */ + private Solution nonTrivialSolution() { + try { + final SATSolver cnf = translation.cnf(); + options.reporter().solvingCNF(translation.numPrimaryVariables(), cnf.numberOfVariables(), cnf.numberOfClauses()); + final long startSolve = System.currentTimeMillis(); + final boolean isSat = cnf.solve(); + final long endSolve = System.currentTimeMillis(); + + final Statistics stats = new Statistics(translation, translTime, endSolve - startSolve); + if (isSat) { + // extract the current solution; can't use the sat(..) method because it frees the sat solver + final Solution sol = Solution.satisfiable(stats, padInstance(translation.interpret(), bounds)); + // add the negation of the current model to the solver + final int primary = translation.numPrimaryVariables(); + final int[] notModel = new int[primary]; + for(int i = 1; i <= primary; i++) { + notModel[i-1] = cnf.valueOf(i) ? -i : i; + } + cnf.addClause(notModel); + return sol; + } else { + formula = null; bounds = null; // unsat, no more solutions, free up some space + return unsat(translation, stats); + } + } catch (SATAbortedException sae) { + throw new AbortedException(sae); + } + } + + /** + * Packages the information from the given trivial formula exception + * into a solution and returns it. If the formula is satisfiable, + * this.formula and this.bounds are modified to eliminate the current + * trivial solution from the set of possible solutions. + * @requires this.translation = null + * @ensures this.formula and this.bounds are modified to eliminate the current + * trivial solution from the set of possible solutions. + * @return current solution + */ + private Solution trivialSolution(TrivialFormulaException tfe) { + final Statistics stats = new Statistics(0, 0, 0, translTime, 0); + if (tfe.value().booleanValue()) { + trivial++; + final Bounds translBounds = tfe.bounds(); + final Instance trivialInstance = padInstance(toInstance(translBounds), bounds); + final Solution sol = Solution.triviallySatisfiable(stats, trivialInstance); + + final List changes = new LinkedList(); + + for(Map.Entry entry: trivialInstance.relationTuples().entrySet()) { + final Relation r = entry.getKey(); + + if (!translBounds.relations().contains(r)) { + translBounds.bound(r, bounds.lowerBound(r), bounds.upperBound(r)); + } + + if (translBounds.lowerBound(r)!=translBounds.upperBound(r)) { // r may change + if (entry.getValue().isEmpty()) { + changes.add(r.some()); + } else { + final Relation rmodel = Relation.nary(r.name()+"_"+trivial, r.arity()); + translBounds.boundExactly(rmodel, entry.getValue()); + changes.add(r.eq(rmodel).not()); + } + } + } + + bounds = translBounds; + + // no changes => there can be no more solutions (besides the current trivial one) + formula = changes.isEmpty() ? Formula.FALSE : tfe.formula().and(Formula.or(changes)); + + return sol; + } else { + formula = null; bounds = null; + return Solution.triviallyUnsatisfiable(stats, trivialProof(tfe.log())); + } + } + /** + * Returns the next solution if any. + * @see java.util.Iterator#next() + */ + public Solution next() { + if (!hasNext()) throw new NoSuchElementException(); + if (translation==null) { + try { + translTime = System.currentTimeMillis(); + translation = Translator.translate(formula, bounds, options); + translTime = System.currentTimeMillis() - translTime; + return nonTrivialSolution(); + } catch (TrivialFormulaException tfe) { + translTime = System.currentTimeMillis() - translTime; + return trivialSolution(tfe); + } + } else { + return nonTrivialSolution(); + } + } + + /** + * @see java.util.Iterator#remove() + */ + public void remove() { + throw new UnsupportedOperationException(); + } + + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/Statistics.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/Statistics.java new file mode 100644 index 00000000..2ecd6b79 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/Statistics.java @@ -0,0 +1,135 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.engine; + +import kodkod.engine.fol2sat.Translation; + +/** + * Stores the statistics gathered while solving + * a given formula. + * @specfield formula: Formula // the formula being solved + * @specfield bounds: Bounds // the bounds on the formula + */ +public final class Statistics { + + private static final String NEW_LINE = System.getProperty("line.separator"); + + private final int vars, pVars, clauses; + private final long translation, solving; + + /** + * Constructs a new Statistics object using the provided values. + */ + Statistics(int primaryVariables, int variables, int clauses, + long translationTime, long solvingTime) { + this.pVars = primaryVariables; + this.vars = variables; + this.clauses = clauses; + this.translation = translationTime; + this.solving = solvingTime; + } + + /** + * Constructs a new Statistics object using the provided values. + */ + Statistics(Translation translation, long translationTime, long solvingTime) { + this(translation.numPrimaryVariables(), translation.cnf().numberOfVariables(), + translation.cnf().numberOfClauses(), translationTime, solvingTime); + } + + /** + * Returns the number of variables needed + * to encode this.formula in CNF. + * @return the number of variables needed + * to encode this.formula in CNF. + */ + public int variables() { + return vars; + } + + /** + * Returns the number of primary variables + * used in the encoding of this.formula; i.e. the variables + * allocated to all the relations at the leaves + * of this.formula. + * @return the number of primary variables + * used in the encoding of this.formula + */ + public int primaryVariables() { + return pVars; + } + + /** + * Returns the number of clauses needed to + * encode this.formula in CNF. + * @return the number of variables needed + * to encode this.formula in CNF. + */ + public int clauses() { + return clauses; + } + + /** + * Returns the number of miliseconds spent + * on translation this.formula to CNF. + * @return the number of miliseconds spent + * on translation this.formula to CNF. + */ + public long translationTime() { + return translation; + } + + /** + * Returns the number of miliseconds spent + * on solving the CNF encoding of this.formula. + * @return the number of miliseconds spent + * on solving the CNF encoding of this.formula. + */ + public long solvingTime() { + return solving; + } + + /** + * Returns a string representation of this + * Statistics object. + * @return a string representation of this + * Statistics object. + */ + public String toString() { + final StringBuilder ret = new StringBuilder(); + ret.append("p cnf "); + ret.append(vars); + ret.append(" "); + ret.append(clauses); + ret.append(NEW_LINE); + ret.append("primary variables: "); + ret.append(pVars); + ret.append(NEW_LINE); + ret.append("translation time: "); + ret.append(translation); + ret.append(" ms").append(NEW_LINE); + ret.append("solving time: "); + ret.append(solving); + ret.append(" ms"); + return ret.toString(); + } +} \ No newline at end of file diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/TrivialProof.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/TrivialProof.java new file mode 100644 index 00000000..b02d8bb2 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/TrivialProof.java @@ -0,0 +1,339 @@ +package kodkod.engine; + +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; + +import kodkod.ast.BinaryFormula; +import kodkod.ast.ComparisonFormula; +import kodkod.ast.ConstantFormula; +import kodkod.ast.Decl; +import kodkod.ast.Formula; +import kodkod.ast.IntComparisonFormula; +import kodkod.ast.MultiplicityFormula; +import kodkod.ast.NaryFormula; +import kodkod.ast.Node; +import kodkod.ast.NotFormula; +import kodkod.ast.QuantifiedFormula; +import kodkod.ast.RelationPredicate; +import kodkod.ast.Variable; +import kodkod.ast.visitor.AbstractVoidVisitor; +import kodkod.engine.fol2sat.RecordFilter; +import kodkod.engine.fol2sat.TranslationLog; +import kodkod.engine.fol2sat.TranslationRecord; +import kodkod.engine.satlab.ReductionStrategy; +import kodkod.instance.TupleSet; +import kodkod.util.collections.IdentityHashSet; +import kodkod.util.ints.SparseSequence; +import kodkod.util.ints.TreeSequence; + +/** + * A proof of unsatisfiability for a trivially unsatisfiable formula. + * A formula is considered trivally unsatisfiable if its unsatisfiability + * is discovered through translation alone. + * + * @author Emina Torlak + */ +final class TrivialProof extends Proof { + private Map coreRoots; + private RecordFilter coreFilter; + + /** + * Constructs a proof of unsatisfiability for the trivially unsatisfiable + * formula whose translation is recorded in the given log. + * @requires log != null + * @ensures this.formula' = log.formula + */ + TrivialProof(TranslationLog log) { + super(log); + this.coreFilter = null; + this.coreRoots = null; + } + + /** + * {@inheritDoc} + * @see kodkod.engine.Proof#core() + */ + public final Iterator core() { + if (coreFilter==null) { + coreFilter = new RecordFilter() { + final Set coreNodes = NodePruner.relevantNodes(log(), coreRoots==null ? log().roots() : coreRoots.keySet()); + public boolean accept(Node node, Formula translated, int literal, Map env) { + return coreNodes.contains(translated) ; + } + }; + } + return log().replay(coreFilter); + } + + /** + * {@inheritDoc} + * @see kodkod.engine.Proof#highLevelCore() + */ + public final Map highLevelCore() { + if (coreRoots==null) { + final Iterator itr = core(); + final Set roots = log().roots(); + coreRoots = new LinkedHashMap(); + while( itr.hasNext() ) { + TranslationRecord rec = itr.next(); + if (roots.contains(rec.translated())) + coreRoots.put(rec.translated(), rec.node()); + } + coreRoots = Collections.unmodifiableMap(coreRoots); + } + return coreRoots; + } + + /** + * Minimizes the current core using the trivial strategy + * that does one of the following: (1) if there is a + * root that simplified to FALSE, sets the minimal core + * to that root; or (2) if not, there must be two + * roots that translated to x and -x, where x is a boolean + * literal, so we pick those two as the minimal core. + * The strategy argument is ignored (it can be null). + * @see Proof#minimize(ReductionStrategy) + */ + @Override + public void minimize(ReductionStrategy strategy) { + final Map rootLits = new LinkedHashMap(); + final Map rootNodes = new LinkedHashMap(); + final Set roots = log().roots(); + + for(Iterator itr = core(); itr.hasNext();) { + final TranslationRecord rec = itr.next(); + if (roots.contains(rec.translated())) { + // simply record the most recent output value for each formula: + // this is guaranteed to be the final output value for that + // formula because of the translation log guarantee that the + // log is replayed in the order of translation: i.e. a child's + // output value is always recorded before the parent's + int[] val = rootLits.get(rec.translated()); + if (val==null) { + val = new int[1]; + rootLits.put(rec.translated(), val); + } + val[0] = rec.literal(); + rootNodes.put(rec.translated(), rec.node()); + } + } + + final SparseSequence lits = new TreeSequence(); + for(Map.Entry entry : rootLits.entrySet()) { + final int lit = entry.getValue()[0]; + if (lit==-Integer.MAX_VALUE) { + coreRoots = Collections.singletonMap(entry.getKey(), rootNodes.get(entry.getKey())); + break; + } else if (lits.containsIndex(-lit)) { + final Formula f0 = lits.get(-lit); + final Formula f1 = entry.getKey(); + coreRoots = new LinkedHashMap(3); + coreRoots.put(f0, rootNodes.get(f0)); + coreRoots.put(f1, rootNodes.get(f1)); + coreRoots = Collections.unmodifiableMap(coreRoots); + break; + } else { + lits.put(lit, entry.getKey()); + } + } + + coreFilter = null; + assert coreRoots.size()==1 && rootLits.get(coreRoots.keySet().iterator().next())[0]==-Integer.MAX_VALUE || coreRoots.size()==2; + } + + /** + * Given a translation log for a trivially unsatisfiable formula, finds the nodes + * necessary for proving the formula's unsatisfiability. Instances of this + * visitor should be constructed and applied using the {@linkplain #relevantNodes(TranslationLog)} + * + * @specfield log: TranslationLog + * @author Emina Torlak + */ + private static final class NodePruner extends AbstractVoidVisitor { + private final Set visited, relevant; + private final Map constNodes; + + /** + * Constructs a proof finder for the given log. + * @ensures this.log' = log + */ + NodePruner(TranslationLog log) { + visited = new IdentityHashSet(); + relevant = new IdentityHashSet(); + + final RecordFilter filter = new RecordFilter() { + public boolean accept(Node node, Formula translated, int literal, Map env) { + return env.isEmpty(); + } + }; + + constNodes = new LinkedHashMap(); + for(Iterator itr = log.replay(filter); itr.hasNext(); ) { + TranslationRecord rec = itr.next(); + int lit = rec.literal(); + if (Math.abs(lit) != Integer.MAX_VALUE) { + constNodes.remove(rec.translated()); + } else if (lit==Integer.MAX_VALUE) { + constNodes.put(rec.translated(), Boolean.TRUE); + } else { + constNodes.put(rec.translated(), Boolean.FALSE); + } + } + } + + /** + * Returns the nodes necessary for proving the trivial unsatisfiability of log.formula. + * @requires some r: log.records | r.node = log.formula && r.literal = BooleanConstant.FALSE.label + * @requires highLevelCore in log.roots() and unsatisfiable(highLevelCore, log.bounds, log.options) + * @return nodes necessary for proving the trivial unsatisfiability of log.formula. + */ + static Set relevantNodes(TranslationLog log, Set highLevelCore) { + final NodePruner finder = new NodePruner(log); + for(Formula root : highLevelCore) { + if (!finder.isTrue(root)) { + root.accept(finder); + } + } + return finder.relevant; + } + + /** + * Returns true if the given node has been visited before. + * @ensures this.visited' = this.visited + n + * @return n in this.visited + */ + @Override + protected boolean visited(Node n) { + return !visited.add(n); + } + + /** + * Returns true if the node was simplified to TRUE during translation. + * @return some r: this.log.records | r.node = node && no r.env && r.literal = BooleanConstant.TRUE.label + */ + final boolean isTrue(Node node) { return constNodes.get(node)==Boolean.TRUE; } + + public void visit(Decl decl) { + if (visited(decl)) return; + relevant.add(decl); + } + + public void visit(QuantifiedFormula quantFormula) { + if (visited(quantFormula)) return; + relevant.add(quantFormula); + } + + public void visit(ComparisonFormula compFormula) { + if (visited(compFormula)) return; + relevant.add(compFormula); + } + public void visit(MultiplicityFormula multFormula) { + if (visited(multFormula)) return; + relevant.add(multFormula); + } + public void visit(RelationPredicate pred) { + if (visited(pred)) return; + relevant.add(pred); + } + public void visit(IntComparisonFormula intComp) { + if (visited(intComp)) return; + relevant.add(intComp); + } + + public void visit(ConstantFormula formula) { + relevant.add(formula); + } + + /** + * If the argument node has been been visited, adds it to this.relevant and visits its child. + */ + public void visit(NotFormula not) { + if (visited(not)) return; + relevant.add(not); + not.formula().accept(this); + } + + + /** + * If this formula should be visited, then we visit its children only + * if they could have contributed to the unsatisfiability of the top-level + * formula. For example, let binFormula = "p && q", binFormula simplified + * to FALSE, p simplified to FALSE and q was not simplified, then only p + * should be visited since p caused binFormula's reduction to FALSE. + */ + public void visit(BinaryFormula binFormula) { + if (visited(binFormula)) return; + relevant.add(binFormula); + + final Formula l = binFormula.left(), r = binFormula.right(); + final Boolean lval = constNodes.get(l), rval = constNodes.get(r); + final boolean lvisit, rvisit; + + switch(binFormula.op()) { + case AND : + lvisit = (lval==Boolean.FALSE || (lval==null && rval!=Boolean.FALSE)); + rvisit = (rval!=Boolean.TRUE && lval!=Boolean.FALSE); + break; + case OR : + lvisit = (lval==Boolean.TRUE || (lval==null && rval!=Boolean.TRUE)); + rvisit = (rval!=Boolean.FALSE && lval!=Boolean.TRUE); + break; + case IMPLIES: // !l || r + lvisit = (lval==Boolean.FALSE || (lval==null && rval!=Boolean.TRUE)); + rvisit = (rval!=Boolean.FALSE && lval!=Boolean.FALSE); + break; + case IFF: // (!l || r) && (l || !r) + lvisit = rvisit = true; + break; + default : + throw new IllegalArgumentException("Unknown operator: " + binFormula.op()); + } + + if (lvisit) { l.accept(this); } + if (rvisit) { r.accept(this); } + } + + /** + * If this formula should be visited, then we visit its children only + * if they could have contributed to the unsatisfiability of the top-level + * formula. For example, let binFormula = "p && q", binFormula simplified + * to FALSE, p simplified to FALSE and q was not simplified, then only p + * should be visited since p caused binFormula's reduction to FALSE. + */ + public void visit(NaryFormula formula) { + if (visited(formula)) return; + relevant.add(formula); + + final Boolean val = constNodes.get(formula); + final Boolean cancel; + + switch(formula.op()) { + case AND : cancel = Boolean.FALSE; break; + case OR : cancel = Boolean.TRUE; break; + default : throw new IllegalArgumentException("Unknown nary operator: " + formula.op()); + } + + final Boolean iden = Boolean.valueOf(!cancel); + if (val!=iden) { + for(Formula child : formula) { + if (constNodes.get(child)==cancel) { + child.accept(this); + return; + } + } + for(Formula child : formula) { + if (constNodes.get(child)!=iden) { + child.accept(this); + } + } + return; + } + + for(Formula child : formula) { child.accept(this); } + } + } + +} \ No newline at end of file diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/bool/BinaryGate.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/bool/BinaryGate.java new file mode 100644 index 00000000..e3ff93a9 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/bool/BinaryGate.java @@ -0,0 +1,139 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.engine.bool; + +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.Set; + +/** + * A logic gate with two inputs. + * @invariant #this.inputs = 2 + * @invariant digest = sum(inputs.digest(this.op)) + */ +final class BinaryGate extends MultiGate { + private final BooleanFormula low, high; + + /** + * Constructs a new binary gate with the given operator, label, and inputs. + * @requires components.h = components.l && l.label < h.label + * @ensures this.op' = op && this.inputs' = l + h && this.label' = label + */ + BinaryGate(Operator.Nary op, int label, int hashcode, BooleanFormula l, BooleanFormula h) { + super(op, label, hashcode); + assert l.label() < h.label(); + this.low = l; + this.high = h; + } + + /** + * Returns an integer k' such that 0 < |k'| < k and |k'| is the number of flattening + * steps that need to be taken to determine that this circuit has (or does not have) + * an input with the given label. + * A positive k' indicates that f is found to be an input to this circuit in k' steps. + * A negative k' indicates that f is not an input to this circuit, when it is flattened + * using at most k steps. + * @requires k > 0 + * @return the number of flattening + * steps that need to be taken to determine that f is (not) an input to this circuit + */ + @Override + int contains(Operator op, int f, int k) { + assert k > 0; + if (f==label()) return 1; + else if (this.op != op || k < 2 || f>label() || -f>label()) return -1; + else { + final int l = low.contains(op, f, k-1); + if (l > 0) return l; + else { + final int h = high.contains(op, f, k - l); + return h > 0 ? h - l : h + l; + } + } + } + + /** + * Flattens this circuit with respect to the given operator into + * the provided set. + * Specifically, the method modifies the set so that it contains + * the elements f_0, ..., f_k' where k' <= k elements and + * [[this]] = op(f_0, ..., f_k'). + * The default implementation simply adds this to the set. + * @requires k > 0 + * @ensures 1 <= k' <= k && some f_0,..., f_k' : flat.elts' | + * [[this]] = op([[f_0]], ..., [[f_k']]) + */ + @Override + void flatten(Operator op, Set flat, int k) { + assert k > 0; + if (this.op==op && k > 1) { + final int oldsize = flat.size(); + low.flatten(op, flat, k-1); + high.flatten(op, flat, k - (flat.size()-oldsize)); + } else { + flat.add(this); + } + } + + /** + * Returns 2. + * @return 2 + */ + @Override + public int size() { return 2; } + + /** + * Returns an iterator over the inputs to this gate, in + * the increasing label order. + * @return an iterator over this.inputs. + */ + @Override + public Iterator iterator() { + return new Iterator() { + int next = 0; + public boolean hasNext() { return next < 2; } + public BooleanFormula next() { + if (!hasNext()) throw new NoSuchElementException(); + return (next++ == 0 ? low : high); + } + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + + /** + * Returns the ith input to this gate. + * @return this.inputs[i] + * @requires 0 <= i < size + * @throws IndexOutOfBoundsException - i < 0 || i >= #this.inputs + */ + @Override + public BooleanFormula input(int i) { + switch(i) { + case 0 : return low; + case 1 : return high; + default: + throw new IndexOutOfBoundsException(); + } + } +} \ No newline at end of file diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/bool/BooleanAccumulator.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/bool/BooleanAccumulator.java new file mode 100644 index 00000000..97b067d8 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/bool/BooleanAccumulator.java @@ -0,0 +1,210 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.engine.bool; + +import java.util.Iterator; + +import kodkod.util.ints.IndexedEntry; +import kodkod.util.ints.SparseSequence; +import kodkod.util.ints.TreeSequence; + + +/** + * An accumulator for easy construction of gates with multiple inputs. + * An accumulator cannot be combined with other boolean values + * using BooleanFactory methods. To use the circuit + * represented by an accumulator, one must first convert it into a gate + * by calling {@link BooleanFactory#accumulate(BooleanAccumulator)}. + * + * @specfield components: set BooleanValue + * @specfield op: Operator.Nary + * @author Emina Torlak + */ +public final class BooleanAccumulator extends BooleanValue implements Iterable{ + final Operator.Nary op; + private final SparseSequence inputs; +// private final List inputs; + + /** + * Constructs a new accumulator with the given + * operator. + * @requires op != null + * @ensures this.op' = op && this.label' = label + */ + private BooleanAccumulator(Operator.Nary op) { + this.op = op; + inputs = new TreeSequence(); +// inputs = new ArrayList(); + } + + /** + * Returns a tree based implementation of BooleanAccumulator. + * The addInput operation executes in O(lg n) time where n is the number of gate inputs. + * @return an empty tree based BooleanAccumulator with the given operator. + * @throws NullPointerException - op = null + */ + public static BooleanAccumulator treeGate(Operator.Nary op) { + if (op==null) throw new NullPointerException(); + return new BooleanAccumulator(op); + } + + /** + * Returns a tree based implementation of BooleanAccumulator, initialized with the given inputs. + * The addInput operation executes in O(lg n) time where n is the number of gate inputs. + * @return a tree based BooleanAccumulator with the given operator, initialized with the given inputs + * @throws NullPointerException - op = null || inputs = null + */ + public static BooleanAccumulator treeGate(Operator.Nary op, BooleanValue... inputs) { + if (op==null) throw new NullPointerException(); + final BooleanAccumulator ret = new BooleanAccumulator(op); + for(BooleanValue v : inputs) { + if (ret.add(v)!=ret) + break; + } + return ret; + } + + /** + * Returns the operator for this accumulator. + * @return this.op + */ + public Operator.Nary op() { + return op; + } + + /** + * Adds the given value to this.components and returns the result. Specifically, + * if the addition of the value causes the gate to evaluate to op.shortCircuit, + * then this.inputs is set to op.shortCircuit. If the given value has already + * been added or it is equal to this.op.identity, nothing changes. Otherwise, v + * is added to this.input. The method returns this.op.shortCircuit if this.inputs + * contains it after the addition, otherwise it returns the gate itself. + * @ensures v = this.op.shortCircuit || v.negation in this.components => this.components' = this.op.shortCircuit, + * v !in BooleanConstant => this.components' = this.components + v, + * this.components' = this.components + * @return this.components' = op.shortCircuit => op.shortCircuit, this + */ + public BooleanValue add(BooleanValue v) { + if (isShortCircuited()) return op.shortCircuit(); + else{ + final int lit = v.label(); + if (v==op.shortCircuit() || inputs.containsIndex(-lit)) { + inputs.clear(); + inputs.put(op.shortCircuit().label, op.shortCircuit()); + return op.shortCircuit(); + } + if (v!=op.identity() && !inputs.containsIndex(lit)) { inputs.put(lit, (BooleanValue) v); } +// if (v==op.shortCircuit()) { +// inputs.clear(); +// inputs.add(op.shortCircuit()); +// return op.shortCircuit(); +// } +// if (v!=op.identity()) { inputs.add(v); } + return this; + } + } + + /** + * Returns true if this gate is short circuited; that is, + * its inputs are reduced to this.op.shortCircuit. + * @return this.inputs = this.op.shortCircuit + */ + public boolean isShortCircuited() { + return inputs.size()==1 && inputs.first().value()==op.shortCircuit(); +// return inputs.size()==1 && inputs.get(0)==op.shortCircuit(); + } + + /** + * Throws an IllegalArgumentException if op != this.op, + * otherwise returns the sum of digests of this gate's + * inputs with respect to the given operator. + * @return op = this.op => sum(this.inputs.digest(op)) + * @throws IllegalArgumentException - op != this.op + * @throws ClassCastException - some this.inputs & BooleanConstant + */ + int digest(Operator op) { + if (this.op != op) throw new IllegalArgumentException(); + int d = 0; + for(Iterator inputs = iterator(); inputs.hasNext();) { + d += ((BooleanFormula)inputs.next()).hash(op); + } + return d; + } + + + /** + * Returns the size of this accumulator. + * @return #this.inputs + */ + public int size() { + return inputs.size(); + } + + /** + * Returns an iterator over this.components, in + * the increasing order of labels. The returned iterator + * does not support removal. + * @return an iterator over this.components, in the + * increasing order of labels. + */ + public Iterator iterator() { + return new Iterator() { + final Iterator> iter = inputs.iterator(); + public boolean hasNext() { + return iter.hasNext(); + } + + public BooleanValue next() { + return iter.next().value(); + } + + public void remove() { + throw new UnsupportedOperationException(); + } + + }; +// return inputs.iterator(); + } + + + /** + * Throws an unsupported operation exception. + * @throws UnsupportedOperationException + */ + @Override + BooleanValue negation() { + throw new UnsupportedOperationException(); + } + + /** + * Returns 0. + * @return 0. + */ + @Override + public int label() { + return 0; + } + + public String toString() { + return inputs.toString(); + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/bool/BooleanConstant.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/bool/BooleanConstant.java new file mode 100644 index 00000000..fc321342 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/bool/BooleanConstant.java @@ -0,0 +1,99 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.engine.bool; + +/** + * A boolean constant, true or false. The integer + * label of the true and false constants are Integer.MAX_VALUE and -Integer.MAX_VALUE, respectively. + * The two boolean constants, TRUE and FALSE, are shared among all factories. + * + * @specfield value: boolean + * @invariant this.op = Operator.CONST + * @invariant value => Integer.MAX_VALUE, -Integer.MAX_VALUE + * @author Emina Torlak + */ +public final class BooleanConstant extends BooleanValue { + final int label; + + public static final BooleanConstant TRUE = new BooleanConstant(true); + public static final BooleanConstant FALSE = new BooleanConstant(false); + + /** + * Constructs a BooleanConstant that represent the given boolean + * value. + * @ensures value => this.label' = Integer.MAX_VALUE, this.label' = -Integer.MAX_VALUE + */ + private BooleanConstant(boolean value) { + this.label = (value ? Integer.MAX_VALUE : -Integer.MAX_VALUE); + } + + /** + * Returns the negation of this value. + * @return c: BooleanConstant | [[c]] = ![[this]] + */ + @Override + BooleanValue negation() { + return this==TRUE ? FALSE : TRUE; + } + + /** + * Returns the primitive boolean representation of this label. + * @return this.label == Integer.MAX_VALUE + */ + public boolean booleanValue() { return label > 0; } + + /** + * Returns the BooleanConstant that represents the given boolean value. + * @return {c: BooleanConstant | value => c.label = Integer.MAX_VALUE, c.label = -Integer.MAX_VALUE } + */ + public static BooleanConstant constant(boolean value) { + return value ? TRUE : FALSE; + } + + /** + * Returns the label for this value. + * @return this.label + */ + @Override + public int label() { + return label; + } + + /** + * Returns a string representation of this boolean value. + * @return a string representation of this boolean value. + */ + public String toString() { + return label>0 ? "T" : "F"; + } + + /** + * Returns Operator.CONST. + * @return Operator.CONST + */ + @Override + public Operator op() { + return Operator.CONST; + } + + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/bool/BooleanFactory.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/bool/BooleanFactory.java new file mode 100644 index 00000000..58ec9832 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/bool/BooleanFactory.java @@ -0,0 +1,464 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.engine.bool; + +import static kodkod.engine.bool.Operator.AND; +import static kodkod.engine.bool.Operator.OR; + +import java.util.Collection; +import java.util.Iterator; + +import kodkod.engine.config.Options; +import kodkod.engine.config.Options.IntEncoding; +import kodkod.util.ints.IntSet; + + +/** + * A factory for creating {@link kodkod.engine.bool.BooleanValue boolean values}, + * {@link kodkod.engine.bool.BooleanMatrix matrices}, and {@link kodkod.engine.bool.Int ints}. + * + * @specfield comparisonDepth: int // the depth to which circuits should be checked for equality + * @specfield intEncoding: Options.IntEncoding // the encoding used for generating integers ({@link #integer(int)} + * @specfield bitwidth: int // the bitwidth used for integer computations + * @specfield components: set BooleanValue + * @invariant no f1, f2: BooleanFactory | f1 != f2 => f1.components & f2.components = BooleanConstant + * @author Emina Torlak + */ +public abstract class BooleanFactory { + /** + * IMPLEMENTATION NOTE: BooleanFactory is the facade and a mediator for this package. + */ + private static CBCFactory CONSTANT_FACTORY; + /** + * A circuit factory used internally to assemble circuits. + */ + private final CBCFactory circuits; + + /** The bitwidth used for integer computations */ + final int bitwidth; + + /** Whether or not it should forbid overflows */ //[AM] + final boolean noOverflow; + + /** + * Constructs a boolean factory with the given number of input variables. Gates are + * checked for semantic equality down to the given depth. Integers are represented + * using the given number of bits. The noOverflow bit tells whether or not to forbid + * overflows. + * + * @requires 0 <= numVars < Integer.MAX_VALUE + * @requires checkToDepth >= 0 && bitwidth > 0 + * @ensures #this.components' = numInputVariables && this.components' in BooleanVariable + * @ensures this.bitwidth' = bitwidth + * @ensures this.comparisonDepth' = comparisonDepth + */ + private BooleanFactory(int numVars, int comparisonDepth, int bitwidth, boolean noOverflow) { + if (numVars==0) { + if (CONSTANT_FACTORY==null) + CONSTANT_FACTORY = new CBCFactory(0, 1); + this.circuits = CONSTANT_FACTORY; + } else { + this.circuits = new CBCFactory(numVars, 1<Gates are checked for semantic equality + * down to the depth given by options.sharing when checking for cached values. In general, setting the + * comparison depth to a higher value will result in more + * subcomponents being shared. However, it will also slow down + * gate construction.

+ *

Integers are created/manipulated according to the specifications in the given Options object.

+ * @return {f: BooleanFactory | #(f.components & BooleanVariable) = numVars && + * BooleanConstant in f.components && f.components in BooleanVariable + BooleanConstant && + * f.comparisonDepth = options.sharing && + * f.bitwidth = options.bitwidth && f.intEncoding = options.intEncoding && + * (all i: [1..numVars] | one f.components.label & i }} + * @throws IllegalArgumentException - numVars < 0 || numVars = Integer.MAX_VALUE + * @throws NullPointerException - options = null + */ + public static BooleanFactory factory(int numVars, Options options) { + switch(options.intEncoding()) { + case TWOSCOMPLEMENT : + return new TwosComplementFactory(numVars, options.sharing(), options.bitwidth(), options.noOverflow()); + default : + throw new IllegalArgumentException("unknown encoding: " + options.intEncoding()); + } + } + + /** + * Returns a BooleanFactory with no variables; the returned factory + * can manipulate only constants. + * @return {f: BooleanFactory | f.components = BooleanConstant && + * f.comparisonDepth = options.sharing && + * f.bitwidth = options.bitwidth && f.intEncoding = options.intEncoding } + * @throws NullPointerException - options = null + */ + public static BooleanFactory constantFactory(Options options) { + return factory(0, options); + } + + /** + * Returns the depth (from the root) to which components are checked for + * semantic equality during gate construction. + * @return this.comparisonDepth + */ + public final int comparisonDepth() { return Integer.numberOfTrailingZeros(circuits.cmpMax()); } + + /** + * Sets the comparison depth to the given value. Setting the + * comparison depth to a high value will result in more + * subcomponents being shared. However, it will also slow down + * gate construction. + * @ensures this.comparisonDepth' = newDepth + * @throws IllegalArgumentException - newDepth < 1 + */ + public final void setComparisonDepth(int newDepth) { + if (newDepth < 1) + throw new IllegalArgumentException("newDepth < 1: " + newDepth); + circuits.setCmpMax(1< [[v1]]. + * The behavior of this method is unspecified if v0 or v1 are not components of this factory. + * @requires v0 + v1 in this.components + * @return { v: BooleanValue | [[v]] = [[v0]] => [[v1]] } + * @ensures this.components' = this.components + v + * @throws NullPointerException - any of the arguments are null + */ + public final BooleanValue implies(BooleanValue v0, BooleanValue v1) { + return circuits.assemble(OR, v0.negation(), v1); + } + + /** + * Returns a boolean value whose meaning is [[v0]] <=> [[v1]]. + * The behavior of this method is unspecified if v0 or v1 are not components of this factory. + * @requires v0 + v1 in this.components + * @return { v: BooleanValue | [[v]] = [[v0]] iff [[v1]] } + * @ensures this.components' = this.components + v + * @throws NullPointerException - any of the arguments are null + */ + public final BooleanValue iff(BooleanValue v0, BooleanValue v1) { + return circuits.assemble(v0, v1, v1.negation()); + } + + /** + * Returns a boolean value whose meaning is [[i]] ? [[t]] : [[e]]. + * The behavior of this method is unspecified if i, t, or e are not components of this factory. + * @requires i + t + e in this.components + * @return { v: BooleanValue | [[v]] = [[i]] ? [[t]] : [[e]] } + * @ensures this.components' = this.components + v + * @throws NullPointerException - any of the arguments are null + */ + public final BooleanValue ite(BooleanValue i, BooleanValue t, BooleanValue e) { + return circuits.assemble(i, t, e); + } + + /** + * Returns a boolean value whose meaning is the sum bit of a full binary adder. + * The behavior of this method is unspecified if v0, v1, or cin are not components of this factory. + * @requires v0 + v1 + cin in this.components + * @return { v: BooleanValue | [[v]] = [[cin]] xor [[v0]] xor [[v1]] } + * @ensures this.components' = this.components + v + * @throws NullPointerException - any of the arguments are null + */ + public final BooleanValue sum(BooleanValue v0, BooleanValue v1, BooleanValue cin) { + return xor(cin, xor(v0, v1)); + } + + /** + * Returns a boolean value whose meaning is the carry out bit of a full binary adder. + * The behavior of this method is unspecified if v0, v1, or cin are not components of this factory. + * @requires v0 + v1 + cin in this.components + * @return { v: BooleanValue | [[v]] = ([[v0]] and [[v1]]) or ([[cin]] and ([[v0]] xor [[v1]])) } + * @ensures this.components' = this.components + v + * @throws NullPointerException - any of the arguments are null + */ + public final BooleanValue carry(BooleanValue v0, BooleanValue v1, BooleanValue cin) { + return or(and(v0, v1), and(cin, xor(v0, v1))); + } + + /** + * Converts the given accumulator into an immutable boolean value and adds it to this.components. + * This method requires that all of g's inputs are in this.components. If g has no inputs, + * its operator's identity constant is returned. If g has one input, that input is returned. + * Otherwise, an immutable value that is semantically equivalent to g is returned. + * The behavior of this method is unspecified if the components of g are not components of this factory. + * @requires g.components in this.components + * @return no g.inputs => g.op.identity(), + * one g.inputs => g.inputs, + * {g' : BooleanValue - BooleanAccumulator | [[g']] = [[g]] } + * @ensures this.components' = this.components + g' + */ + public final BooleanValue accumulate(BooleanAccumulator g) { + return circuits.assemble(g); + } + + /** + * Returns an Int that represents the given number using this.intEncoding. + * @return { i: Int | [[i]] = number && i.encoding && this.intEncoding && i.factory = this} + * @throws IllegalArgumentException - the number cannot be represented using + * the specified encoding + */ + public abstract Int integer(int number); + + /** + * Returns an Int that represents 0 or the given number, depending on the value of the given bit. + * The behavior of this method is unspecified if the bit is not a component of this factory. + * @return { i: Int | [[bit]] => [[i]] = number, [[i]] = 0 && i.encoding = this.intEncoding && i.factory = this} + */ + public abstract Int integer(int number, BooleanValue bit); + + /** + * Returns an Int that represents the sum of the elements returned by the iterator, + * using this.intEncoding. + * @param lo the first element of the current partial sum. Initial should be 0. + * @param hi the last element of the current partial sum. Initial should be size-1, where size is the total + * number of elements returned by the iterator. + * @return an Int that represents the sum of the elements returned by the iterator, + * using this.intEncoding. + */ + private Int sum(Iterator values, int low, int high) { + if (low > high) + return integer(0); + else if (low == high) + return integer(1, values.next()); + else { + final int mid = (low + high) / 2; + final Int lsum = sum(values, low, mid); + final Int hsum = sum(values, mid+1, high); + return lsum.plus(hsum); + } + } + + /** + * Returns an Int that represents the sum of all values in the given collection. + * @return an Int that represents the sum of all values in the given collection. + */ + public final Int sum(Collection bits) { + return sum(bits.iterator(), 0, bits.size()-1); + } + + /** + * Removes all formulas with one or more inputs from this.components. + * @ensures this.componets' = + * BooleanConstant + this.components & BooleanVariable + */ + public final void clear() { + circuits.clear(); + } + + /** + * Returns a BooleanMatrix with the given dimensions and this + * as the factory for its non-FALSE components. The returned matrix + * can store any value from this.components at all + * indices between 0, inclusive, and d.capacity(), exclusive. + * @throws NullPointerException - d = null + * @return { m: BooleanMatrix | m.factory = this && m.dimensions = d && m.elements = [0..d.capacity) -> one FALSE } + */ + public final BooleanMatrix matrix(Dimensions d) { + if (d == null ) throw new NullPointerException(); + return new BooleanMatrix(d, this); + } + + /** + * @throws IllegalArgumentException - indices !in [0..d.capacity()) + */ + private static void validate(IntSet indices, Dimensions d) { + if (!indices.isEmpty()) { + if (!d.validate(indices.min()) || !d.validate(indices.max())) + throw new IllegalArgumentException(); + } + } + + /** + * Returns a BooleanMatrix m with the given dimensions, this + * as its factory, and the indices from the set trueIndices initialized + * to TRUE. An IndexOutOfBoundsException may be thrown + * if {@link BooleanMatrix#set(int, BooleanValue)} is called on m with an index + * not contained in allIndices. If allIndices.equals(trueIndices), + * m may be a constant matrix; that is, an IllegalArgumentException may be + * thrown if {@link BooleanMatrix#set(int, BooleanValue)} is called on m with + * a non-constant value. Finally, if cloning trueIndices results in an immutable + * set, then {@link BooleanMatrix#set(int, BooleanValue) m.set(int, BooleanValue)} may throw + * an UnsupportedOperationException when called with a member of trueIndices. + * @requires allIndices.containsAll(trueIndices) + * @return { m: BooleanMatrix | m.factory = this && m.dimensions = dims && + * m.elements = [0..d.capacity()-1] ->one FALSE ++ indices->TRUE } + * @throws IllegalArgumentException - allIndices !in [0..d.capacity()) + * @throws IllegalArgumentException - one of the input sets is not cloneable + * @throws NullPointerException - d = null || allIndices = null || trueIndices = null + */ + public final BooleanMatrix matrix(Dimensions d, IntSet allIndices, IntSet trueIndices) { + assert allIndices.size() >= trueIndices.size(); // sanity check + validate(allIndices, d); validate(trueIndices, d); + try { + return new BooleanMatrix(d, this, allIndices, trueIndices.clone()); + } catch (CloneNotSupportedException e) { + throw new IllegalArgumentException(); + } + + } + + /** + * BooleanFactory that produces TwosComplementInts. + * @invariant encoding = TwosComplement + * @author Emina Torlak + */ + private static final class TwosComplementFactory extends BooleanFactory { + + /** + * Constructs a boolean factory with the given number of input variables. Gates are + * checked for semantic equality down to the given depth. Integers are represented + * using the given number of bits. + * @requires 0 <= numVars < Integer.MAX_VALUE + * @requires checkToDepth >= 0 && bitwidth > 0 + * @ensures #this.components' = numInputVariables && this.components' in BooleanVariable + * @ensures this.bitwidth' = bitwidth + * @ensures this.comparisonDepth' = comparisonDepth + * @ensures this.intEncoding' = BINARY + */ + TwosComplementFactory(int numVars, int comparisonDepth, int bitwidth, boolean noOverflow) { + super(numVars, comparisonDepth, bitwidth, noOverflow); + } + /** + * Returns TWOSCOMPLEMENT. + * @return TWOSCOMPLEMENT + * @see kodkod.engine.bool.BooleanFactory#intEncoding() + */ + @Override + public IntEncoding intEncoding() { + return IntEncoding.TWOSCOMPLEMENT; + } + + /** + * {@inheritDoc} + * @see kodkod.engine.bool.BooleanFactory#integer(int) + */ + @Override + public Int integer(int number) { + return new TwosComplementInt(this, number, BooleanConstant.TRUE); + } + + /** + * {@inheritDoc} + * @see kodkod.engine.bool.BooleanFactory#integer(int, kodkod.engine.bool.BooleanValue) + */ + @Override + public Int integer(int number, BooleanValue bit) { + return new TwosComplementInt(this, number, bit); + } + + } +} + diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/bool/BooleanFormula.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/bool/BooleanFormula.java new file mode 100644 index 00000000..9039ea07 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/bool/BooleanFormula.java @@ -0,0 +1,139 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.engine.bool; + +import java.util.Iterator; +import java.util.Set; + + + +/** + * A non-constant boolean value with zero or more inputs. + * @specfield op: Operator + * @specfield size: int + * @specfield inputs: [0..size) -> one BooleanFormula + * @invariant size >= 0 + * @author Emina Torlak + */ +public abstract class BooleanFormula extends BooleanValue implements Iterable { + private BooleanFormula negation; + + /** + * Constructs a boolean formula with the given negation. + */ + BooleanFormula(BooleanFormula negation) { + this.negation = negation; + } + /** + * Returns an integer hash of this formula, used + * to compute the hash of the composition of this and + * some other formula with the given operator. + * @return an integer hash of this formula when acting + * as an input to a multigate with the given operator. + */ + abstract int hash(Operator op); + + /** + * Returns an integer k' such that 0 < |k'| < k and |k'| is the number of flattening + * steps that need to be taken to determine that this circuit has (or does not have) + * an input with the given label. + * A positive k' indicates that f is found to be an input to this circuit in k' steps. + * A negative k' indicates that f is not an input to this circuit, when it is flattened + * using at most k steps. + * @requires k > 0 + * @return the number of flattening + * steps that need to be taken to determine that f is (not) an input to this circuit + */ + int contains(Operator op, int f, int k) { + return f==label() ? 1 : -1; + } + + /** + * Flattens this circuit with respect to the given operator into + * the provided set. + * Specifically, the method modifies the set so that it contains + * the elements f_0, ..., f_k' where k' <= k elements and + * [[this]] = op(f_0, ..., f_k'). + * The default implementation simply adds this to the set. + * @requires k > 0 + * @ensures 1 <= k' <= k && some f_0,..., f_k' : flat.elts' | + * [[this]] = op([[f_0]], ..., [[f_k']]) + */ + void flatten(Operator op, Set flat, int k) { + assert k > 0; + flat.add(this); + } + + + + /** + * Returns the negation of this formula if it has already been computed. + * Otherwise, computes, caches and returns the negation of this formula. + * @return !this + * @see kodkod.engine.bool.BooleanValue#negation() + */ + final BooleanFormula negation() { + if (negation==null) { + negation = new NotGate(this); + } + return negation; + } + + /** + * Returns true if the negation of this formula + * has already been computed. + * @return true if the negation of this formula has already been computed. + */ + final boolean hasNegation() { return negation != null; } + + /** + * Passes this value and the given + * argument value to the visitor, and returns the resulting value. + * @return the value produced by the visitor when visiting this node + * with the given argument. + */ + public abstract T accept(BooleanVisitor visitor, A arg); + + + + /** + * Returns an iterator over the inputs to this gate. + * @return an iterator over this.inputs. + */ + public abstract Iterator iterator(); + + + /** + * Returns the number of inputs to this gate. + * @return #this.inputs + */ + public abstract int size(); + + /** + * Returns the ith input to this gate. + * @return this.inputs[i] + * @requires 0 <= i < size + * @throws IndexOutOfBoundsException - i < 0 || i >= #this.inputs + */ + public abstract BooleanFormula input(int i); + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/bool/BooleanMatrix.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/bool/BooleanMatrix.java new file mode 100644 index 00000000..3ed8e65e --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/bool/BooleanMatrix.java @@ -0,0 +1,1058 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.engine.bool; + +import static kodkod.engine.bool.BooleanConstant.FALSE; +import static kodkod.engine.bool.BooleanConstant.TRUE; +import static kodkod.engine.bool.Operator.AND; +import static kodkod.engine.bool.Operator.OR; + +import java.util.Iterator; + +import kodkod.engine.fol2sat.Environment; +import kodkod.util.collections.Containers; +import kodkod.util.ints.ArraySequence; +import kodkod.util.ints.HomogenousSequence; +import kodkod.util.ints.IndexedEntry; +import kodkod.util.ints.IntIterator; +import kodkod.util.ints.IntSet; +import kodkod.util.ints.Ints; +import kodkod.util.ints.RangeSequence; +import kodkod.util.ints.SparseSequence; +import kodkod.util.ints.TreeSequence; + + +/** + *

An n-dimensional matrix of {@link kodkod.engine.bool.BooleanValue boolean values}. + * Boolean matrices are indexed using flat integer indeces. For example, + * let m be a the 2 x 3 matrix of boolean variables identifed by labels [0 4 1; 5 10 2]. + * Then, m[0] = 0, m[3] = 5, m[5] = 2, etc.

+ * + *

All values stored in the same matrix must be created by the same {@link kodkod.engine.bool.BooleanFactory circuit factory}. + * All methods that accept another BooleanMatrix as an input will throw an + * IllegalArgumentException if the values in the input matrix do not belong + * to the same factory as the values in the receiver matrix.

+ * + *

Some instances can store only constant values, or can only store + * values at particular indices (see {@link kodkod.engine.bool.BooleanFactory#matrix(Dimensions, IntSet, IntSet)}). + * If this is the case, an attempt to call {@link #set(int, BooleanValue) } + * with invalid parameters will cause an IllegalArgumentException or an IndexOutOfBoundsException.

+ * + * @specfield dimensions: Dimensions + * @specfield factory: BooleanFactory + * @specfield elements: [0..dimensions.capacity) -> one factory.components + * + * @author Emina Torlak + */ +public final class BooleanMatrix implements Iterable>, Cloneable { + + private DefCond defCond = new DefCond(); + + private final Dimensions dims; + private final BooleanFactory factory; + private final SparseSequence cells; + + /** + * Constructs a new matrix with the given dimensions, factory, and entries. + * + * @requires dimensions != null && factory != null && seq != null + * @requires seq.indices() in [0..dimensions.capacity) + * @ensures this.dimensions' = dimensions && this.factory' = factory && + * this.elements' = [0..dimensions.capacity)->one FALSE + */ + private BooleanMatrix(Dimensions dimensions, BooleanFactory factory, SparseSequence seq) { + this.dims = dimensions; + this.factory = factory; + this.cells = seq; + } + + /** + * Constructs a new matrix with the given dimensions and factory, + * backed by a sparse sequence which can most efficiently hold + * the elements storable in the sparse sequences s0 and s1. + * @ensures this.dimensions' = dimensions && this.factory' = factory && + * this.elements' = [0..dimensions.capacity)->one FALSE + */ + private BooleanMatrix(Dimensions d, BooleanFactory f, SparseSequence s0, SparseSequence s1) { + this.dims = d; + this.factory = f; + final Class c0 = s0.getClass(), c1 = s1.getClass(); + if (c0!=c1 || c0==RangeSequence.class) + this.cells = new RangeSequence(); + else if (c0==HomogenousSequence.class) + this.cells = new HomogenousSequence(TRUE, Ints.bestSet(d.capacity())); + else + this.cells = new TreeSequence(); + } + + /** + * Constructs a new matrix with the given dimensions and factory, + * backed by a sparse sequence which can most efficiently hold + * the elements storable in the matrices m and rest. + * @requires null !in d + m + rest[int] + * @requires m.factory = rest[int].factory + * @requires d.equals(m.dims) => d.equals(rest[int].dims) + * @ensures this.dimensions' = dimensions && this.factory' = m.factory && + * this.elements' = [0..dimensions.capacity)->one FALSE + * @throws IllegalArgumentException m.factory != rest[int].factory + * @throws IllegalArgumentException !(d.equals(m.dims) => d.equals(rest[int].dims)) + */ + private BooleanMatrix(Dimensions d, BooleanMatrix m, BooleanMatrix...rest) { + this.dims = d; + this.factory = m.factory; + + final Class h = HomogenousSequence.class, t = TreeSequence.class; + final boolean sameDim = d.equals(m); + + Class c = m.cells.getClass(); + int cId = c==h ? 1 : c==t ? 2 : 4; + + for(BooleanMatrix other : rest) { + checkFactory(factory, other.factory); + if (sameDim) checkDimensions(d, other.dims); + + c = other.cells.getClass(); + cId |= c==h ? 1 : c==t ? 2 : 4; + } + + switch(cId) { + case 1 : this.cells = new HomogenousSequence(TRUE, Ints.bestSet(d.capacity())); break; + case 2 : this.cells = new TreeSequence(); break; + default : this.cells = new RangeSequence(); + } + + mergeDefConds(m); + mergeDefConds(rest); + } + + /** + * Constructs a new matrix with the given dimensions and factory. + * The constructed matrix can store any kind of BooleanValue. + * + * @requires dimensions != null && factory != null + * @ensures this.dimensions' = dimensions && this.factory' = factory && + * this.elements' = [0..dimensions.capacity)->one FALSE + */ + BooleanMatrix(Dimensions dims, BooleanFactory factory) { + this.dims = dims; + this.factory = factory; + this.cells = new RangeSequence(); + } + + /** + * Constructs a new matrix with the given dimensions and factory, + * and initializes the indices in the given set to TRUE. + * The constructed matrix will be capable of storing only constants + * iff trueIndeces.equals(allIndices). Otherwise, it will be able to store any kind of BooleanValue + * ONLY at the indices given by allIndices. Any attempt to call {@link #set(int, BooleanValue) } on + * an index outside of allIndices may result in an IndexOutOfBoundsException. + * + * @requires allIndices.containsAll(trueIndices) + * @requires trueIndices is not modifiable using an external handle + * @requires dimensions != null && factory != null && trueIndices != null && allIndices != null + * @requires dimensions.validate(allIndices.min()) && dimensions.validate(allIndices.max()) + * @ensures this.dimensions' = dimensions && this.factory' = factory && + * this.elements' = [0..dimensions.capacity)->one FALSE ++ trueIndices -> one TRUE + */ + BooleanMatrix(Dimensions dims, BooleanFactory factory, IntSet allIndices, IntSet trueIndices) { + this.dims = dims; + this.factory = factory; + final int tsize = trueIndices.size(), asize = allIndices.size(); + if (tsize==asize) + this.cells = new HomogenousSequence(TRUE, trueIndices); + else { + this.cells = tsize==0 || asize/tsize >= 2 ? new ArraySequence(allIndices) : new RangeSequence(); + for(IntIterator iter = trueIndices.iterator(); iter.hasNext(); ) { + cells.put(iter.next(), TRUE); + } + } + } + + private void mergeDefConds(BooleanMatrix ... bms) { + mergeDefConds(FALSE, bms); + } + + private void mergeDefConds(BooleanValue of, BooleanMatrix ... bms) { + DefCond[] dcs = new DefCond[1 + bms.length]; + dcs[0] = this.defCond(); + for (int i = 0; i < bms.length; i++) { + dcs[i+1] = bms[i].defCond(); + this.defCond().addVars(bms[i].defCond().vars()); + } + this.defCond().setOverflows(of, DefCond.merge(factory, dcs)); + } + + /** + * Returns the dimensions of this matrix. + * @return this.dimensions + */ + public final Dimensions dimensions() { return dims; } + + /** + * Returns the factory used to construct all the non-constant + * entries in this matrix. + * @return this.factory + */ + public final BooleanFactory factory() { return factory; } + + /** + * Returns this.defCond + * @return this.defCond + */ + public final DefCond defCond() { return defCond; } + + public void setDefCond(DefCond dc) { + //[aaa] this.defCond = dc; + } + + /** + * Returns the number of non-FALSE entries in this matrix. + * @return #this.elements.(BooleanValue - FALSE) + */ + public final int density() { return cells.size(); } + + /** + * Returns an IndexedEntry-based view of the non-FALSE entries in this matrix. The returned + * iterator enumerates indexed entries that represent the non-FALSE entries in the matrix, in the ascending + * order of indeces. For example, suppose that the elements of this are 0->FALSE, 1->(a & b), 2->FALSE, 3->(c | d). Then, + * the Iterator will return two IndexedEntries, c1 then c2, such that c1.index=1 && c1.value = a & b and + * c2.index=3 && c.value = c | d. Calling {@link Iterator#remove()} on the returned iterator has the same effect + * as setting the entry obtained through the last call to {@link Iterator#next()} to FALSE. + * @return an iterator over IndexedEntries representing the non-FALSE entries in this matrix. + */ + public final Iterator> iterator() { + return cells.iterator(); + } + + /** + * Returns the set of all indices in this matrix that contain + * non-FALSE values. + * @return the set of all indices in this matrix that contain + * non-FALSE values. + */ + public final IntSet denseIndices() { + return cells.indices(); + } + + /** + * Return FALSE if value is null; otherwise return value itself. + * @return FALSE if value is null; otherwise return value itself. + */ + private final BooleanValue maskNull(BooleanValue value) { + return value == null ? FALSE : value; + } + + /** + * Returns the value at the given index, without checking that the index is in bounds. + * @return this.elements[index] + */ + private final BooleanValue fastGet(final int index) { + return maskNull(cells.get(index)); + } + + /** + * Returns the element at the specified index. + * @return this.elements[index] + * @throws IndexOutOfBoundsException index < 0 || index >= this.dimensions.capacity + */ + public final BooleanValue get(final int index) { + if (!dims.validate(index)) throw new IndexOutOfBoundsException(index + " is not a valid index."); + return maskNull(cells.get(index)); + } + + + /** + * Returns a new matrix each of whose entries is a negation of the + * corresponding entry in this matrix. + * + * @return { m: BooleanMatrix | m.dimensions=this.dimensions && m.factory = this.factory && + * all i: [0..m.dimensions.capacity) | m.elements[i] = !this.elements[i] } + */ + public final BooleanMatrix not() { + final BooleanMatrix negation = new BooleanMatrix(dims, factory, cells, cells); + negation.mergeDefConds(this); + + for (int i = 0, max = dims.capacity(); i < max; i++) { + BooleanValue v = cells.get(i); + if (v==null) + negation.cells.put(i, TRUE); + else if (v!=TRUE) + negation.cells.put(i, v.negation()); + } + + return negation; + } + + /** + * @throws IllegalArgumentException f != this.factory + */ + private static final void checkFactory(BooleanFactory f0, BooleanFactory f1) { + if (f0 != f1) throw new IllegalArgumentException("Incompatible factories: " + f0 + " and " + f1); + } + + /** + * @throws IllegalArgumentException !d0.equals(d1) + */ + private static final void checkDimensions(Dimensions d0, Dimensions d1) { + if (!d0.equals(d1)) throw new IllegalArgumentException("Incompatible dimensions: " + d0 + " and " + d1); + } + + /** + * Returns a new matrix such that an entry in the returned matrix represents a + * conjunction of the corresponding entries in this and other matrix. The effect + * of this method is the same as calling this.compose(ExprOperator.Binary.AND, other). + * + * @return { m: BooleanMatrix | m.dimensions = this.dimensions && m.factory = this.factory && + * all i: [0..m.dimensions.capacity) | + * m.elements[i] = this.elements[i] AND other.elements[i] } + * @throws NullPointerException other = null + * @throws IllegalArgumentException !other.dimensions.equals(this.dimensions) || this.factory != other.factory + */ + public final BooleanMatrix and(BooleanMatrix other) { + checkFactory(this.factory, other.factory); checkDimensions(this.dims, other.dims); + + final BooleanMatrix ret = new BooleanMatrix(dims, factory, cells, other.cells); + ret.mergeDefConds(this, other); + + final SparseSequence s1 = other.cells; + for(IndexedEntry e0 : cells) { + BooleanValue v1 = s1.get(e0.index()); + if (v1!=null) + ret.fastSet(e0.index(), factory.and(e0.value(), v1)); + } + return ret; + } + + /** + * Returns a new matrix such that an entry in the returned matrix represents a + * conjunction of the corresponding entries in this and other matrices. + * + * @requires all i: [0..others.length) | others[i].dimensions = this.dimensions && others[i].factory = this.factory + * @return others.length = 0 => m else + * { m: BooleanMatrix | m.dimensions = this.dimensions && m.factory = this.factory && + * all i: [0..m.dimensions.capacity) | m.elements[i] = AND(this.elements[i], others[int].elements[i]) } + * @throws NullPointerException others = null + * @throws IllegalArgumentException some m: others[int] | !m.dimensions.equals(this.dimensions) || m.factory != this.factory + */ + public final BooleanMatrix and(final BooleanMatrix...others) { + final BooleanMatrix ret = new BooleanMatrix(dims, this, others); + + for(IndexedEntry cell : cells) { + final BooleanAccumulator acc = BooleanAccumulator.treeGate(AND, cell.value()); + for(BooleanMatrix other : others) { + if (acc.add(other.fastGet(cell.index()))==BooleanConstant.FALSE) + break; + } + if (!acc.isShortCircuited()) { ret.fastSet(cell.index(), factory.accumulate(acc)); } + } + return ret; + } + + /** + * Returns a new matrix such that an entry in the returned matrix represents a + * combination of the corresponding entries in this and other matrix. The effect + * of this method is the same as calling this.compose(ExprOperator.Binary.OR, other). + * + * @return { m: BooleanMatrix | m.dimensions = this.dimensions && m.factory = this.factory && + * all i: [0..m.dimensions.capacity) | + * m.elements[i] = this.elements[i] OR other.elements[i] } + * @throws NullPointerException other = null + * @throws IllegalArgumentException !other.dimensions.equals(this.dimensions) || this.factory != other.factory + */ + public final BooleanMatrix or(BooleanMatrix other) { + checkFactory(this.factory, other.factory); checkDimensions(this.dims, other.dims); + + final BooleanMatrix ret = new BooleanMatrix(dims, factory, cells, other.cells); + ret.mergeDefConds(this, other); + + final SparseSequence retSeq = ret.cells; + for(IndexedEntry e0 : cells) { + BooleanValue v1 = other.cells.get(e0.index()); + if (v1==null) + retSeq.put(e0.index(), e0.value()); + else + retSeq.put(e0.index(), factory.or(e0.value(), v1)); + } + for(IndexedEntry e1 : other.cells) { + if (!cells.containsIndex(e1.index())) + retSeq.put(e1.index(), e1.value()); + } + return ret; + } + + /** + * Returns a new matrix such that an entry in the returned matrix represents a + * disjunction of the corresponding entries in this and other matrices. + * + * @requires all i: [0..others.length) | others[i].dimensions = this.dimensions && others[i].factory = this.factory + * @return others.length = 0 => m else + * { m: BooleanMatrix | m.dimensions = this.dimensions && m.factory = this.factory && + * all i: [0..m.dimensions.capacity) | m.elements[i] = OR(this.elements[i], others[int].elements[i]) } + * @throws NullPointerException others = null + * @throws IllegalArgumentException some m: others[int] | !m.dimensions.equals(this.dimensions) || m.factory != this.factory + */ + public final BooleanMatrix or(final BooleanMatrix... others) { + final BooleanMatrix ret = new BooleanMatrix(dims, this, others); + + for(IndexedEntry cell : cells) { + final BooleanAccumulator acc = BooleanAccumulator.treeGate(OR, cell.value()); + for(BooleanMatrix other : others) { + if (acc.add(other.fastGet(cell.index()))==BooleanConstant.TRUE) + break; + } + ret.fastSet(cell.index(), factory.accumulate(acc)); + } + + for(int i = 0, length = others.length; i < length; i++) { + for(IndexedEntry cell : others[i].cells) { + if (ret.cells.containsIndex(cell.index())) continue; + final BooleanAccumulator acc = BooleanAccumulator.treeGate(OR, cell.value()); + for(int j = i+1; j < length; j++) { + if (acc.add(others[j].fastGet(cell.index()))==BooleanConstant.TRUE) + break; + } + ret.fastSet(cell.index(), factory.accumulate(acc)); + } + } + + return ret; + } + + /** + * Returns the cross product of this and other matrix, using conjunction instead of + * multiplication. + * + * @return { m: BooleanMatrix | m = this x other } + * @throws NullPointerException other = null + * @throws IllegalArgumentException this.factory != other.factory + */ + public final BooleanMatrix cross(final BooleanMatrix other) { + checkFactory(this.factory, other.factory); + + final BooleanMatrix ret = new BooleanMatrix(dims.cross(other.dims), factory, cells, other.cells); + ret.mergeDefConds(this, other); + + if (cells.isEmpty() || other.cells.isEmpty()) return ret; + + final int ocap = other.dims.capacity(); + for(IndexedEntry e0 : cells) { + int i = ocap * e0.index(); + for(IndexedEntry e1: other.cells) { + BooleanValue conjunction = factory.and(e0.value(), e1.value()); + if (conjunction != FALSE) + ret.cells.put(i + e1.index(), conjunction); + } + } + + return ret; + } + + /** + * Updates the itrs and idxs arrays for the next step of the cross-product computation and returns a partial + * index based on the updated idxs values. + * @requires matrices.length = itrs.length = idxs.length + * @requires all m: matrices[int] | m.density() > 0 + * @requires currentIdx is a partial index based on the current value of idxs + * @ensures updates the itrs and idxs arrays for the next step cross-product computation + * @return a partial index based on the freshly updated idxs values. + */ + private static int nextCross(final BooleanMatrix[] matrices, final IntIterator[] itrs, final int[] idxs, int currentIdx) { + + int mult = 1; + for(int i = itrs.length-1; i >= 0; i--) { + if (itrs[i].hasNext()) { + final int old = idxs[i]; + idxs[i] = itrs[i].next(); + return currentIdx - mult*old + mult*idxs[i]; + } else { + itrs[i] = matrices[i].cells.indices().iterator(); + final int old = idxs[i]; + idxs[i] = itrs[i].next(); + currentIdx = currentIdx - mult*old + mult*idxs[i]; + mult *= matrices[i].dims.capacity(); + } + } + + return -1; + } + + /** + * Initializes the itrs and idxs arrays for cross-product computation and returns a partial + * index based on the freshly computed idxs values. + * @requires matrices.length = itrs.length = idxs.length + * @requires all m: matrices[int] | m.density() > 0 + * @ensures initializes the itrs and idxs arrays for cross-product computation + * @return a partial index based on the freshly computed idxs values. + */ + private static int initCross(final BooleanMatrix[] matrices, final IntIterator[] itrs, final int[] idxs) { + int mult = 1, idx = 0; + for(int i = matrices.length-1; i >= 0; i--) { + itrs[i] = matrices[i].cells.indices().iterator(); + idxs[i] = itrs[i].next(); + idx += mult*idxs[i]; + mult *= matrices[i].dims.capacity(); + } + return idx; + } + + /** + * Returns the cross product of this and other matrices, using conjunction instead of + * multiplication. + * @requires this.factory = others[int].factory + * @return others.length=0 => { m: BooleanMatrix | m.dimensions = this.dimensions && no m.elements } else + * { m: BooleanMatrix | m = this x others[0] x ... x others[others.length-1] } + * @throws NullPointerException others = null + * @throws IllegalArgumentException this.factory != others[int].factory + */ + public final BooleanMatrix cross(final BooleanMatrix...others) { + Dimensions retDims = dims; + boolean empty = cells.isEmpty(); + for(BooleanMatrix other : others) { + retDims = retDims.cross(other.dims); + empty = empty || other.cells.isEmpty(); + } + + final BooleanMatrix ret = new BooleanMatrix(retDims, this, others); + if (empty) return ret; + + final IntIterator[] itrs = new IntIterator[others.length]; + final int[] otherIdxs = new int[others.length]; + + final int ocap = retDims.capacity() / dims.capacity(); + + for(IndexedEntry cell : cells) { + final int idx = ocap * cell.index(); + for(int restIdx = initCross(others, itrs, otherIdxs); restIdx >= 0; restIdx = nextCross(others, itrs, otherIdxs, restIdx)) { + final BooleanAccumulator acc = BooleanAccumulator.treeGate(AND, cell.value()); + for(int i = others.length-1; i >= 0; i--) { + if (acc.add(others[i].fastGet(otherIdxs[i]))==BooleanConstant.FALSE) + break; + } + if (!acc.isShortCircuited()) { ret.fastSet(idx + restIdx, factory.accumulate(acc)); } + } + + } + + return ret; + } + + /** + * Sets the value at the specified index to the given value; + * returns the value previously at the specified position. + * It performs no index or null checking. + * + * @ensures this.elements'[index] = formula + */ + private final void fastSet(final int index, final BooleanValue formula) { + if (formula==FALSE) cells.remove(index); + else cells.put(index,formula); + } + + /** + * Returns the dot product of this and other matrix, using conjunction instead of + * multiplication and disjunction instead of addition. + * + * @return { m: BooleanMatrix | m = this*other } + * @throws NullPointerException other = null + * @throws IllegalArgumentException this.factory != other.factory + * @throws IllegalArgumentException dimensions incompatible for multiplication + */ + public final BooleanMatrix dot(final BooleanMatrix other) { + checkFactory(this.factory, other.factory); + + final BooleanMatrix ret = new BooleanMatrix(dims.dot(other.dims), factory, cells, other.cells); + ret.mergeDefConds(this, other); + + if (cells.isEmpty() || other.cells.isEmpty()) return ret; + + final SparseSequence mutableCells = ret.clone().cells; + final int b = other.dims.dimension(0); + final int c = other.dims.capacity() / b; + + for(IndexedEntry e0 : cells) { + int i = e0.index(); + BooleanValue iVal = e0.value(); + int rowHead = (i % b)*c, rowTail = rowHead + c - 1; + for(Iterator> iter1 = other.cells.iterator(rowHead, rowTail); iter1.hasNext();) { + IndexedEntry e1 = iter1.next(); + BooleanValue retVal = factory.and(iVal, e1.value()); + if (retVal != FALSE) { + int k = (i / b)*c + e1.index()%c; + if (retVal==TRUE) mutableCells.put(k, TRUE); + else { + BooleanValue kVal = mutableCells.get(k); + if (kVal != TRUE) { + if (kVal==null) { + kVal = BooleanAccumulator.treeGate(OR); + mutableCells.put(k, kVal); + } + ((BooleanAccumulator) kVal).add(retVal); + } + } + } + } + } + + // make mutable gates immutable + for(IndexedEntry e : mutableCells) { + if (e.value()!=TRUE) { + ret.fastSet(e.index(), factory.accumulate((BooleanAccumulator) e.value())); + } else { + ret.fastSet(e.index(), TRUE); + } + } + + return ret; + } + + /** + * Returns a formula stating that the entries in this matrix are a subset of + * the entries in the given matrix; i.e. the value of every entry in this matrix + * implies the value of the corresponding entry in the given matrix. + * @return { f: BooleanValue | f <=> (this.elements[0]=>other.elements[0]) AND ... + * AND (this.elements[this.dimensions.capacity-1]=>other.elements[this.dimensions.capacity-1])) + * @throws NullPointerException other = null + * @throws IllegalArgumentException !other.dimensions.equals(this.dimensions) || this.factory != other.factory + */ + public final BooleanValue subset(BooleanMatrix other, Environment env) { + checkFactory(this.factory, other.factory); checkDimensions(this.dims, other.dims); + final BooleanAccumulator a = BooleanAccumulator.treeGate(AND); + for(IndexedEntry e0: cells) { + if (a.add(factory.or(e0.value().negation(), other.fastGet(e0.index())))==FALSE) + return FALSE; + } + BooleanValue val = factory.accumulate(a); + return DefCond.ensureDef(factory, env, val, this.defCond(), other.defCond()); + } + + /** + * Returns a formula stating that the entries in this matrix are equivalent to + * the entries in the given matrix; i.e. the value of every entry in this matrix + * is true if and only if the value of the corresponding entry in the given matrix is true. + * The same formula can be obtained by calling factory.and(this.subset(other), other.subset(this)), + * but this method performs the operation more efficiently. + * @return { f: BooleanValue | f <=> (this.elements[0]<=>other.elements[0]) AND ... + * AND (this.elements[this.dimensions.capacity-1]<=>other.elements[this.dimensions.capacity-1])) + * @throws NullPointerException other = null + * @throws IllegalArgumentException !other.dimensions.equals(this.dimensions) || this.factory != other.factory + */ + public final BooleanValue eq(BooleanMatrix other, Environment env) { + BooleanValue val = factory.and(this.subset(other, env), other.subset(this, env)); + return DefCond.ensureDef(factory, env, val, this.defCond(), other.defCond()); + } + + /** + * Returns a matrix representing the asymmetric difference between + * the entries in this and the given matrix. The same matrix can + * be obtained by calling this.and(other.not()), but this method + * performs the operation more efficiently (intermediate + * values are not explicity created). + * @return { m: BooleanMatrix | m.dimensions = this.dimensions && m.factory = this.factory && + * all i: [0..m.dimensions.capacity) | + * m.elements[i] = this.elements[i] AND !other.elements[i] } + * @throws NullPointerException other = null + * @throws IllegalArgumentException !other.dimensions.equals(this.dimensions) || this.factory != other.factory + */ + public final BooleanMatrix difference(BooleanMatrix other) { + checkFactory(this.factory, other.factory); checkDimensions(this.dims, other.dims); + + final BooleanMatrix ret = new BooleanMatrix(dims, factory, cells, other.cells); + ret.mergeDefConds(this, other); + + for(IndexedEntry e0 : cells) { + ret.fastSet(e0.index(), factory.and(e0.value(), other.fastGet(e0.index()).negation())); + } + + return ret; + } + + /** + * Returns the transitive closure of this matrix. + * + * @return { m: BooleanMatrix | m = ^this } + * @throws UnsupportedOperationException #this.diensions != 2 || !this.dimensions.square() + */ + public final BooleanMatrix closure() { + if (dims.numDimensions() != 2 || !dims.isSquare()) { + throw new UnsupportedOperationException("#this.diensions != 2 || !this.dimensions.square()"); + } + if (cells.isEmpty()) + return clone(); + +// System.out.println("closure of " + this); + BooleanMatrix ret = this; + + // compute the number of rows in the matrix + int rowNum = 0; + final int rowFactor = dims.dimension(1); + for(IndexedEntry rowLead = cells.first(); + rowLead != null; rowLead = cells.ceil(((rowLead.index()/rowFactor) + 1) * rowFactor)) { + rowNum++; + } + + // compute closure using iterative squaring + for(int i = 1; i < rowNum; i*=2) { + ret = ret.or(ret.dot(ret)); + } +// System.out.println(ret); + return ret==this ? clone() : ret; + } + + /** + * Returns the transpose of this matrix. + * + * @return { m: BooleanMatrix | m = ~this } + * @throws UnsupportedOperationException #this.dimensions != 2 + */ + public final BooleanMatrix transpose() { + final BooleanMatrix ret = new BooleanMatrix(dims.transpose(), factory, cells, cells); + ret.mergeDefConds(this); + + final int rows = dims.dimension(0), cols = dims.dimension(1); + for (IndexedEntry e0 : cells) { + ret.cells.put((e0.index()%cols)*rows + (e0.index()/cols), e0.value()); + } + return ret; + } + + /** + * Returns a boolean matrix m such that m = this if the given condition evaluates + * to TRUE and m = other otherwise. + * + * @return { m: BooleanMatrix | m.dimensions = this.dimensions && + * all i: [0..m.dimensions.capacity) | + * m.elements[i] = condition => this.elements[i], other.elements[i] } + * @throws NullPointerException other = null || condition = null + * @throws IllegalArgumentException !other.dimensions.equals(this.dimensions) || this.factory != other.factory + */ + public final BooleanMatrix choice(BooleanValue condition, BooleanMatrix other) { + checkFactory(this.factory, other.factory); checkDimensions(this.dims, other.dims); + if (condition==TRUE) return this.clone(); + else if (condition==FALSE) return other.clone(); + + final BooleanMatrix ret = new BooleanMatrix(dims, factory); + final SparseSequence otherCells = other.cells; + for(IndexedEntry e0 : cells) { + BooleanValue v1 = otherCells.get(e0.index()); + if (v1==null) + ret.fastSet(e0.index(), factory.and(condition, e0.value())); + else + ret.fastSet(e0.index(), factory.ite(condition, e0.value(), v1)); + } + for(IndexedEntry e1 : other.cells) { + if (!cells.containsIndex(e1.index())) + ret.fastSet(e1.index(), factory.and(condition.negation(), e1.value())); + } + + BooleanValue of = factory.ite(condition, defCond().getOverflow(), other.defCond().getOverflow()); + BooleanValue accumOF = factory.ite(condition, defCond().getAccumOverflow(), other.defCond().getAccumOverflow()); + ret.defCond().setOverflows(of, accumOF); + return ret; + } + + /** + * Returns a matrix m such that the relational value of m is equal to the + * relational value of this projected on the specified columns. + * @requires column[int] in this.dimensions.dimensions[int] + * @requires this.dimensions.isSquare() + * @return { m: BooleanMatrix | [[m]] = project([[this]], columns) } + * @throws IllegalArgumentExceptions columns.length < 1 || !this.dimensions.isSquare() + * @throws NullPointerException columns = null + */ + public final BooleanMatrix project(Int[] columns) { + if (!dims.isSquare()) + throw new IllegalArgumentException("!this.dimensions.isSquare()"); + + final int rdnum = columns.length; + + if (rdnum < 1) + throw new IllegalArgumentException("columns.length < 1"); + + final Dimensions rdims = Dimensions.square(dims.dimension(0), rdnum); + final BooleanMatrix ret = new BooleanMatrix(rdims, factory, cells, cells); + ret.mergeDefConds(this); + + final int tdnum = dims.numDimensions(); + final int[] tvector = new int[tdnum]; + final int[] ivector = new int[rdnum]; + final int[] rvector = new int[rdnum]; + + int nVarCols = 1; + + // detect constant columns to avoid unnecessary looping; + for(int i = 0; i < rdnum; i++) { + if (columns[i].isConstant()) { + int value = columns[i].value(); + if (value < 0 || value >= tdnum) { + return ret; + } else { // distinguish constants by making them negative + ivector[i] = -value; + } + } else { + nVarCols *= tdnum; + } + } + + PROJECT : for(int i = 0; i < nVarCols; i++) { + BooleanValue colVal = TRUE; + for(int j = 0; j < rdnum; j++) { + // if the jth column is non-constant, check that it can take on the value ivector[j] + if (ivector[j] >= 0) { + colVal = factory.and(colVal, columns[j].eq(factory.integer(ivector[j]))); + if (colVal==FALSE) + continue PROJECT; + } + } + for(IndexedEntry e : cells) { + dims.convert(e.index(), tvector); + for(int j = 0; j < rdnum; j++) { + rvector[j] = tvector[StrictMath.abs(ivector[j])]; + } + int rindex = rdims.convert(rvector); + ret.fastSet(rindex, factory.or(factory.and(e.value(), colVal), ret.fastGet(rindex))); + } + for(int j = rdnum-1; j >= 0; j--) { // update ivector + // update ivector[j] only if the jth column is not constant + if (ivector[j]>=0) { + if (ivector[j]+1==tdnum) { + ivector[j] = 0; + } else { + ivector[j] += 1; + break; + } + } + } + } + + return ret; + } + + /** + * Returns a conjunction of the negated values between + * start, inclusive, and end, exclusive. + * @requires 0 <= start < end <= this.dimensions.capacity() + * @return !this.elements[start] && !this.elements[start+1] && ... && !this.elements[end-1] + */ + private final BooleanValue nand(int start, int end) { + final BooleanAccumulator g = BooleanAccumulator.treeGate(AND); + for(Iterator> iter = cells.iterator(start, end-1); iter.hasNext(); ) { + if (g.add(iter.next().value().negation())==FALSE) + return FALSE; + } + return factory.accumulate(g); + } + + /** + * Overrides the values in this matrix with those in other. + * Specifically, for each index i of the returned matrix m, + * m.elements[i] is true iff other.elements[i] is true or + * this.elements[i] is true and all elements of other + * that are in the same row as i are false. + * @return {m: BooleanMatrix | m.dimensions = this.dimensions && + * all i: [0..m.capacity()) | m.elements[i] = + * other.elements[i] || + * this.elements[i] && !OR(other.elements[row(i)]) } + * where other.elements[row(i)] selects all elements of other + * that are in the same row as i. + * @throws NullPointerException other = null + * @throws IllegalArgumentException other.dimensions != this.dimensions + */ + public final BooleanMatrix override(BooleanMatrix other) { + checkFactory(this.factory, other.factory); checkDimensions(this.dims, other.dims); + if (other.cells.isEmpty()) return this.clone(); + + final BooleanMatrix ret = new BooleanMatrix(dims, factory, cells, other.cells); + ret.mergeDefConds(this, other); + + ret.cells.putAll(other.cells); + final int rowLength = dims.capacity() / dims.dimension(0); + int row = -1; + BooleanValue rowVal = BooleanConstant.TRUE; + for(IndexedEntry e0 : cells) { + int e0row = e0.index() / rowLength; + if (row != e0row) { + row = e0row; + rowVal = other.nand(row*rowLength, (row+1)*rowLength); + } + ret.fastSet(e0.index(), factory.or(ret.fastGet(e0.index()), + factory.and(e0.value(), rowVal))); + } + return ret; + } + + /** + * Overrides the values in this matrix with those in other. + * @return others.length = 0 => { m: BooleanMatrix | m.dimensions = this.dimensions && m.elements = this.elements) else + * others.length = 1 => {m: BooleanMatrix | m.dimensions = this.dimensions && + * all i: [0..m.capacity()) | m.elements[i] = + * other.elements[i] || this.elements[i] && !OR(other.elements[rowOf(i)]) } else + * this.override(others[0).override(others[1..others.length)) + * @throws NullPointerException others = null + * @throws IllegalArgumentException others[int].factory != this.factory or others[int].dimensions != this.dimensions + */ + public final BooleanMatrix override(BooleanMatrix... others) { + if (others.length==0) return clone(); + final BooleanMatrix[] matrices = Containers.copy(others, 0, new BooleanMatrix[others.length+1], 1, others.length); + matrices[0] = this; + for(int part = matrices.length; part > 1; part -= part/2) { + final int max = part-1; + for(int i = 0; i < max; i += 2) { + matrices[i/2] = matrices[i].override(matrices[i+1]); + } + if (max%2==0) { // even max => odd number of entries + matrices[max/2] = matrices[max]; + } + } + return matrices[0]; + } + + /** + * Returns an Int that represents the cardinality (number of non-FALSE entries) of this + * matrix using this.factory.intEncoding. + * @return {i: Int | [[i]] = sum({v: elements[int] | if [[v]] then 1 else 0}) } + */ + public final Int cardinality() { + final Int ret = factory.sum(cells.values()); + BooleanValue accum = DefCond.merge(factory, ret.defCond(), this.defCond()); + ret.defCond().setOverflows(ret.defCond().getOverflow(), accum); + return ret; + } + + /** + * Returns a BooleanValue that constrains at least one value in this.elements to be true. The + * effect of this method is the same as calling this.orFold(). + * @return { f: BooleanValue | f <=> this.elements[0] || ... || this.elements[this.dimensions.capacity-1] } + */ + public final BooleanValue some(Environment env) { + final BooleanAccumulator g = BooleanAccumulator.treeGate(OR); + for(IndexedEntry e : cells) { + if (g.add(e.value())==TRUE) + return TRUE; + } + final BooleanValue val = factory.accumulate(g); + return DefCond.ensureDef(factory, env, val, this.defCond()); + } + + /** + * Returns a BooleanValue that constrains at most one value in this.elements to be true. + * The effect of this method is the same as calling this.factory.or(this.one(), this.none()). + * @return { f: BooleanValue | f <=> this.one() || this.none() } + */ + public final BooleanValue lone(Environment env) { + if (cells.isEmpty()) + return TRUE; + else { + final BooleanAccumulator g = BooleanAccumulator.treeGate(AND); + + BooleanValue partial = FALSE; + for(IndexedEntry e: cells) { + if (g.add(factory.or(e.value().negation(), partial.negation()))==FALSE) + return FALSE; + partial = factory.or(partial, e.value()); + } + + final BooleanValue val = factory.accumulate(g); + return DefCond.ensureDef(factory, env, val, this.defCond()); + } + } + + /** + * Returns a BooleanValue that constraints exactly one value in this.elements to be true. + * @return { f: BooleanValue | f <=> #this.elements[int] = 1 } + */ + public final BooleanValue one(Environment env) { + if (cells.isEmpty()) + return FALSE; + else { + final BooleanAccumulator g = BooleanAccumulator.treeGate(AND); + + BooleanValue partial = FALSE; + for(IndexedEntry e: cells) { + if (g.add(factory.or(e.value().negation(), partial.negation()))==FALSE) + return FALSE; + partial = factory.or(partial, e.value()); + } + g.add(partial); + + final BooleanValue val = factory.accumulate(g); + return DefCond.ensureDef(factory, env, val, this.defCond()); + } + } + + /** + * Returns a BooleanValue that constraints all values in this.elements to be false. + * The effect of this method is the same as calling this.factory.not(this.some()). + * @return { f: BooleanValue | f <=> !(this.elements[0] || ... || !this.elements[this.dimensions.capacity-1]) } + */ + public final BooleanValue none(Environment env) { + env.negate(); + BooleanValue val = some(env).negation(); + env.negate(); + return val; + } + + /** + * Sets the specified index to the given value. + * + * @requires value in this.factory.components + * @ensures this.elements'[index] = value + * @throws NullPointerException value = null + * @throws IllegalArgumentException the given is a formula, and this matrix accepts only constants + * @throws IndexOutOfBoundsException the given index does not belong to the set of indices at which + * this matrix can store non-FALSE values. + */ + public final void set(final int index, final BooleanValue value) { + if (!dims.validate(index)) throw new IndexOutOfBoundsException("index < 0 || index >= this.dimensions.capacity"); + if (value==null) throw new NullPointerException("formula=null"); + if (value==FALSE) + cells.remove(index); + else + cells.put(index,value); + } + + /** + * Returns a copy of this boolean matrix. + * @return {m: BooleanMatrix - this | m.dimensions = this.dimensions && + * m.elements = copy of this.elements } + */ + public BooleanMatrix clone() { + try { + final BooleanMatrix ret = new BooleanMatrix(dims, factory, cells.clone()); + ret.mergeDefConds(this); + return ret; + } catch (CloneNotSupportedException e) { + throw new InternalError(); // unreachable code. + } + } + + /** + * @see java.lang.Object#toString() + */ + public String toString() { + final StringBuilder buff = new StringBuilder("dimensions: "); + buff.append(dims); + buff.append(", elements: "); + buff.append(cells); + return buff.toString(); + } + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/bool/BooleanValue.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/bool/BooleanValue.java new file mode 100644 index 00000000..ab5fcf6b --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/bool/BooleanValue.java @@ -0,0 +1,72 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.engine.bool; + +/** + * Represents a boolean value, which may be a {@link kodkod.engine.bool.BooleanFormula formula} + * or a {@link kodkod.engine.bool.BooleanConstant constant}. Boolean formulas are produced by + * {@link kodkod.engine.bool.BooleanFactory circuit factories}. Each value is associated with + * an integer label; the labels are unique within a given factory. + * A boolean value with a negative label -|l| represents the negation of the value with the positive + * label |l|. Non-constant values are not shared among factories. + * + * @specfield op: Operator + * @specfield label: [-Integer.MAX_VALUE, Integer.MAX_VALUE] + * @invariant no c: BooleanValue - this | some components.c & components.this && c.label = this.label + * @author Emina Torlak + */ +public abstract class BooleanValue implements Comparable { + + BooleanValue() {} + + /** + * Returns the negation of this boolean value + * @return { f: BooleanFormula | [[f]] = ![[this]] } + */ + abstract BooleanValue negation(); + + /** + * Returns the label for this value. + * @return this.label + */ + public abstract int label(); + + /** + * Returns the operator representing the function + * computed by this gate. + * @return this.op + */ + public abstract Operator op(); + + /** + * Boolean components are ordered according to their labels. + * Note that the ordering is well defined on components produced by the same factory. + * Specifically, this comparison function is consistent with equals for the components + * produced by the same factory, but may not be for the components produced by different factories. + * @return 0 if the label of this and other are the same, a negative + * integer if the label of this is smaller than the label of other; and + * a positive integer otherwise. + */ + public final int compareTo(BooleanValue other) { + return label() - other.label(); + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/bool/BooleanVariable.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/bool/BooleanVariable.java new file mode 100644 index 00000000..0176a21c --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/bool/BooleanVariable.java @@ -0,0 +1,131 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.engine.bool; + +import java.util.Iterator; + +import kodkod.util.collections.Containers; +import kodkod.util.ints.Ints; + + +/** + * Represents a boolean variable. + * + * @invariant op = Operator.VAR + * @invariant no inputs && label in [1, ..., Integer.MAX_VALUE) + * @author Emina Torlak + */ +public final class BooleanVariable extends BooleanFormula { + final int label; + private final int hashcode; + + /** + * Constructs a new BooleanVariable with the given label. + * @requires label != 0 + * @ensures this.label' = label + */ + BooleanVariable(int label) { + super(null); + assert label != 0; + this.label = label; + this.hashcode = Ints.superFastHash(label); + } + + /** + * Returns a hash of this variable's label. + * @return Ints.superFastHash(this.label) + */ + @Override + int hash(Operator op) { + return hashcode; + } + + /** + * Returns the label for this value. + * @return this.label + */ + @Override + public int label() { return label; } + + /** + * Returns a string representation of this variable. + * @return a string representation of this variable. + */ + public String toString() { + return Integer.toString(label); + } + + /** + * Passes this value and the given + * argument value to the visitor, and returns the resulting value. + * @return the value produced by the visitor when visiting this node + * with the given argument. + */ + @Override + public T accept(BooleanVisitor visitor, A arg) { + return visitor.visit(this, arg); + } + + /** + * Returns the VAR operator. + * @return Operator.VAR + */ + @Override + public Operator op() { + return Operator.VAR; + } + + /** + * Returns an empty iterator. + * @return an empty iterator + */ + @Override + public Iterator iterator() { + return Containers.emptyIterator(); + } + + /** + * Returns 0. + * @return 0 + */ + @Override + public int size() { + return 0; + } + + /** + * Throws an IndexOutOfBoundsException. + * @throws IndexOutOfBoundsException + */ + @Override + public BooleanFormula input(int i) { + throw new IndexOutOfBoundsException(); + } + + /** + * Returns a hashcode for this variable. + * @return a hashcode for this variable. + */ + public int hashCode() { + return hashcode; + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/bool/BooleanVisitor.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/bool/BooleanVisitor.java new file mode 100644 index 00000000..df4867f0 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/bool/BooleanVisitor.java @@ -0,0 +1,57 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.engine.bool; + +/** + * Visits {@link kodkod.engine.bool.BooleanFormula boolean formulas}. + * In addition to passing themselves as the argument to the visitor, + * the boolean values also pass along satelite information of type A. + * + * @author Emina Torlak + */ +public interface BooleanVisitor { + + /** + * Visits the multigate and returns the result. + * @return the result of visiting the given multigate + */ + public T visit(MultiGate multigate, A arg); + + /** + * Visits the if-then-else gate and returns the result. + * @return the result of visiting the given ITEGate + */ + public T visit(ITEGate ite, A arg); + + /** + * Visits the inverter and returns the result. + * @return the result of visiting the given inverter + */ + public T visit(NotGate negation, A arg); + + /** + * Visits the variable and returns the result. + * @return the result of visiting the given variable + */ + public T visit(BooleanVariable variable, A arg); + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/bool/CBCFactory.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/bool/CBCFactory.java new file mode 100644 index 00000000..7b509d5a --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/bool/CBCFactory.java @@ -0,0 +1,670 @@ + +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package kodkod.engine.bool; + +import static kodkod.engine.bool.BooleanConstant.FALSE; +import static kodkod.engine.bool.BooleanConstant.TRUE; +import static kodkod.engine.bool.Operator.AND; +import static kodkod.engine.bool.Operator.CONST; +import static kodkod.engine.bool.Operator.ITE; +import static kodkod.engine.bool.Operator.NOT; +import static kodkod.engine.bool.Operator.OR; +import static kodkod.engine.bool.Operator.VAR; + +import java.util.Iterator; +import java.util.Set; + +import kodkod.ast.operator.ExprOperator; +import kodkod.engine.bool.Operator.Nary; +import kodkod.util.collections.CacheSet; +import kodkod.util.collections.IdentityHashSet; + + +/** + * A factory for creating variables, multigates, and if-then-else gates. + * @specfield values: set (BooleanVariable + MultiGate + ITEGate) + * @specfield cmpMax: int // the maximum number of comparisons made when comparing circuits for equality + * @invariant no disj factory, factory' : CircuitFactory | some factory.values & factory'.values + * @author Emina Torlak + */ +final class CBCFactory { + + /** + * Sets used as `scrap paper' for gate comparisons. Its capacity is 2^(depth), where + * depth is the depth to which gates should be checked for equality. + */ + private final Set scrap0, scrap1; + /** + * Stores input variables. + * @invariant all i: [1..iLits.size()] | vars[i-1].positive.label = i + */ + private final BooleanVariable[] vars; + /** + * Caches the AND, OR, and ITE gates. + * @invariant all i: [0..2] | c[i].op.ordinal = i + */ + private final CacheSet[] cache; + private int label, cmpMax; + + + + /** + * Constructs a CircuitFactory using the given max comparison parameter, initialized + * to contain the given number of variables. + * @requires cmpMax > 0 && numVars >= 0 + * @ensures #this.values' = numVars && this.values in BooleanVariable + * @ensures this.cmpMax' = cmpMax + */ + @SuppressWarnings("unchecked") CBCFactory(int numVars, int cmpMax) { + assert cmpMax > 0 && numVars >= 0; + this.cmpMax = cmpMax; + this.label = numVars + 1; + vars = new BooleanVariable[numVars]; + for(int i = 0; i < numVars; i++) { + vars[i]= new BooleanVariable(i+1); + } + scrap0 = new IdentityHashSet(cmpMax); + scrap1 = new IdentityHashSet(cmpMax); + cache = new CacheSet[]{new CacheSet(), new CacheSet(), new CacheSet()}; + } + + /** + * Returns the cache for gates with the given operator. + * @requires op in AND + OR + ITE + * @return cache[op.ordinal] + */ + private CacheSet opCache(Operator op) { + return cache[op.ordinal]; + } + + /** + * Sets this.cmpMax to the given value. + * @requires cmpMax > 0 + * @ensures this.cmpMax' = cmpMax + */ + void setCmpMax(int cmpMax) { + assert cmpMax > 0; + this.cmpMax = cmpMax; + } + + /** + * Returns this.cmpMax. + * @return this.cmpMax + */ + int cmpMax() { return cmpMax; } + + /** + * Removes all MultiGates and ITEGates from this.factory. + * @ensures this.values' = this.values & BooleanVariable + */ + void clear() { + label = vars.length+1; + cache[0].clear(); + cache[1].clear(); + cache[2].clear(); + scrap0.clear(); + scrap1.clear(); + } + + /** + * Returns true if the given value + * is a valid argument to one of the assemble + * methods. Otherwise returns false. + * @return v in this.values + this.values.negation + BooleanConstant + */ + boolean canAssemble(BooleanValue v) { + if (v.op()==CONST) return true; + if (v.label() < 0) v = v.negation(); + final int absLit = v.label(); + if (absLit <= vars.length) { + return v == vars[absLit-1]; + } else { + final BooleanFormula g = (BooleanFormula) v; + for(Iterator gates = opCache(g.op()).get(g.hashCode()); gates.hasNext(); ) { + if (gates.next()==g) return true; + } + return false; + } + } + + /** + * Returns the number of variables in this factory. + * @return #(this.values & BooleanVariable) + */ + int numVars() { return vars.length; } + + /** + * Returns the boolean variable from this.values with the given label. + * @requires 0 < label <= #(this.values & BooleanVariable) + * @return (this.values & BooleanVariable).label + */ + BooleanVariable variable(int label) { + return vars[label-1]; + } + + /** + * Returns a boolean value whose meaning is (if [[i]] then [[t]] else [[e]]). + * @requires i + t + e in (this.values + this.values.negation + BooleanConstant) + * @return v: BooleanValue | [[v]] = if [[i]] then [[t]] else [[e]] + * @ensures v in BooleanFormula - NotGate => this.values' = this.values + v, this.values' = this.values + * @throws NullPointerException - any of the arguments are null + */ + BooleanValue assemble(BooleanValue i, BooleanValue t, BooleanValue e) { + if (i==TRUE || t==e) return t; + else if (i==FALSE) return e; + else if (t==TRUE || i==t) return assemble(OR, i, e); + else if (t==FALSE || i.label()==-t.label()) return assemble(AND, i.negation(), e); + else if (e==TRUE || i.label()==-e.label()) return assemble(OR, i.negation(), t); + else if (e==FALSE || i==e) return assemble(AND, i, t); + else { + final BooleanFormula f0 = (BooleanFormula) i, f1 = (BooleanFormula) t, f2 = (BooleanFormula) e; + final int hash = ITE.hash(f0, f1, f2); + + for(Iterator gates = opCache(ITE).get(hash); gates.hasNext();) { + BooleanFormula gate = gates.next(); + if (gate.input(0)==i && gate.input(1)==t && gate.input(2)==e) + return gate; + } + final BooleanFormula ret = new ITEGate(label++, hash, f0, f1, f2); + opCache(ITE).add(ret); + return ret; + } + } + + /** + * Returns a boolean value whose meaning is ([[v0]] op [[v1]]). + * @requires v0 + v1 in (this.values + this.values.negation + BooleanConstant) + * @return v: BooleanValue | [[v]] = [[v0]] op [[v1]] + * @ensures v in BooleanFormula - NotGate => this.values' = this.values + v, this.values' = this.values + * @throws NullPointerException - any of the arguments are null + */ + BooleanValue assemble(Operator.Nary op, BooleanValue v0, BooleanValue v1) { + final BooleanValue l, h; + if (v0.op().ordinal < v1.op().ordinal) { + l = v0; h = v1; + } else { + l = v1; h = v0; + } + if (h.op()==CONST) + return h==op.identity() ? l : h; + else + return assembler(l.op(), h.op()).assemble(op, (BooleanFormula)l, (BooleanFormula)h); + } + + /** + * Returns a boolean value with the same meaning as the given accumulator. + * @requires acc.components in (this.values + this.values.negation + BooleanConstant) + * @return v: BooleanValue | [[v]] = [[acc]] + * @ensures v in BooleanFormula - NotGate => this.values' = this.values + v, this.values' = this.values + * @throws NullPointerException - any of the arguments are null + */ + @SuppressWarnings("unchecked") + BooleanValue assemble(BooleanAccumulator acc) { + final int asize = acc.size(); + final Operator.Nary op = acc.op; + switch(asize) { + case 0 : return op.identity(); + case 1 : return acc.iterator().next(); + case 2 : + final Iterator inputs = acc.iterator(); + return assemble(op, inputs.next(), inputs.next()); + default : + @SuppressWarnings("rawtypes") + final int hash = op.hash((Iterator)acc.iterator()); + if (asize > cmpMax) { + for(Iterator gates = opCache(op).get(hash); gates.hasNext(); ) { + BooleanFormula g = gates.next(); + if (g.size()==asize && ((NaryGate) g).sameInputs(acc.iterator())) { + return g; + } + } + } else { + LOOKUP: for(Iterator gates = opCache(op).get(hash); gates.hasNext(); ) { + BooleanFormula g = gates.next(); + if (g.size()==asize && ((NaryGate) g).sameInputs(acc.iterator())) { + return g; + } else if (g.size() < asize) { + scrap0.clear(); + g.flatten(op, scrap0, cmpMax); + if (scrap0.size()==asize) { + for(BooleanValue v : acc) { + if (!scrap0.contains(v)) + continue LOOKUP; + } + return g; + } + } + } + } + final BooleanFormula ret = new NaryGate(acc, label++, hash); + opCache(acc.op).add(ret); + return ret; + } + } + + /** + * Given two operators, op0 and op1, returns an Assembler + * which contains the creator method for expressions of the form v0 op v1 where + * op in ExprOperator.Nary and v0.op = op0 and v1.op = op1. + * @requires op0 <= op1 && no (op0 + op1) & CONST + * @requires op0 != null && op1 != null + * @return a Assembler which contains the creator method for expressions of the form v0 op v1 where + * op in ExprOperator.Nary and v0.op = op0 and v1.op = op1. + */ + private Assembler assembler(Operator op0, Operator op1) { + return ASSEMBLERS[(op0.ordinal << 2) + op1.ordinal - ( (op0.ordinal*(op0.ordinal-1) >> 1 ))]; + } + + /** + * Returns a BooleanFormula f such that [[f]] = f0 op f1. The method + * requires that the formulas f0 and f1 be already reduced with respect to op. + * A new formula is created and cached iff the circuit with the meaning + * [[f0]] op [[f1]] has not already been created. + * @requires f0 and f1 have already been reduced with respect to op; i.e. + * f0 op f1 cannot be reduced to a constant or a simple circuit + * by applying absorption, idempotence, etc. laws to f0 and f1. + * @return f : BooleanFormula | [[f]] = [[f0]] op [[f1]] + * @ensures f !in this.values => this.values' = this.values + f, + * this.values' = this.values + */ + private BooleanFormula cache(Operator.Nary op, BooleanFormula f0, BooleanFormula f1) { + final BooleanFormula l, h; + if (f0.label() gates = opCache(op).get(hash); gates.hasNext(); ) { + BooleanFormula gate = gates.next(); + if (gate.size()==2 && gate.input(0)==l && gate.input(1)==h) + return gate; + else { + scrap1.clear(); + gate.flatten(op, scrap1, cmpMax); + if (scrap0.equals(scrap1)) + return gate; + } + } + } else { + for(Iterator gates = opCache(op).get(hash); gates.hasNext(); ) { + BooleanFormula gate = gates.next(); + if (gate.size()==2 && gate.input(0)==l && gate.input(1)==h) + return gate; + } + } + final BooleanFormula ret = new BinaryGate(op, label++, hash, l, h); + opCache(op).add(ret); + return ret; + } + + /** + * Wrapper for a method that generates boolean values + * out of existing gates, using AND and OR operators. + * @author Emina Torlak + */ + private static abstract class Assembler { + + /** + * Returns a BooleanValue whose meaning is [[f0]] op [[f1]]. A + * new circuit is created and cached iff [[f0]] op [[f1]] cannot be reduced + * to a simpler value and a circuit with equivalent meaning has not already been created. + * @requires f0.op <= f1.op && f0 + f1 in CircuitFactory.this.values + CircuitFactory.this.values.negation + * @return { v: BooleanValue | [[v]] = [[f0]] op [[f1]] } + * @ensures (no v: CircuitFactory.this.values | [[v]] = [[f0]] op [[f1]]) => + * CircuitFactory.this.values' = CircuitFactory.this.values + {v: BooleanValue | [[v]] = [[f0]] op [[f1]]} => + * CircuitFactory.this.values' = CircuitFactory.this.values + */ + abstract BooleanValue assemble(Operator.Nary op, BooleanFormula f0, BooleanFormula f1); + } + + /** + * Performs common simplifications on circuits of the form AND op X or OR op X, + * where X can be any operator other than CONST (J stands for 'junction'). + */ + private final Assembler JoX = new Assembler() { + /** + * Performs the following reductions, if possible. Note that + * these reductions will be possible only if f0 was created after f1 (i.e. |f0.label| > |f1.label|). + * (a & b) & a = a & b (a & b) & !a = F (a & b) | a = a + * (a | b) | a = a | b (a | b) | !a = T (a | b) & a = a + * @requires f0.op in (AND + OR) + */ + BooleanValue assemble(Nary op, BooleanFormula f0, BooleanFormula f1) { + assert f0.op().ordinal < 2; + final int label = f1.label(); + if (f0.contains(f0.op(), label, cmpMax) > 0) + return op==f0.op() ? f0 : f1; + else if (op==f0.op() && f0.contains(op, -label, cmpMax)>0) + return op.shortCircuit(); + else + return cache(op, f0, f1); + } + }; + + /** + * Performs common simplifications on circuits of the form AND op OR. + */ + private final Assembler AoO = new Assembler() { + /** + * Performs the following reductions, if possible, along with JoX reductions. + * (aj & ... & ak) & (a1 | ... | an) = (aj & ... & ak) where 1 <= j <= k <= n + * (a1 & ... & an) | (aj | ... | ak) = (aj | ... | ak) where 1 <= j <= k <= n + * @requires f0.op = AND && f1.op = OR + */ + BooleanValue assemble(Nary op, BooleanFormula f0, BooleanFormula f1) { + assert f0.op() == AND && f1.op() == OR; + scrap0.clear(); + scrap1.clear(); + f0.flatten(f0.op(), scrap0, cmpMax); + f1.flatten(f1.op(), scrap1, cmpMax); + for(BooleanFormula formula : scrap1) { + if (scrap0.contains(formula)) + return op==AND ? f0 : f1; + } + return (f0.label() < f1.label()) ? JoX.assemble(op, f1, f0) : JoX.assemble(op, f0, f1); + } + }; + + /** + * Performs common simplifications on circuits of the form AND op AND or OR op OR. + */ + private final Assembler JoJ = new Assembler() { + /** + * Performs the following reductions, if possible, along with the JoX reductions. + * (a1 & ... & an) & (aj & ... & ak) = (a1 & ... & an) where 1 <= j <= k <= n + * (a1 & ... & an) | (aj & ... & ak) = (aj & ... & ak) where 1 <= j <= k <= n + * (a1 | ... | an) | (aj | ... | ak) = (a1 | ... | an) where 1 <= j <= k <= n + * (a1 | ... | an) & (aj | ... | ak) = (aj | ... | ak) where 1 <= j <= k <= n + * @requires f0.op = f1.op && (f0+f1).op in (AND + OR) + */ + BooleanValue assemble(Nary op, BooleanFormula f0, BooleanFormula f1) { + assert f0.op() == f1.op(); + if (f0==f1) return f0; + final Operator fop = f0.op(); + scrap0.clear(); + scrap1.clear(); + f0.flatten(fop, scrap0, cmpMax); + f1.flatten(fop, scrap1, cmpMax); + if (scrap0.size() < scrap1.size() && scrap1.containsAll(scrap0)) + return op==fop ? f1 : f0; + else if (scrap0.size() >= scrap1.size() && scrap0.containsAll(scrap1)) + return op==fop ? f0 : f1; + else if (f0.label() |f1.label|). + * !(a | b) & a = F !(a | b) & !a = !(a | b) + * !(a & b) | a = T !(a & b) | !a = !(a & b) + * @requires f0.op = NOT + */ + BooleanValue assemble(Nary op, BooleanFormula f0, BooleanFormula f1) { + assert f0.op() == NOT ; + final int label = f1.label(); + if (f0.input(0).contains(op.complement(), label, cmpMax)>0) return op.shortCircuit(); + else if (f0.input(0).contains(op.complement(), -label, cmpMax)>0) return f0; + else return cache(op, f0, f1); + } + }; + + /** + * Performs common simplifications on circuits of the form NOT op NOT. + */ + private final Assembler NoN = new Assembler() { + /** + * Performs the following reductions, if possible, along with NoX reductions. + * !a & !a = !a !a | !a = !a + * @requires f1.op + f0.op = NOT + */ + BooleanValue assemble(Nary op, BooleanFormula f0, BooleanFormula f1) { + assert f0.op() == NOT && f1.op() == NOT; + if (f0==f1) return f0; + else if (f0.label() < f1.label()) // f0 created after f1 + return NoX.assemble(op, f0, f1); + else + return NoX.assemble(op, f1, f0); + } + }; + + /** + * Performs common simplifications on circuits of the form NOT op VAR. + */ + private final Assembler NoV = new Assembler() { + /** + * Performs the following reductions, if possible, along with NoX reductions. + * !a & a = F !a | a = T + * @requires f1.op = NOT && f1.op = VAR + */ + BooleanValue assemble(Nary op, BooleanFormula f0, BooleanFormula f1) { + assert f0.op() == NOT && f1.op() == VAR; + if (f0.label()==-f1.label()) return op.shortCircuit(); + else return NoX.assemble(op, f0, f1); + } + }; + + + /** + * Performs common simplifications on circuits of the form X op X. + */ + private final Assembler XoX = new Assembler() { + /** + * Performs the following reductions, if possible. + * a & a = a a | a = a + * @requires f0.op = f1.op + */ + @Override + BooleanValue assemble(Nary op, BooleanFormula f0, BooleanFormula f1) { + assert f0.op()==f1.op(); + return (f0==f1) ? f0 : cache(op, f0, f1); + } + }; + + + /** + * 15 Assembler entires representing all possible composition combinations of + * non-constant vertices using the operators AND and OR. Note that there + * are 15 of them rather than 25 because of the v0.op <= v1.op requirement + * of the {@link Assembler#assemble(ExprOperator.Nary, BooleanFormula, BooleanFormula)} method. + */ + private final Assembler[] ASSEMBLERS = { + JoJ, /* AND op AND */ + AoO, /* AND op OR */ + JoI, /* AND op ITE */ + JoN, /* AND op NOT */ + JoX, /* AND op VAR */ + JoJ, /* OR op OR */ + JoI, /* OR op ITE */ + JoN, /* OR op NOT */ + JoX, /* OR op VAR */ + XoX, /* ITE op ITE */ + IoN, /* ITE op NOT */ + IoV, /* ITE op VAR */ + NoN, /* NOT op NOT */ + NoV, /* NOT op VAR */ + XoX /* VAR op VAR */ + }; + +// /** +// * Returns true if for all inputs i1 in f1 there is a corresponding input i0 in +// * f0 such that i0.label = i1.label +// * @requires #f0.inputs <= f1.inputs +// */ +// private static boolean containsAll(BooleanFormula f0, BooleanFormula f1) { +// final int s0 = f0.size(), s1 = f1.size(); +// for(int i = 0, j = 0; i=0;) { +// final int l0 = f0.input(i).label(), l1 = -f1.input(j).label(); +// if (l0 < l1) { +// i++; +// } else if (l0 == l1) { +// i++; j--; +// } else { +// return false; +// } +// } +// return true; +// } +// +// /** +// * Returns true if there are inputs i0 in f0 and i1 in f1 such that i0.label = i1.label +// * @requires #f0.inputs <= f1.inputs +// */ +// private static boolean containsSome(BooleanFormula f0, BooleanFormula f1) { +// final int s0 = f0.size(), s1 = f1.size(); +// for(int i = 0, j = 0; i=0;) { +// final int l0 = f0.input(i).label(), l1 = -f1.input(j).label(); +// if (l0 < l1) { +// i++; +// } else if (l0 == l1) { +// return true; +// } else { +// j--; +// } +// } +// return false; +// } +} \ No newline at end of file diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/bool/DefCond.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/bool/DefCond.java new file mode 100644 index 00000000..5c75367a --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/bool/DefCond.java @@ -0,0 +1,117 @@ +package kodkod.engine.bool; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import kodkod.ast.Variable; +import kodkod.ast.operator.Quantifier; +import kodkod.engine.fol2sat.Environment; + +public class DefCond { + + /* ------------------------------------------------------------------------------------ */ + /* used during translation */ + /* ------------------------------------------------------------------------------------ */ + + private BooleanValue overflow = BooleanConstant.FALSE; + private BooleanValue accumOverflow = BooleanConstant.FALSE; + private Set vars = new HashSet(); + + public BooleanValue getOverflow() { return overflow; } + public BooleanValue getAccumOverflow() { return accumOverflow; } + public void setOverflows(BooleanValue of, BooleanValue accumOF) { + this.overflow = of; + this.accumOverflow = accumOF; + } + + public void addVar(Variable v) { vars.add(v); } + public void addVars(Collection vars) { this.vars.addAll(vars); } + public Set vars() { return vars; } + + /** + * ORs overflow circuits of this object ( + * this.mergedOverflow), a given other object ( + * other.mergedOverflow), and a given overflow circuit ( + * of) + */ + public static BooleanValue merge(BooleanFactory factory, BooleanValue accum, DefCond ... conds) { + BooleanValue ret = accum; + for (DefCond dc : conds) { + ret = factory.or(ret, dc.accumOverflow); + } + return ret; + } + + public static BooleanValue merge(BooleanFactory factory, DefCond ... conds) { + return merge(factory, BooleanConstant.FALSE, conds); + } + + /** + * If overflow checking is disabled returns value. Otherwise, + * returns a conjunction of value, lhs.accumOverflow, + * and rhs.accumOverflow. + * + * ~~~ NOTE ~~~: Every time a BooleanValue is returned as a result of an operation + * over Ints, one of the ensureNoOverflow methods + * should be called. + */ + public static BooleanValue ensureDef(BooleanFactory factory, Environment env, + BooleanValue value, DefCond ... dcs) { + if (!factory.noOverflow) + return value; + List univQuantInts = new ArrayList(dcs.length); + List extQuantInts = new ArrayList(dcs.length); + for (DefCond e : dcs) { + if (isUnivQuant(env, e)) + univQuantInts.add(e); + else + extQuantInts.add(e); + } + BooleanValue ret = value; + if (!env.isNegated()) { + for (DefCond e : extQuantInts) ret = factory.and(ret, factory.not(e.getAccumOverflow())); + for (DefCond e : univQuantInts) ret = factory.or(ret, e.getAccumOverflow()); + } else { + for (DefCond e : extQuantInts) ret = factory.or(ret, e.getAccumOverflow()); + for (DefCond e : univQuantInts) ret = factory.and(ret, factory.not(e.getAccumOverflow())); + } + return ret; + } + + private static boolean isUnivQuant(Environment env, DefCond e) { + if (env.isEmpty()) + return false; +// if (!isInt(env.type())) +// return isUnivQuant(env.parent(), e); + if (e.vars().contains(env.variable())) { + return env.envType() == Quantifier.ALL; + } else { + return isUnivQuant(env.parent(), e); + } + } + +// /** +// * Returns if this expression represents the Int type. +// */ +// private static boolean isInt(Object expression) { +// if (expression == null) +// return false; +// if (!(expression instanceof Expression)) +// return false; +// // TODO: this is probably not complete +// return "ints".equals(expression.toString()); +// } + + /* ------------------------------------------------------------------------------------ */ + /* used by the evaluator */ + /* ------------------------------------------------------------------------------------ */ + + private boolean isOverflowFlag = false; + + public void setOverflowFlag(boolean overflow) { this.isOverflowFlag = overflow; } + public boolean isOverflowFlag() { return this.isOverflowFlag; } + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/bool/Dimensions.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/bool/Dimensions.java new file mode 100644 index 00000000..ce300382 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/bool/Dimensions.java @@ -0,0 +1,472 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.engine.bool; + +import kodkod.engine.CapacityExceededException; +import kodkod.util.ints.Ints; + + + + +/** + * Stores information about the size of a matrix. Specifically, + * for an n-dimensional matrix n, a Dimensions object is abstractly + * a vector consisting of n integers; the ith integer in the vector + * represents the size of the ith dimension of a matrix. + * + * @specfield n: int + * @specfield dimensions: [0..n) -> one int + * @specfield capacity: dimensions[0] x ... x dimensions[n-1] + * @invariant n > 0 + * + * @author Emina Torlak + */ +public abstract class Dimensions { + private final int capacity; + + /** + * Constructs a Dimensions with the given capacity. + */ + private Dimensions(int capacity) { + this.capacity = capacity; + } + + /** + * Returns a new Dimensions object with n dimensions, each of + * which has the specified size. + * + * @return {d: Dimensions | d.n = n && d.dimensions[int] = size } + * @throws IllegalArgumentException - n < 1 || size < 1 + */ + public static Dimensions square(int size, int n) { + if (n < 1 || size < 1) throw new IllegalArgumentException("n < 1 || size < 1"); + return new Square(n, size); + } + + /** + * Constructs a new Dimensions object with the given dimensions. + * + * @return {d: Dimensions | d.n = dimensions.length && d.dimensions = dimensions } + * @throws NullPointerException - dimensions = null + * @throws IllegalArgumentException - dimensions.length = 0 || + * some i: [0..dimensions.n) | dimensions[i] < 1 + */ + public static Dimensions rectangular(int[] dimensions) { + if (dimensions.length==0) throw new IllegalArgumentException("n=0."); + long capacity = 1; + int size = dimensions[0]; + for (int i = 0; i < dimensions.length; i++) { + if (dimensions[i] < 1) throw new IllegalArgumentException("Invalid dimension: " + dimensions[i]); + capacity *= dimensions[i]; + if (size!=dimensions[i]) size = 0; + } + if (size>0) { + return new Square(dimensions.length, size); + } else { + final int[] dims = new int[dimensions.length]; + System.arraycopy(dimensions, 0, dims, 0, dimensions.length); + return new Rectangle(dims, capacity); + } + } + + /** + * Returns the capacity of this. + * @return this.capacity + */ + public final int capacity() { return capacity; } + + /** + * Returns the size of the ith dimensions + * @return this.dimensions[i] + * @throws ArrayIndexOutOfBoundsException - i < 0 || i >= this.capacity + */ + public abstract int dimension(int i); + + /** + * Returns the number of dimensions in this Dimensions object. + * @return this.n + */ + public abstract int numDimensions(); + + /** + * Returns true if this represents the dimensions of a square matrix; + * otherwise returns false. + * + * @return all i, j: [0..capacity) | this.dimensions[i] = this.dimensions[j] + */ + public abstract boolean isSquare(); + + /** + * Returns true if the dimensions data in this object is homogeneous + * from start, inclusive, to end, exclusive. + * @return some x: int | this.dimensions[start..end) = x + */ + abstract boolean isSquare(int start, int end); + + /** + * Fills the destination array, beginning at destPos, with the dimension data + * from this Dimensions object, beginning at srcPos. The number of components copied + * is equal to the length argument. The dimensions at positions srcPos through srcPos+length-1 + * are copied into positions destPos through destPos+length-1, respectively, of the destination + * array. + * @ensures dest[destPos..destPos+length) = this.dimensions[srcPos..srcPos+length) + */ + abstract void copy(int srcPos, int[] dest, int destPos, int length); + + /** + * Returns the dimensions of a matrix that would result from multiplying a + * matrix of dimensions given by this by a matrix whose dimensions are + * specified by dim. + * + * @return { d: Dimensions | d.n = this.n + dim.n - 2 && + * (all i: [0..this.n-1) | d.dimensions[i] = this.dimensions[i]) && + * (all i: [this.n-1..d.n) | d.dimensions[i] = dim.dimensions[i-this.n+1])} + * @throws IllegalArgumentException - this.n + dim.n < 3 || this.dimensions[n-1] != dim.dimensions[0] + */ + public final Dimensions dot(Dimensions dim) { + final int n0 = numDimensions(), n1 = dim.numDimensions(); + final int n = n0 + n1 - 2, drop = dim.dimension(0); + if (n == 0 || dimension(n0-1) != drop) { + throw new IllegalArgumentException(); + } + + if (isSquare(0,n0-1) && dim.isSquare(1,n1) && + (n0==1 || n1==1 || dimension(0)==dim.dimension(1))) { + return new Square(n, dimension(0)); + } else { + final int[] dims = new int[n]; + copy(0, dims, 0, n0-1); + dim.copy(1, dims, n0-1, n1-1); + return new Rectangle(dims, (capacity*dim.capacity) / (drop*drop)); + } + } + + /** + * Returns the dimensions of a matrix that would result from taking the cross + * product of a matrix of dimensions given by this and a matrix whose dimensions are + * specified by dim. + * + * @return { d: Dimensions | d.n = this.n + dim.n && + * (all i: [0..this.n) | d.dimensions[i] = this.dimensions[i]) && + * (all i: [this.n..d.n) | d.dimensions[i] = dim.dimensions[i-this.n])} + */ + public final Dimensions cross(Dimensions dim) { + final int n0 = numDimensions(), n1 = dim.numDimensions(); + if (isSquare() && dim.isSquare() && dimension(0)==dim.dimension(0)) + return new Square(n0+n1, dimension(0)); + else { + final int[] dims = new int[n0+n1]; + copy(0, dims, 0, n0); + dim.copy(0, dims, n0, n1); + return new Rectangle(dims, (long)capacity*(long)dim.capacity); + } + } + + + /** + * Returns the transpose of these dimensions. + * + * @return { d: Dimensions | d.n = 2 && d.dimensions[0] = this.dimensions[1] && + * d.dimensions[1] = this.dimensions[0] } + * @throws UnsupportedOperationException - this.n != 2 + */ + public abstract Dimensions transpose(); + + /** + * @return true if index is positive and less than bound. + */ + private static boolean positiveBounded(int index, int bound) { + return 0 <= index && index < bound; + } + + /** + * Returns true if index is a valid flat index for a matrix with + * these dimensions; otherwise returns false. + * + * @return 0 <= i < this.capacity + */ + public final boolean validate(int index) { + return positiveBounded(index, capacity); + } + + /** + * Returns true if index is a valid vector index for a matrix + * with these dimensions; otherwise returns false. + * + * @return index.length = n && + * (all i: [0..this.capacity) | 0 <= index[i] < this.dimensions[i]) + * @throws NullPointerException - index = null + */ + public final boolean validate(int[] index) { + final int length = numDimensions(); + if (index.length != length) return false; + for (int i = 0; i < length; i++) { + if (!positiveBounded(index[i], dimension(i))) return false; + } + return true; + } + + /** + * Converts an integer index into a matrix with these dimensions into a vector index. + * The effect of this method is the same as calling + * this.convert(index, new int[this.numDimensions()]). + * @return an array of ints that represents a vector index corresponding to the specified + * integer index into a this.dimensions[0]x...xthis.dimensions[n-1] matrix + * @throws IndexOutOfBoundsException - !validate(index) + */ + public final int[] convert(int index) { + final int[] vector = new int[numDimensions()]; + convert(index, vector); + return vector; + } + + /** + * Converts an integer index into a matrix with these dimensions into a vector index, + * and stores the result in the provided array. This method requires that + * the array argument have at least this.n cells, which are used to store the + * vector representation of the given index. The contents of the cells of vectorIndex + * beyond the first this.n cells are left unchanged. + * @requires vectorIndex.length <= this.n + * @ensures the first this.numDimensions entries of vectorIndex contain + * the vector index representation of the specified integer index into a + * this.dimensions[0]x...xthis.dimensions[n-1] matrix + * @throws NullPointerException - vectorIndex = null + * @throws IllegalArgumentException - vectorIndex.length < this.numDimensions + * @throws IndexOutOfBoundsException - !validate(index) + */ + public final void convert(int index, int[] vectorIndex) { + final int length = numDimensions(); + if (vectorIndex.length < length) + throw new IllegalArgumentException("arrayIndex.length= this.dimensions[i] + */ + public final int convert(int[] vectorIndex) { + final int length = numDimensions(); + if (vectorIndex.length < length) { + throw new IllegalArgumentException("index.length < this.n"); + } + int intIndex = 0; + int conversionFactor = capacity; + for(int i = 0; i < length; i++) { + int dim = dimension(i); + if (!positiveBounded(vectorIndex[i], dim)) throw new IndexOutOfBoundsException("index["+i+"]"); + conversionFactor = conversionFactor / dim; + intIndex += conversionFactor * vectorIndex[i]; + } + return intIndex; + } + + /** + * @see java.lang.Object#toString() + */ + public String toString() { + StringBuilder buffer = new StringBuilder("[ "); + for (int i = 0; i < numDimensions(); i++) { + buffer.append(dimension(i)); + buffer.append(" "); + } + buffer.append("]"); + return buffer.toString(); + } + + /** + * Represents a Dimensions object whose dimensions are all of the + * same size. + */ + private static final class Square extends Dimensions { + private final int n, size; + /** + * Constructs a new Dimensions object with n dimensions, each of + * which has the specified size. + * + * @ensures this.n' = n && this.dimensions[int] = size + * @requires size > 0 && n > 0 + * @throws IllegalArgumentException - n < 1 || size < 1 + */ + Square(int n, int size) { + super(capacity(n, size)); + this.size = size; + this.n = n; + } + + static int capacity(int n, int size) { + final long cap = Math.round(Math.pow(size,n)); + if (cap>Integer.MAX_VALUE || cap<=0) + throw new CapacityExceededException("Matrix too large: requested capacity of " + cap, Ints.nCopies(n, size)); + return (int)cap; + } + + @Override + void copy(int srcPos, int[] dest, int destPos, int length){ + if (srcPos < 0 || length < 0 || srcPos+length > n) throw new ArrayIndexOutOfBoundsException(); + while(srcPos++ < length) { + dest[destPos++] = size; + } + } + + @Override + boolean isSquare(int start, int end) { + if (start <= end && start >= 0 && end <= n) { + return true; + } + throw new ArrayIndexOutOfBoundsException(); + } + + @Override + public int numDimensions() { return n; } + + @Override + public int dimension(int i) { + if (!positiveBounded(i,n)) throw new ArrayIndexOutOfBoundsException(); + return size; + } + + @Override + public boolean isSquare() { return true; } + + @Override + public Dimensions transpose() { + if (numDimensions() != 2) throw new UnsupportedOperationException("n!=2"); + return this; + } + + /** + * Returns true if the given object is logically equivalent to this; + * otherwise returns false. + * + * @return this.dimensions = o.dimensions + */ + public boolean equals(Object o) { + if (o instanceof Square) { + final Square s = (Square) o; + return n==s.n && size==s.size; + } + return false; + } + + public int hashCode() { + return n ^ size; + } + + } + + /** + * Represents a Dimensions object with at least two dimensions of different size. + */ + private static final class Rectangle extends Dimensions { + private final int[] dimensions; + + + /** + * Constructs a new Dimensions object with the given dimensions. + * + * @ensures this.n' = dimensions.length && this.dimensions' = dimensions + * @requires - dimensions.length > 0 && + * (all i: [0..dimensions.n) | dimensions[i] > 0) && + * (some i, j: [0..dimensions.n) | dimensions[i] != dimensions[j]) && + * capacity = dimensions[0]*dimensions[1]*...*dimensions[dimensions.length-1] + */ + Rectangle(int[] dims, long capacity) { + super((int)capacity); + if (capacity>Integer.MAX_VALUE || capacity<=0) + throw new CapacityExceededException("Matrix too large: requested capacity of " + capacity, Ints.asIntVector(dims)); + this.dimensions = dims; + } + + @Override + void copy(int srcPos, int[] dest, int destPos, int length){ + System.arraycopy(dimensions, srcPos, dest, destPos, length); + } + + @Override + boolean isSquare(int start, int end) { + for(int i = start + 1; i < end; i++) { + if (dimensions[i-1] != dimensions[i]) return false; + } + return true; + } + + @Override + public boolean isSquare() { return false; } + + @Override + public int dimension(int i) { return dimensions[i]; } + + @Override + public int numDimensions() { return dimensions.length; } + + @Override + public Dimensions transpose() { + if (numDimensions() != 2) throw new UnsupportedOperationException("n!=2"); + int[] dims = {dimensions[1], dimensions[0]}; + return new Rectangle(dims, capacity()); + } + + /** + * Returns true if the given object is logically equivalent to this; otherwise returns false. + * + * @return this.dimensions = dim.dimensions + */ + public boolean equals(Object o) { + if (o instanceof Rectangle) { + final Rectangle r = (Rectangle) o; + if (dimensions.length != r.dimensions.length || capacity() != r.capacity()) return false; + for (int i = 0; i < dimensions.length; i++) { + if (dimensions[i] != r.dimensions[i]) return false; + } + return true; + } + return false; + + } + + public int hashCode() { + return dimensions.length ^ capacity(); + } + + + } + + + + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/bool/ITEGate.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/bool/ITEGate.java new file mode 100644 index 00000000..121969fb --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/bool/ITEGate.java @@ -0,0 +1,189 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.engine.bool; + +import java.util.Iterator; +import java.util.Set; + +import kodkod.util.collections.Containers; +import kodkod.util.ints.Ints; + +/** + * An if-then-else gate. + * + * @specfield ifFormula: BooleanFormula + * @specfield thenFormula: BooleanFormula + * @specfield elseFormula: BooleanFormula + * @invariant inputs = 0->ifFormula + 1->thenFormula + 2->elseFormula + * @invariant this.label > 0 + * @invariant this.op = Operator.ITE + * @specfield all input: inputs | this.label > |input.label| + * @author Emina Torlak + */ +public final class ITEGate extends BooleanFormula { + private final BooleanFormula[] inputs; + private final int label, hashcode, labelhash; + + /** + * Constructs a new ITEGate from the given formulas and label. + * @requires label >= 0 && null !in ifFormula + thenFormula + elseFormula + * @requires hashcode = ITE.hash(ifFormula, thenFormula, elseFormula) + * @ensures this.label' = label && this.ifFormula' = ifFormula && + * this.thenFormula' = thenFormula && this.elseFormula' = elseFormula + * @throws NullPointerException - owner = null + */ + ITEGate(int label, int hashcode, BooleanFormula ifFormula, BooleanFormula thenFormula, BooleanFormula elseFormula) { + super(null); + assert label >= 0; + this.label = label; + this.labelhash = Ints.superFastHash(label); + this.hashcode = hashcode; + this.inputs = new BooleanFormula[3]; + inputs[0] = ifFormula; + inputs[1] = thenFormula; + inputs[2] = elseFormula; + } + + /** + * Returns a hash of this.label + * @return a hash of this.label + */ + @Override + int hash(Operator op) { + return labelhash; + } + + /** + * Returns an iterator over this.inputs + * @return returns an iterator over this.inputs + * @see kodkod.engine.bool.BooleanFormula#iterator() + */ + @Override + public Iterator iterator() { + return Containers.iterate(inputs); + } + + /** + * Returns 3. + * @return 2 + * @see kodkod.engine.bool.BooleanFormula#size() + */ + @Override + public int size() { + return 3; + } + + /** + * Returns this.label + * @return this.label + * @see kodkod.engine.bool.BooleanValue#label() + */ + @Override + public int label() { + return label; + } + + /** + * Passes this value and the given + * argument value to the visitor, and returns the resulting value. + * @return the value produced by the visitor when visiting this node + * with the given argument. + * @see kodkod.engine.bool.BooleanFormula#accept(kodkod.engine.bool.BooleanVisitor, Object) + */ + @Override + public T accept(BooleanVisitor visitor, A arg) { + return visitor.visit(this, arg); + } + + /** + * Returns a string representation of this ITE gate. + * @return a string representation of this ITE gate. + */ + public String toString() { + return "(" + inputs[0] + "?" + inputs[1] + ":" + inputs[2] + ")"; + } + + /** + * Returns the hashcode for this if-then-else gate. + * @return the hashcode for this gate. + */ + public int hashCode() { + return hashcode; + } + /** + * Returns Operator.ITE. + * @return Operator.ITE + */ + @Override + public kodkod.engine.bool.Operator op() { + return kodkod.engine.bool.Operator.ITE; + } + + /** + * Returns this.inputs[i]. + * @return this.inputs[i] + * @throws IndexOutOfBoundsException - 0 < i || i > 2 + */ + @Override + public BooleanFormula input(int i) { + if (i < 0 || i > 2) + throw new IndexOutOfBoundsException(); + return inputs[i]; + } + + /** + * Returns an integer k' such that 0 < |k'| < k and |k'| is the number of flattening + * steps that need to be taken to determine that this circuit has (or does not have) + * an input with the given label. + * A positive k' indicates that f is found to be an input to this circuit in k' steps. + * A negative k' indicates that f is not an input to this circuit, when it is flattened + * using at most k steps. + * @requires k > 0 + * @return this=f => 1 else op=ITE && k>2 && f in this.inputs[int].label => 3 else -3 + */ + @Override + int contains(Operator op, int f, int k) { + assert k > 0; + if (f==label) return 1; + else if (op != Operator.ITE || k < 3 || f>label || -f>label) return -1; + else return (inputs[0].label()==f || inputs[1].label()==f || inputs[2].label()==f) ? 3 : -3; + } + + /** + * Flattens this circuit with respect to the given operator into + * the provided set. + * @requires k > 0 + * @ensures op = Operator.ITE && k> 2 => flat.elts' = flat.elts + this.inputs[ints], + * flat.elts' = flat.elts + this + */ + @Override + void flatten(Operator op, Set flat, int k) { + assert k > 0; + if (op==Operator.ITE && k > 2) { + flat.add(inputs[0]); + flat.add(inputs[1]); + flat.add(inputs[2]); + } else { + flat.add(this); + } + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/bool/Int.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/bool/Int.java new file mode 100644 index 00000000..6a3b901e --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/bool/Int.java @@ -0,0 +1,356 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.engine.bool; + +import java.util.Collection; +import java.util.List; + +import kodkod.ast.Variable; +import kodkod.engine.fol2sat.Environment; + + +/** +* An integer represented using {@link kodkod.engine.bool.BooleanValue boolean values} +* and a given {@link kodkod.engine.config.Options.IntEncoding integer encoding}. +* +* @specfield factory: BooleanFactory +* @specfield bits: [0..factory.bitwidth) -> one factory.components +* @specfield encoding: factory.intEncoding +* @author Emina Torlak +*/ +@SuppressWarnings("rawtypes") +public abstract class Int { + final BooleanFactory factory; + final DefCond defCond = new DefCond(); + + /** + * Creates an Int with the given factory + * @ensures this.factory' = factory + */ + Int(BooleanFactory factory, Collection vars) { + this.factory = factory; + defCond.addVars(vars); + } + + /** + * Throws IllegalArgumentException if other.factory != this.factory. + * @throws IllegalArgumentException - other.factory != this.factory. + */ + final void validate(Int other) { + if (other.factory != factory) + throw new IllegalArgumentException("other.factory != this.factory"); + } + + /** + * Returns the BooleanValue at the specified index. + * @requires 0 <= i < this.factory.bitwidth + * @return this.bits[i] + */ + abstract BooleanValue bit(int i); + + /** + * Returns the most significant bit + * @return this.bits[this.width()-1] + */ + abstract BooleanValue msb(); + + /** + * Returns the little endian two's complement representation of this integer that is + * this.factory.bitwidth bits wide. Specifically, the returned list L has + * this.factory.bitwidth boolean values such that the meaning of this integer is + * 1*[[L.get(0)]] + ... + (1< twosComplementBits(); + + /** + * Returns this.factory + * @return this.factory + */ + public final BooleanFactory factory() { return factory; } + + /** + * Returns this.defCond + * @return this.defCond + */ + public final DefCond defCond() { return defCond; } + + /** + * Returns the number of bits in the representation of this Int, + * including sign bits (if any). + * @return this.width + */ + public abstract int width(); + + /** + * Returns true if all the bits representing this Int + * are BooleanConstants. + * @return this.bits[int] in BooleanConstant + */ + public abstract boolean isConstant(); + + /** + * If this Int is constant, returns its value. Otherwise + * throws an IllegalStateException. + * @return this.isConstant() => [[this.bits]] + * @throws IllegalStateException - !this.isConstant() + */ + public abstract int value(); + + /** + * Returns a BooleanValue encoding the comparator circuit + * that checks whether the integer represented by this + * Int is equal to the integer represented by the specified Int. + * @requires this.factory = other.factory + * @return BooleanValue encoding the comparator circuit + * that checks whether the integer represented by this + * Int is equal to the integer represented by the specified Int + * @throws IllegalArgumentException - this.factory != other.factory + */ + public final BooleanValue eq(Int other) { return this.eq(other, Environment.empty()); } + public abstract BooleanValue eq(Int other, Environment env); + + public final BooleanValue neq(Int other) { return this.neq(other, Environment.empty()); } + public abstract BooleanValue neq(Int other, Environment env); + + /** + * Returns a BooleanValue encoding the comparator circuit + * that checks whether the integer represented by this + * Int is less than or equal to the integer + * represented by the specified Int + * @requires this.factory = other.factory + * @return BooleanValue encoding the comparator circuit + * that checks whether the integer represented by this + * Int is less than or equal to the integer + * represented by the specified Int + * @throws IllegalArgumentException - this.factory != other.factory + */ + public BooleanValue lte(Int other) { return this.lte(other, Environment.empty()); } + public abstract BooleanValue lte(Int other, Environment env); + + /** + * Returns a BooleanValue encoding the comparator circuit + * that checks whether the integer represented by this + * Int is less than the integer + * represented by the specified Int. + * @requires this.factory = other.factory + * @return BooleanValue encoding the comparator circuit + * that checks whether the integer represented by this + * Int is less than the integer + * represented by the specified Int + * @throws IllegalArgumentException - this.factory != other.factory + */ + public final BooleanValue lt(Int other) { return this.gte(other, Environment.empty()); } + public abstract BooleanValue lt(Int other, Environment env); + + /** + * Returns a BooleanValue encoding the comparator circuit + * that checks whether the integer represented by this + * Int is greater than or equal to the integer + * represented by the specified Int. + * @requires this.factory = other.factory + * @return BooleanValue encoding the comparator circuit + * that checks whether the integer represented by this + * Int is greater than or equal to the integer + * represented by the specified Int + * @throws IllegalArgumentException - this.factory != other.factory + */ + public BooleanValue gte(Int other) { return this.gte(other, Environment.empty()); } + public BooleanValue gte(Int other, Environment env) { + return other.lte(this, env); + } + + /** + * Returns a BooleanValue encoding the comparator circuit + * that checks whether the integer represented by this + * Int is greater than the integer + * represented by the specified Int. + * @requires this.factory = other.factory + * @return BooleanValue encoding the comparator circuit + * that checks whether the integer represented by this + * Int is greater than the integer + * represented by the specified Int + * @throws IllegalArgumentException - this.factory != other.factory + */ + public BooleanValue gt(Int other) { return this.gt(other, Environment.empty()); } + public BooleanValue gt(Int other, Environment env) { + return other.lt(this, env); + } + + /** + * Returns an Int that represents the sum of this and the given Int. + * @requires this.factory = other.factory + * @return an Int that represents the sum of this and the given Int + * @throws IllegalArgumentException - this.factory != other.factory + */ + public abstract Int plus(Int other); + + /** + * Returns an Int that represents the sum of this and the given Ints. + * @requires this.factory = others[int].factory + * @return an Int that represents the sum of this and the given Ints + * @throws IllegalArgumentException - this.factory != others[int].factory + */ + public abstract Int plus(Int... others); + + /** + * Returns an Int that represents the product between this and the given Int. + * @requires this.factory = other.factory + * @return an Int that represents the product between this and the given Int + * @throws UnsupportedOperationException - this.encoding does not support multiplication + */ + public abstract Int multiply(Int other); + + /** + * Returns an Int that represents the product between this and the given Ints. + * @requires this.factory = others[int].factory + * @return an Int that represents the product between this and the given Ints + * @throws UnsupportedOperationException - this.encoding does not support multiplication + */ + public abstract Int multiply(Int... others); + + /** + * Returns an Int that represents the difference between this and the given Int. + * @requires this.factory = other.factory + * @return an Int that represents the difference between this and the given Int + * @throws UnsupportedOperationException - this.encoding does not support subtraction + */ + public abstract Int minus(Int other); + + /** + * Returns an Int that represents the quotient of the division between this and the given Int. + * @requires this.factory = other.factory + * @return an Int that represents the quotient of the division between this and the given Int + * @throws UnsupportedOperationException - this.encoding does not support division + */ + public abstract Int divide(Int other); + + /** + * Returns an Int that represents the remainder of the division between this and the given Int. + * @requires this.factory = other.factory + * @return an Int that represents the remainder of the division between this and the given Int + * @throws UnsupportedOperationException - this.encoding does not support division + */ + public abstract Int modulo(Int other); + + /** + * Returns an Int that evaluates to this if the condition is true, otherwise it + * evaluates to the given Int. + * @requires other + condition in this.factory.components + * @return an Int that evaluates to this if the condition is true, and + * to the given Int if the condition is false. + */ + public abstract Int choice(BooleanValue condition, Int other); + + /** + * Returns an Int that represents the bitwise conjunction of this and the given Int. + * @requires this.factory = other.factory + * @return an Int that represents the bitwise conjunction of this and the given Int. + */ + public abstract Int and(Int other); + + /** + * Returns an Int that represents the bitwise conjunction of this and the given Ints. + * @requires this.factory = others[int].factory + * @return an Int that represents the bitwise conjunction of this and the given Ints. + */ + public abstract Int and(Int... others); + + /** + * Returns an Int that represents the bitwise disjunction of this and the given Int. + * @requires this.factory = other.factory + * @return an Int that represents the bitwise disjunction of this and the given Int. + */ + public abstract Int or(Int other); + + /** + * Returns an Int that represents the bitwise disjunction of this and the given Ints. + * @requires this.factory = others[int].factory + * @return an Int that represents the bitwise disjunction of this and the given Ints. + */ + public abstract Int or(Int... others); + + /** + * Returns an Int that represents the bitwise XOR of this and the given Int. + * @requires this.factory = other.factory + * @return an Int that represents the bitwise XOR of this and the given Int. + * @throws UnsupportedOperationException - this.encoding does not support XOR + */ + public abstract Int xor(Int other); + + /** + * Returns an Int that represents this shifted to the left by the given Int. + * @requires this.factory = other.factory + * @return an Int that represents this shifted to the left by the given Int. + * @throws UnsupportedOperationException - this.encoding does not support SHL + */ + public abstract Int shl(Int other); + + /** + * Returns an Int that represents this shifted to the right by the given Int, + * with zero extension. + * @requires this.factory = other.factory + * @return an Int that represents this shifted to the right by the given Int, + * with zero extension. + * @throws UnsupportedOperationException - this.encoding does not support SHR + */ + public abstract Int shr(Int other); + + /** + * Returns an Int that represents this shifted to the right by the given Int, + * with sign extension. + * @requires this.factory = other.factory + * @return an Int that represents this shifted to the right by the given Int, + * with sign extension. + * @throws UnsupportedOperationException - this.encoding does not support SHA + */ + public abstract Int sha(Int other); + + /** + * Returns an Int that represents the negation of this integer. + * @return -[[this]] + * @throws UnsupportedOperationException - this.encoding does not support negation + */ + public abstract Int negate(); + + /** + * Returns an Int that represents bitwise negation of this integer. + * @return ~[[this]] + * @throws UnsupportedOperationException - this.encoding does not support bitwise negation + */ + public abstract Int not(); + + /** + * Returns an Int that represents the absolute value of this integer. + * @return abs([[this]]) + */ + public abstract Int abs(); + + /** + * Returns an Int that represents the signum of this integer. + * @return sgn([[this]]) + */ + public abstract Int sgn(); + + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/bool/MultiGate.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/bool/MultiGate.java new file mode 100644 index 00000000..d3545215 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/bool/MultiGate.java @@ -0,0 +1,122 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.engine.bool; + +import java.util.Iterator; + +import kodkod.util.ints.Ints; + + +/** + * A logic gate with two or more inputs; an AND or an OR gate. + * + * @specfield op: Operator.Binary + * @invariant #inputs > 1 + * @invariant some components.this => label in [1..Integer.MAX_VALUE), label in [0..Integer.MAX_VALUE) + * @invariant no c1, c2: inputs | c1.label = -c2.label + * @invariant this.label > 0 => all c: inputs | |c.label| < this.label + * @author Emina Torlak + */ +public abstract class MultiGate extends BooleanFormula { + final Operator.Nary op; + + private final int label, labelhash, hashcode; + + /** + * Constructs a new MultiGate gate with the given operator and label. + * @requires op != null && label >= 0 + * @ensures this.op' = op && this.label' = label + */ + MultiGate(Operator.Nary op, int label, int hashcode) { + super(null); + assert op != null; + assert label >= 0; + this.op = op; + this.label = label; + this.labelhash = Ints.superFastHash(label); + this.hashcode = hashcode; + } + + /** + * Returns the label for this value. + * @return this.label + */ + @Override + public final int label() { return label; } + + /** + * Returns the operator used to combine the input + * variables of this connective gate. + * @return this.op + */ + public final Operator.Nary op() { return op; } + + /** + * Passes this value and the given + * argument value to the visitor, and returns the resulting value. + * @return the value produced by the visitor when visiting this node + * with the given argument. + */ + @Override + public T accept(BooleanVisitor visitor, A arg) { + return visitor.visit(this, arg); + } + + /** + * Returns a string representation of this multigate. + * @return a string representation of this multigate. + */ + public String toString() { + final StringBuilder builder = new StringBuilder("("); + final Iterator children = iterator(); + builder.append(children.next()); + while(children.hasNext()) { + builder.append(op); + builder.append(children.next()); + } + builder.append(")"); + return builder.toString(); + } + + /** + * Returns a hashcode for this gate. The hashcode obeys the Object contract. + * @return a hashcode for this gate. + */ + @Override + public final int hashCode() { + return hashcode; + } + + /** + * Returns the digest of this formula that would be used + * to compute the digest of the composition of this and + * some other formula using the given operator. Specifically, + * if op = this.op, then the sum of this circuit's irreducible + * inputs' hashes (with respect to op) is returned. Otherwise, + * the superFastHash of this.label is returned. + * @return this.op = op => this.op.hash(this.inputs), Ints.superFastHash(this.label) + */ + @Override + final int hash(Operator op) { + return op==this.op ? hashcode : labelhash; + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/bool/NaryGate.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/bool/NaryGate.java new file mode 100644 index 00000000..8a573931 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/bool/NaryGate.java @@ -0,0 +1,157 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.engine.bool; + +import java.util.Iterator; +import java.util.Set; + +import kodkod.util.collections.Containers; + +/** + * Represents a multi gate with more than two inputs. + * @invariant #this.inputs > 2 + * @invariant digest = sum(inputs.digest(this.op)) + */ +final class NaryGate extends MultiGate { + private final BooleanFormula[] inputs; + + /** + * Constructs a new n-ary gate with the given label, from the given mutable multi gate. + * @requires g != null && #g.inputs > 2 + * @ensures this.op' = g.op && this.inputs' = g.inputs && this.label' = label + */ + NaryGate(BooleanAccumulator g, int label, int hashcode) { + super(g.op, label, hashcode); + this.inputs = new BooleanFormula[g.size()]; + int index = 0; + for(Iterator i = g.iterator(); i.hasNext(); ) { + inputs[index] = (BooleanFormula) i.next(); + index++; + } + } + + /** + * Returns the number of inputs to this gate. + * @return #this.inputs + */ + @Override + public int size() { + return inputs.length; + } + + /** + * Returns an iterator over the inputs to this gate, in the ascending + * label order. + * @return an iterator over this.inputs + */ + @Override + public Iterator iterator() { + return Containers.iterate(inputs); + } + + /** + * Returns an integer k' such that 0 < |k'| < k and |k'| is the number of flattening + * steps that need to be taken to determine that this circuit has (or does not have) + * an input with the given label. + * A positive k' indicates that f is found to be an input to this circuit in k' steps. + * A negative k' indicates that f is not an input to this circuit, when it is flattened + * using at most k steps. + * @requires k > 0 + * @return the number of flattening + * steps that need to be taken to determine that f is (not) an input to this circuit + */ + @Override + int contains(Operator op, int f, int k) { + assert k > 0; + if (f==label()) return 1; + else if (this.op != op || f>label() || -f>label()) return -1; + else { + int low = 0, high = inputs.length-1, step = 1; + while (low <= high && step <= k) { + int mid = (low + high) >>> 1; + int midVal = inputs[mid].label(); + + if (midVal < f) + low = mid + 1; + else if (midVal > f) + high = mid - 1; + else + return step; // key found in the given number of steps + step++; + } + return 1-step; // key not found. + } + } + + /** + * Flattens this circuit with respect to the given operator into + * the provided set. + * Specifically, the method modifies the set so that it contains + * the elements f_0, ..., f_k' where k' <= k elements and + * [[this]] = op(f_0, ..., f_k'). + * The default implementation simply adds this to the set. + * @requires k > 0 + * @ensures 1 <= k' <= k && some f_0,..., f_k' : flat.elts' | + * [[this]] = op([[f_0]], ..., [[f_k']]) + */ + @Override + void flatten(Operator op, Set flat, int k) { + assert k > 0; + if (this.op == op && k >= inputs.length) { + int diff = k - inputs.length; + for(BooleanFormula f: inputs) { + int oldsize = flat.size(); + f.flatten(op, flat, StrictMath.max(1, diff)); + diff -= (flat.size() - oldsize); + } + } else { + flat.add(this); + } + } + + /** + * Returns true if the given iterator and this.iterator + * return the same elements, in the same order. + * @return true if values and this.iterator return the same elements, + * in the same order. + */ + boolean sameInputs(Iterator values) { + for(BooleanFormula f : inputs) { + if (!(values.hasNext() && f == values.next())) + return false; + } + return !values.hasNext(); + } + + /** + * Returns the ith input to this gate. + * @return this.inputs[i] + * @requires 0 <= i < size + * @throws IndexOutOfBoundsException - i < 0 || i >= #this.inputs + */ + @Override + public BooleanFormula input(int i) { + if (i < 0 || i > inputs.length) + throw new IndexOutOfBoundsException(); + return inputs[i]; + } +} \ No newline at end of file diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/bool/NotGate.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/bool/NotGate.java new file mode 100644 index 00000000..b83a7470 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/bool/NotGate.java @@ -0,0 +1,149 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.engine.bool; + +import java.util.Iterator; +import java.util.NoSuchElementException; + +import kodkod.util.ints.Ints; + + +/** + * A logic NOT gate. + * + * @invariant this.op = Operator.NOT + * @invariant #inputs = 1 + * @invariant this.label = -inputs.label + * @invariant label in (-Integer.MAX_VALUE..-1] + * @author Emina Torlak + */ +public final class NotGate extends BooleanFormula { + + private final int hashcode; + + /** + * Constructs a new NotGate with the given formula as its input. + * @requires input != null && input !in NotGate + * @ensures this.inputs' = 0->input && this.output'.label = -input.label + */ + NotGate(BooleanFormula input) { + super(input); + this.hashcode = Ints.superFastHash(-input.label()); + } + + /** + * Returns a hash of this inverter's label. + * @return Ints.superFastHash(this.label) + */ + @Override + int hash(Operator op) { + return hashcode; + } + + /** + * Returns an iterator that returns this gate's single input. + * @return an iterator over this.inputs. + */ + @Override + public Iterator iterator() { + return new Iterator() { + boolean hasNext = true; + public boolean hasNext() { + return hasNext; + } + + public BooleanFormula next() { + if (!hasNext) throw new NoSuchElementException(); + hasNext = false; + return negation(); + } + + public void remove() { + throw new UnsupportedOperationException(); + } + + }; + } + + /** + * Returns the label for this value. + * @return this.label + */ + @Override + public final int label() { return -negation().label(); } + + /** + * Returns 1. + * @return 1. + */ + @Override + public int size() { + return 1; + } + + /** + * Passes this value and the given + * argument value to the visitor, and returns the resulting value. + * @return the value produced by the visitor when visiting this node + * with the given argument. + */ + @Override + public T accept(BooleanVisitor visitor, A arg) { + return visitor.visit(this, arg); + } + + /** + * Returns a string representation of this inverter. + * @return a string representation of this inverter. + */ + public String toString() { + return "!" + negation().toString(); + } + + /** + * Returns Operator.NOT. + * @return Operator.NOT + */ + @Override + public kodkod.engine.bool.Operator op() { + return kodkod.engine.bool.Operator.NOT; + } + + /** + * Returns this.input[i]. + * @return this.input[i] + * @throws IndexOutOfBoundsException - i != 0 + */ + @Override + public BooleanFormula input(int i) { + if (i != 0) throw new IndexOutOfBoundsException(); + return negation(); + } + + /** + * Returns a hashcode for this inverter. + * @return a hashcode for this inverter. + */ + public int hashCode() { + return hashcode; + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/bool/Operator.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/bool/Operator.java new file mode 100644 index 00000000..5d5ebfca --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/bool/Operator.java @@ -0,0 +1,183 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.engine.bool; + +import java.util.Iterator; + +/** + * ExprOperator associated with a {@link kodkod.engine.bool.BooleanValue boolean value}. + * + * @specfield ordinal: [0..5] + * @invariant AND.ordinal = 0 && OR.ordinal = 1 && ITE.ordinal = 2 && + * NOT.ordinal = 2 && VAR.ordinal = 4 && CONST.ordinal = 5 + * @author Emina Torlak + */ +public abstract class Operator implements Comparable{ + final int ordinal; + + private Operator(int ordinal) { + this.ordinal = ordinal; + } + + /** + * Returns the ordinal of this operator constant. + * @return the ordinal of this operator constant. + */ + public final int ordinal() { + return ordinal; + } + + /** + * Returns an integer i such that i < 0 if this.ordinal < op.ordinal, + * i = 0 when this.ordinal = op.ordinal, and i > 0 when this.ordinal > op.ordinal. + * @return i: int | this.ordinal < op.ordinal => i < 0, + * this.ordinal = op.ordinal => i = 0, i > 0 + * @throws NullPointerException - op = null + */ + public int compareTo(Operator op) { + return ordinal() - op.ordinal(); + } + + /** + * N-ary {@link MultiGate AND} operator. + */ + public static final Nary AND = new Nary(0) { + public String toString() { return "&"; } + /** @return true */ + public BooleanConstant identity() { return BooleanConstant.TRUE; } + /** @return false */ + public BooleanConstant shortCircuit() { return BooleanConstant.FALSE; } + /** @return OR */ + public Nary complement() { return OR; } + }; + + /** + * N-ary {@link MultiGate OR} operator. + */ + public static final Nary OR = new Nary(1) { + public String toString() { return "|"; } + /** @return false */ + public BooleanConstant identity() { return BooleanConstant.FALSE; } + /** @return true */ + public BooleanConstant shortCircuit() { return BooleanConstant.TRUE; } + /** @return AND */ + public Nary complement() { return AND; } + }; + + /** + * Ternary {@link ITEGate if-then-else} operator. + */ + public static final Ternary ITE = new Ternary(2) { + public String toString() { return "?"; } + }; + + /** + * Unary {@link NotGate negation} operator. + */ + public static final Operator NOT = new Operator(3) { + public String toString() { return "!"; } + }; + + /** + * Zero-arity {@link BooleanVariable variable} operator. + */ + public static final Operator VAR = new Operator(4) { + public String toString() { return "var"; } + }; + + /** + * Zero-arity {@link BooleanConstant constant} operator. + */ + public static final Operator CONST = new Operator(5) { + public String toString() { return "const"; } + }; + + /** + * An n-ary operator, where n>=2 + */ + public static abstract class Nary extends Operator { + + private Nary(int ordinal) { + super(ordinal); + } + + /** + * Returns the hashcode for a gate v such that + * v.op = this && v.inputs[int] = f0 + f1 + * @return f0.hash(this) + f1.hash(this) + */ + int hash(BooleanFormula f0, BooleanFormula f1) { + return f0.hash(this) + f1.hash(this); + } + + /** + * Returns the hashcode for a gate v such that + * v.op = this && v.iterator() = formulas. + * @return sum(formulas.hash(this)) + */ + int hash(Iterator formulas) { + int sum = 0; + while(formulas.hasNext()) + sum += formulas.next().hash(this); + return sum; + } + + /** + * Returns the boolean constant c such that + * for all logical values x, c composed + * with x using this operator will result in x. + * @return the identity value of this binary operator + */ + public abstract BooleanConstant identity(); + /** + * Returns the boolean constant c such that + * for all logical values x, c composed + * with x using this operator will result in c. + * @return the short circuiting value of this binary operator + */ + public abstract BooleanConstant shortCircuit(); + /** + * Returns the binary operator whose identity and short circuit + * values are the negation of this operator's identity and + * short circuit. + * @return the complement of this binary operator + */ + public abstract Operator.Nary complement(); + } + + static abstract class Ternary extends Operator { + + private Ternary(int ordinal) { + super(ordinal); + } + + /** + * Returns the hashcode for a gate v such that + * v = (i ? t : e) + * @return 3*i.hash(this) + 5*t.hash(this) + 7*e.hash(this) + */ + int hash(BooleanFormula i, BooleanFormula t, BooleanFormula e) { + return 3*i.hash(this) + 5*t.hash(this) + 7*e.hash(this); + } + } + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/bool/RBCFactory.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/bool/RBCFactory.java new file mode 100644 index 00000000..ca0afbf3 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/bool/RBCFactory.java @@ -0,0 +1,286 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.engine.bool; + +import static kodkod.engine.bool.BooleanConstant.FALSE; +import static kodkod.engine.bool.BooleanConstant.TRUE; +import static kodkod.engine.bool.Operator.AND; +import static kodkod.engine.bool.Operator.CONST; +import static kodkod.engine.bool.Operator.ITE; +import static kodkod.engine.bool.Operator.OR; + +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.ListIterator; + +import kodkod.util.collections.CacheSet; + +/** + * A factory for creating variables, binary gates, and if-then-else gates, in RBC form. + * @specfield values: set (BooleanVariable + MultiGate + ITEGate) + * @specfield cmpMax: int // the maximum number of comparisons made when comparing circuits for equality + * @invariant no disj factory, factory' : CircuitFactory | some factory.values & factory'.values + * @author Emina Torlak + */ +final class RBCFactory { + /** + * Stores input variables. + * @invariant all i: [1..iLits.size()] | vars[i-1].positive.label = i + */ + private final BooleanVariable[] vars; + /** + * Caches the AND, OR, and ITE gates. + * @invariant all i: [0..2] | c[i].op.ordinal = i + */ + private final CacheSet[] cache; + private int label, cmpMax; + + + /** + * Constructs a CircuitFactory using the given max comparison parameter, initialized + * to contain the given number of variables. + * @requires cmpMax > 0 && numVars >= 0 + * @ensures #this.values' = numVars && this.values in BooleanVariable + * @ensures this.cmpMax' = cmpMax + */ + @SuppressWarnings("unchecked") RBCFactory(int numVars, int cmpMax) { + assert cmpMax > 0 && numVars >= 0; + this.cmpMax = cmpMax; + this.label = numVars + 1; + vars = new BooleanVariable[numVars]; + for(int i = 0; i < numVars; i++) { + vars[i]= new BooleanVariable(i+1); + } + + cache = new CacheSet[]{new CacheSet(), new CacheSet(), new CacheSet()}; + } + + /** + * Returns the cache for gates with the given operator. + * @requires op in AND + OR + ITE + * @return cache[op.ordinal] + */ + private CacheSet opCache(Operator op) { + return cache[op.ordinal]; + } + + /** + * Sets this.cmpMax to the given value. + * @requires cmpMax > 0 + * @ensures this.cmpMax' = cmpMax + */ + void setCmpMax(int cmpMax) { + assert cmpMax > 0; + this.cmpMax = cmpMax; + } + + /** + * Returns this.cmpMax. + * @return this.cmpMax + */ + int cmpMax() { return cmpMax; } + + /** + * Removes all MultiGates and ITEGates from this.factory. + * @ensures this.values' = this.values & BooleanVariable + */ + void clear() { + label = vars.length+1; + cache[0].clear(); + cache[1].clear(); + cache[2].clear(); + } + + /** + * Returns true if the given value + * is a valid argument to one of the assemble + * methods. Otherwise returns false. + * @return v in this.values + this.values.negation + BooleanConstant + */ + boolean canAssemble(BooleanValue v) { + if (v.op()==CONST) return true; + if (v.label() < 0) v = v.negation(); + final int absLit = v.label(); + if (absLit <= vars.length) { + return v == vars[absLit-1]; + } else { + final BooleanFormula g = (BooleanFormula) v; + for(Iterator gates = opCache(g.op()).get(g.hashCode()); gates.hasNext(); ) { + if (gates.next()==g) return true; + } + return false; + } + } + + /** + * Returns the number of variables in this factory. + * @return #(this.values & BooleanVariable) + */ + int numVars() { return vars.length; } + + /** + * Returns the boolean variable from this.values with the given label. + * @requires 0 < label <= #(this.values & BooleanVariable) + * @return (this.values & BooleanVariable).label + */ + BooleanVariable variable(int label) { + return vars[label-1]; + } + + /** + * Returns a boolean value whose meaning is (if [[i]] then [[t]] else [[e]]). + * @requires i + t + e in (this.values + this.values.negation + BooleanConstant) + * @return v: BooleanValue | [[v]] = if [[i]] then [[t]] else [[e]] + * @ensures v in BooleanFormula - NotGate => this.values' = this.values + v, this.values' = this.values + * @throws NullPointerException - any of the arguments are null + */ + BooleanValue assemble(BooleanValue i, BooleanValue t, BooleanValue e) { + if (i==TRUE || t==e) return t; + else if (i==FALSE) return e; + else if (t==TRUE || i==t) return assemble(OR, i, e); + else if (t==FALSE || i.negation()==t) return assemble(AND, i.negation(), e); + else if (e==TRUE || i.negation()==e) return assemble(OR, i.negation(), t); + else if (e==FALSE || i==e) return assemble(AND, i, t); + else { + final int ilabel = i.label(), tlabel = t.label(), elabel = e.label(); + boolean neg = false; + BooleanFormula f0 = (BooleanFormula) i, f1 = (BooleanFormula) t, f2 = (BooleanFormula) e; + if (Math.abs(tlabel)==Math.abs(elabel)) { + if (ilabel>0 && tlabel<0 && elabel>0) { // (a <=> !b) becomes !(a <=> b) + neg = true; + f1 = f1.negation(); + f2 = f2.negation(); + } else if (ilabel<0 && tlabel>0 && elabel<0) { // (!a <=> b) becomes !(a <=> b) + neg = true; + f0 = f0.negation(); + } else if (ilabel<0 && tlabel<0 && elabel>0) {// (!a <=> !b) becomes (a <=> b) + f0 = f0.negation(); + f1 = f1.negation(); + f2 = f2.negation(); + } + } + + final int hash = ITE.hash(f0, f1, f2); + + for(Iterator gates = opCache(ITE).get(hash); gates.hasNext();) { + BooleanFormula gate = gates.next(); + if (gate.input(0)==i && gate.input(1)==t && gate.input(2)==e) + return gate; + } + final BooleanFormula ret = new ITEGate(label++, hash, f0, f1, f2); + opCache(ITE).add(ret); + return neg ? ret.negation() : ret; + } + } + + /** + * Returns a boolean value whose meaning is ([[v0]] op [[v1]]). + * @requires v0 + v1 in (this.values + this.values.negation + BooleanConstant) + * @return v: BooleanValue | [[v]] = [[v0]] op [[v1]] + * @ensures v in BooleanFormula - NotGate => this.values' = this.values + v, this.values' = this.values + * @throws NullPointerException - any of the arguments are null + */ + BooleanValue assemble(Operator.Nary op, BooleanValue v0, BooleanValue v1) { + if (op==OR) { + return assemble(AND, v0.negation(), v1.negation()).negation(); + } + + if (v0==v1) return v0; + if (v0.label()==-v1.label()) return FALSE; + if (v0==TRUE) return v1; + if (v1==TRUE) return v0; + if (v0==FALSE) return FALSE; + if (v1==FALSE) return FALSE; + + return cache(op, (BooleanFormula)v0, (BooleanFormula)v1); + } + + /** + * Returns a boolean value with the same meaning as the given accumulator. + * @requires acc.components in (this.values + this.values.negation + BooleanConstant) + * @return v: BooleanValue | [[v]] = [[acc]] + * @ensures v in BooleanFormula - NotGate => this.values' = this.values + v, this.values' = this.values + * @throws NullPointerException - any of the arguments are null + */ + BooleanValue assemble(BooleanAccumulator acc) { + final int asize = acc.size(); + final Operator.Nary op = acc.op; + switch(asize) { + case 0 : return op.identity(); + case 1 : return acc.iterator().next(); + case 2 : + final Iterator inputs = acc.iterator(); + return assemble(op, inputs.next(), inputs.next()); + default : + final List vals = new LinkedList(); + for(BooleanValue v : acc) { + vals.add(v); + } + while(vals.size()>1) { + final ListIterator itr = vals.listIterator(); + for(int i = 0, max = vals.size()-1; i < max; i+=2) { + final BooleanValue v0 = itr.next(); + itr.remove(); + final BooleanValue v1 = itr.next(); + final BooleanValue v0opv1 = assemble(op, v0, v1); + if (v0opv1==op.shortCircuit()) return op.shortCircuit(); + else if (v0opv1==op.identity()) itr.remove(); + else itr.set(v0opv1); + } + } + return vals.get(0); + } + } + + /** + * Returns a BooleanFormula f such that [[f]] = f0 op f1. The method + * requires that the formulas f0 and f1 be already reduced with respect to op. + * A new formula is created and cached iff the circuit with the meaning + * [[f0]] op [[f1]] has not already been created. + * @requires f0 and f1 have already been reduced with respect to op; i.e. + * f0 op f1 cannot be reduced to a constant or a simple circuit + * by applying absorption, idempotence, etc. laws to f0 and f1. + * @return f : BooleanFormula | [[f]] = [[f0]] op [[f1]] + * @ensures f !in this.values => this.values' = this.values + f, + * this.values' = this.values + */ + private BooleanFormula cache(Operator.Nary op, BooleanFormula f0, BooleanFormula f1) { + final BooleanFormula l, h; + if (f0.label() gates = opCache(op).get(hash); gates.hasNext(); ) { + BooleanFormula gate = gates.next(); + if (gate.size()==2 && gate.input(0)==l && gate.input(1)==h) + return gate; + } + final BooleanFormula ret = new BinaryGate(op, label++, hash, l, h); + opCache(op).add(ret); + return ret; + } + + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/bool/TwosComplementInt.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/bool/TwosComplementInt.java new file mode 100644 index 00000000..053373d8 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/bool/TwosComplementInt.java @@ -0,0 +1,848 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.engine.bool; + +import static kodkod.engine.bool.BooleanConstant.FALSE; +import static kodkod.engine.bool.BooleanConstant.TRUE; +import static kodkod.engine.bool.Operator.AND; +import static kodkod.engine.bool.Operator.OR; + +import java.util.AbstractList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import kodkod.ast.Variable; +import kodkod.engine.bool.Operator.Nary; +import kodkod.engine.fol2sat.Environment; +import kodkod.util.collections.Containers; + +/** + * Two's complement integer representation. Supports comparisons, addition and subtraction. + * Integers are represented in little-endian (least significant bit first) order. + * @author Emina Torlak + */ +@SuppressWarnings("rawtypes") +final class TwosComplementInt extends Int { + private final BooleanValue[] bits; + + /** + * Constructs a TwosComplementInt out of the given factory and bits. + * @requires bits is well formed + * @ensures this.factory' = factory && this.bits' = bits + */ + private TwosComplementInt(BooleanFactory factory, BooleanValue[] bits, + BooleanValue overflow, BooleanValue accumOverflow) { + this(factory, bits, Collections.emptySet(), overflow, accumOverflow); + } + + private TwosComplementInt(BooleanFactory factory, BooleanValue[] bits, Collection vars, + BooleanValue overflow, BooleanValue accumOverflow) { + super(factory, vars); + this.bits = bits; + defCond().setOverflows(overflow, accumOverflow); + } + + /** + * Constructs a TwosComplementInt that represents either 0 or the given number, depending on + * the value of the given bit. + * @requires factory.encoding = TWOSCOMPLEMENT && bit in factory.components + * @ensures this.factory' = factory + * @ensures bits is a two's-complement representation of the given number + * that uses the provided bit in place of 1's + */ + TwosComplementInt(BooleanFactory factory, int number, BooleanValue bit) { + super(factory, Collections.emptySet()); + final int width = bitwidth(number); + this.bits = new BooleanValue[width]; + for(int i = 0; i < width; i++) { + bits[i] = (number & (1<this.factory.bitwidth bits. + */ + private boolean checkBounds(int num) { + return num >= minInt() && num <= maxInt(); + } + + /** + * Returns the min int representable using only this.factory.bitwidth bits. + */ + private int minInt() { return -(1 << (factory.bitwidth - 1)); } + + /** + * Returns the max int representable using only this.factory.bitwidth bits. + */ + private int maxInt() { return (1 << (factory.bitwidth - 1)) - 1; } + + /** + * ORs overflow circuits of this object ( + * this.mergedOverflow), a given other object ( + * other.mergedOverflow), and a given overflow circuit ( + * of) + */ + private BooleanValue mergeOverflows(Int other, BooleanValue of) { + return DefCond.merge(factory, of, defCond(), other.defCond()); + } + + /** + * Returns the number of bits needed/allowed to represent the given number. + * @return the number of bits needed/allowed to represent the given number. + */ + private int bitwidth(int number) { + if (number > 0) + return StrictMath.min(33 - Integer.numberOfLeadingZeros(number), factory.bitwidth); + else if (number < 0) + return StrictMath.min(33 - Integer.numberOfLeadingZeros(~number), factory.bitwidth); + else // number = 0 + return 1; + } + + /** + * {@inheritDoc} + * @see kodkod.engine.bool.Int#isConstant() + */ + public final boolean isConstant() { + for(int i = width()-1; i >= 0; i--) { + BooleanValue b = bit(i); + if (b!=TRUE && b!=FALSE) + return false; + } + return true; + } + + /** + * {@inheritDoc} + * @see kodkod.engine.bool.Int#twosComplementBits() + */ + @Override + public final List twosComplementBits() { + return new AbstractList() { + @Override + public BooleanValue get(int i) { + if (i < 0 || i >= factory.bitwidth) + throw new IndexOutOfBoundsException(); + return bit(i); + } + @Override + public int size() { return factory.bitwidth; } + }; + } + + /** + * {@inheritDoc} + * @see kodkod.engine.bool.Int#width() + */ + @Override + public int width() { + return bits.length; + } + + /** + * {@inheritDoc} + * @see kodkod.engine.bool.Int#value() + */ + public final int value() { + int ret = 0; + final int max = bits.length-1; + for(int i = 0; i < max; i++) { + if (bits[i]==TRUE) ret += 1<value. Otherwise, + * returns a conjunction of value, lhs.accumOverflow, + * and rhs.accumOverflow. + * + * ~~~ NOTE ~~~: Every time a BooleanValue is returned as a result of an operation + * over Ints, one of the ensureNoOverflow methods + * should be called. + */ + private BooleanValue ensureNoOverflow(Environment env, BooleanValue value, Int... ints) { + DefCond[] dcs = new DefCond[ints.length]; + for (int i = 0; i < ints.length; i++) dcs[i] = ints[i].defCond(); + return DefCond.ensureDef(factory, env, value, dcs); + } + + /** + * {@inheritDoc} + * @see kodkod.engine.bool.Int#eq(kodkod.engine.bool.Int) + */ + public final BooleanValue eq(Int other, Environment env) { + BooleanValue ret = eqWithoutOverflow(other); + return ensureNoOverflow(env, ret, this, other); + } + + public final BooleanValue neq(Int other, Environment env) { + BooleanValue ret = factory.not(eqWithoutOverflow(other)); + return ensureNoOverflow(env, ret, this, other); + } + + private BooleanValue eqWithoutOverflow(Int other) { + validate(other); + final BooleanAccumulator cmp = BooleanAccumulator.treeGate(AND); + for(int i = 0, width = StrictMath.max(width(), other.width()); i < width; i++) { + if (cmp.add(factory.iff(bit(i), other.bit(i)))==FALSE) + return FALSE; + } + BooleanValue ret = factory.accumulate(cmp); + return ret; + } + + /** + * {@inheritDoc} + * @see kodkod.engine.bool.Int#lt(kodkod.engine.bool.Int) + */ + public final BooleanValue lt(Int other, Environment env) { + final BooleanValue leq = lte(other); + final BooleanAccumulator acc = BooleanAccumulator.treeGate(OR); + for(int i = 0, width = StrictMath.max(width(), other.width()); i < width; i++) { + acc.add(factory.xor(bit(i), other.bit(i))); + } + BooleanValue ret = factory.and(leq, factory.accumulate(acc)); + return ensureNoOverflow(env, ret, this, other); + } + + /** + * {@inheritDoc} + * @see kodkod.engine.bool.Int#lte(kodkod.engine.bool.Int) + */ + @Override + public BooleanValue lte(Int other, Environment env) { + validate(other); + final BooleanAccumulator cmp = BooleanAccumulator.treeGate(Operator.AND); + final int last = StrictMath.max(width(), other.width())-1; + cmp.add(factory.implies(other.bit(last), bit(last))); + BooleanValue prevEquals = factory.iff(bit(last), other.bit(last)); + for(int i = last-1; i >= 0; i--) { + BooleanValue v0 = bit(i), v1 = other.bit(i); + cmp.add(factory.implies(prevEquals, factory.implies(v0, v1))); + prevEquals = factory.and(prevEquals, factory.iff(v0, v1)); + } + BooleanValue ret = factory.accumulate(cmp); + return ensureNoOverflow(env, ret, this, other); + } + + /** + * {@inheritDoc} + * @see kodkod.engine.bool.Int#plus(kodkod.engine.bool.Int) + */ + @Override + public Int plus(Int other) { + validate(other); + final int width = StrictMath.min(StrictMath.max(width(), other.width()) + 1, factory.bitwidth); + final BooleanValue[] plus = new BooleanValue[width]; + BooleanValue carry = FALSE; + BooleanValue c1 = FALSE; + BooleanValue c2 = FALSE; + for(int i = 0; i < width; i++) { + BooleanValue v0 = bit(i), v1 = other.bit(i); + plus[i] = factory.sum(v0, v1, carry); + carry = factory.carry(v0, v1, carry); + if (i == width-2) c2 = carry; + else if (i == width-1) c1 = carry; + } + BooleanValue overflow = FALSE; + if (width == factory.bitwidth) { + overflow = factory.xor(c1, c2); + } + BooleanValue accumOF = mergeOverflows(other, overflow); + return new TwosComplementInt(factory, plus, unionVars(this, other), overflow, accumOF); + } + + /** + * {@inheritDoc} + * @see kodkod.engine.bool.Int#minus(kodkod.engine.bool.Int) + */ + @Override + public Int minus(Int other) { + validate(other); + final int width = StrictMath.min(StrictMath.max(width(), other.width()) + 1, factory.bitwidth); + final BooleanValue[] minus = new BooleanValue[width]; + BooleanValue carry = TRUE; + BooleanValue c1 = FALSE; + BooleanValue c2 = FALSE; + for(int i = 0; i < width; i++) { + BooleanValue v0 = bit(i), v1 = other.bit(i).negation(); + minus[i] = factory.sum(v0, v1, carry); + carry = factory.carry(v0, v1, carry); + if (i == width-2) c2 = carry; + else if (i == width-1) c1 = carry; + } + BooleanValue overflow = FALSE; + if (width == factory.bitwidth) { + overflow = factory.xor(c1, c2); + } + BooleanValue accumOF = mergeOverflows(other, overflow); + return new TwosComplementInt(factory, minus, unionVars(this, other), overflow, accumOF); + } + + /** + * Adds the newBit and the given carry to this.bits[index] and returns the new carry. + * @requires 0 <= index < this.width + * @ensures this.bits'[index] = this.factory.sum(this.bits[index], newBit, cin) + * @return this.factory.carry(this.bits[index], newBit, cin) + */ + private BooleanValue addAndCarry(int index, BooleanValue newBit, BooleanValue cin) { + BooleanValue oldBit = bits[index]; + bits[index] = factory.sum(oldBit, newBit, cin); + return factory.carry(oldBit, newBit, cin); + } + + /** + * {@inheritDoc} + * @see kodkod.engine.bool.Int#multiply(kodkod.engine.bool.Int) + */ + //[AM] TODO: not optimal, uses double precision (too many bits)! + @Override + public Int multiply(Int other) { + validate(other); + final int retWidth = width() + other.width(); + final BooleanValue[] mult = new BooleanValue[retWidth]; + final Set unionVars = unionVars(this, other); + final TwosComplementInt ret = new TwosComplementInt(factory, mult, unionVars, FALSE, FALSE); + + /* first partial sum */ + BooleanValue iBit = bit(0); + for(int j = 0; j < retWidth; j++) { + mult[j] = factory.and(iBit, other.bit(j)); + } + + /* intermediate partial sums */ + int last = retWidth - 1; + for(int i = 1; i < last; i++) { + iBit = this.bit(i); + BooleanValue carry = FALSE; + for (int j = 0; j < retWidth - i; j++) { + BooleanValue bit = factory.and(iBit, other.bit(j)); + carry = ret.addAndCarry(i + j, bit, carry); + } + } + + /* last partial sum is subtracted (see http://en.wikipedia.org/wiki/Multiplication_ALU) */ + iBit = this.bit(last); + BooleanValue carry = TRUE; + for (int j = 0; j < retWidth - last; j++) { + BooleanValue bit = factory.and(iBit, other.bit(j)).negation(); + carry = ret.addAndCarry(last + j, bit, carry); + } + + final int width = StrictMath.min(mult.length, factory.bitwidth); + final BooleanValue[] multTrunc = new BooleanValue[width]; + for (int i = 0; i < multTrunc.length; i++) + multTrunc[i] = mult[i]; + + BooleanAccumulator acc = BooleanAccumulator.treeGate(Nary.OR); + for (int i = multTrunc.length; i < mult.length; i++) { + acc.add(factory.xor(mult[i - 1], mult[i])); + } + BooleanValue overflow = factory.accumulate(acc); + BooleanValue accumOF = mergeOverflows(other, overflow); + return new TwosComplementInt(factory, multTrunc, unionVars, overflow, accumOF); + } + + //@Override + public Int multiply_no_overflow_detection(Int other) { + validate(other); + final int width = StrictMath.min(width()+other.width(), factory.bitwidth); + final BooleanValue[] mult = new BooleanValue[width]; + final TwosComplementInt ret = new TwosComplementInt(factory, mult, unionVars(this, other), FALSE, FALSE); + + /* first partial sum */ + BooleanValue iBit = bit(0), carry; + for(int j = 0; j < width; j++) { + mult[j] = factory.and(iBit, other.bit(j)); + } + + final int last = width-1; + /* intermediate partial sums */ + for(int i = 1; i < last; i++) { + carry = FALSE; + iBit = bit(i); + for(int j = 0, jmax = width-i; j < jmax; j++) { + carry = ret.addAndCarry(j+i, factory.and(iBit, other.bit(j)), carry); + } + } + + /* last partial sum is subtracted (see http://en.wikipedia.org/wiki/Multiplication_ALU) */ + ret.addAndCarry(last, factory.and(this.bit(last), other.bit(0)).negation(), TRUE); + return ret; + } + + /** + * Returns an array of BooleanValues that represents the same + * integer as this, but using extwidth bits. + * @requires extwidth >= this.width() + * @return an array of BooleanValues that represents the same + * integer as this, but using extwidth bits. + */ + private BooleanValue[] extend(int extwidth) { + final BooleanValue[] ext = new BooleanValue[extwidth]; + final int width = width(); + for(int i = 0; i < width; i++) { + ext[i] = bits[i]; + } + final BooleanValue sign = bits[width-1]; + for(int i = width; i < extwidth; i++) { + ext[i] = sign; + } + return ext; + } + + /** + * Performs non-restoring signed division of this and the given integer. Returns + * the this.factory.bitwidth low-order bits of the quotient if the quotient flag + * is true; otherwise returns the this.factory.bitwidth low-order bits of the remainder. + * Both the quotionent and the remainder are given in little endian format. + * @see Behrooz Parhami, Computer Arithmetic: Algorithms and Hardware Designs, + * Oxford University Press, 2000, pp. 218-221. + * @requires this.factory = d.factory && d instanceof BinaryInt + * @return an array of boolean values, as described above + */ + private BooleanValue[] nonRestoringDivision(Int d, boolean quotient) { + final int width = factory.bitwidth, extended = width*2 + 1; + + // extend the dividend to bitwidth*2 + 1 and store it in s; the quotient will have width digits + final BooleanValue[] s = this.extend(extended), q = new BooleanValue[width]; + + // detects if one of the intermediate remainders is zero + final BooleanValue[] svalues = new BooleanValue[width]; + + BooleanValue carry, sbit, qbit, dbit; + + // the sign bit of the divisor + final BooleanValue dMSB = d.bit(width); + + int sleft = 0; // the index which contains the LSB of s + for(int i = 0; i < width; i++) { + svalues[i] = factory.accumulate(BooleanAccumulator.treeGate(Operator.OR, s)); + int sright = (sleft + extended - 1) % extended; // the index which contains the MSB of s + + // q[width-i-1] is 1 if sign(s_(i)) = sign(d), otherwise it is 0 + qbit = factory.iff(s[sright], dMSB); + q[width-i-1] = qbit; + + // shift s to the left by 1 -- simulated by setting sright to FALSE and sleft to sright + s[sright] = FALSE; + sleft = sright; + + // if sign(s_(i)) = sign(d), form s_(i+1) by subtracting (2^width)d from s_(i); + // otherwise, form s_(i+1) by adding (2^width)d to s_(i). + carry = qbit; + for(int di = 0, si = (sleft+width) % extended; di <= width; di++, si = (si+1) % extended) { + dbit = factory.xor(qbit, d.bit(di)); + sbit = s[si]; + s[si] = factory.sum(sbit, dbit, carry); + carry = factory.carry(sbit, dbit, carry); + } + } + + // s[0..width] holds the width+1 high order bits of s + assert (sleft+width) % extended == 0 ; + + // correction needed if one of the intermediate remainders is zero + // or s is non-zero and its sign differs from the sign of the dividend + final BooleanValue incorrect = factory.or( + factory.not(factory.accumulate(BooleanAccumulator.treeGate(Operator.AND, svalues))), + factory.and(factory.xor(s[width], this.bit(width)), + factory.accumulate(BooleanAccumulator.treeGate(Operator.OR, s)))); + final BooleanValue corrector = factory.iff(s[width], d.bit(width)); + + if (quotient) { // convert q to 2's complement, correct it if s is nonzero, and return + + // convert q to 2's complement: shift to the left by 1 and set LSB to TRUE + System.arraycopy(q, 0, q, 1, width-1); + q[0] = TRUE; + + // correct if incorrect evaluates to true as follows: if corrector evaluates to true, + // increment q; otherwise decrement q. + final BooleanValue sign = factory.and(incorrect, factory.not(corrector)); + carry = factory.and(incorrect, corrector); + + for(int i = 0; i < width; i++) { + qbit = q[i]; + q[i] = factory.sum(qbit, sign, carry); + carry = factory.carry(qbit, sign, carry); + } + + return q; + } else { // correct s if non-zero and return + + // correct if incorrect evaluates to true as follows: if corrector evaluates to true, + // subtract (2^width)d from s; otherwise add (2^width)d to s + carry = factory.and(incorrect, corrector); + + for(int i = 0; i <= width; i++) { + dbit = factory.and(incorrect, factory.xor(corrector, d.bit(i))); + sbit = s[i]; + s[i] = factory.sum(sbit, dbit, carry); + carry = factory.carry(sbit, dbit, carry); + } + + final BooleanValue[] r = new BooleanValue[width]; + System.arraycopy(s, 0, r, 0, width); + return r; + } + + } + + + /** + * {@inheritDoc} + * @see kodkod.engine.bool.Int#divide(kodkod.engine.bool.Int) + */ + @Override + public Int divide(Int other) { + validate(other); + TwosComplementInt ret = new TwosComplementInt(factory, nonRestoringDivision(other, true), + unionVars(this, other), FALSE, FALSE); + BooleanValue divByZero = other.eq(factory.integer(0)); + BooleanValue singleOverflowCase = factory.and(this.eq(factory.integer(-(1 << (factory.bitwidth-1)))), other.eq(factory.integer(-1))); + BooleanValue overflow = factory.or(divByZero, singleOverflowCase); + BooleanValue accumOF = mergeOverflows(other, overflow); + ret.defCond().setOverflows(overflow, accumOF); + return ret; + } + + /** + * {@inheritDoc} + * @see kodkod.engine.bool.Int#modulo(kodkod.engine.bool.Int) + */ + @Override + public Int modulo(Int other) { + validate(other); + TwosComplementInt ret = new TwosComplementInt(factory, nonRestoringDivision(other, false), + unionVars(this, other), FALSE, FALSE); + BooleanValue divByZero = other.eq(factory.integer(0)); + BooleanValue accumOF = mergeOverflows(other, divByZero); + ret.defCond().setOverflows(FALSE, accumOF); + return ret; + } + + /** + * {@inheritDoc} + * @see kodkod.engine.bool.Int#choice(kodkod.engine.bool.BooleanValue, kodkod.engine.bool.Int) + */ + @Override + public Int choice(BooleanValue condition, Int other) { + validate(other); + final int width = StrictMath.max(width(), other.width()); + final BooleanValue[] choice = new BooleanValue[width]; + for(int i = 0; i < width; i++) { + choice[i] = factory.ite(condition, bit(i), other.bit(i)); + } + BooleanValue of = factory.ite(condition, defCond().getOverflow(), other.defCond().getOverflow()); + BooleanValue accumOF = factory.ite(condition, defCond().getAccumOverflow(), other.defCond().getAccumOverflow()); + return new TwosComplementInt(factory, choice, unionVars(this, other), of, accumOF); + } + + /** + * {@inheritDoc} + * @see kodkod.engine.bool.Int#and(kodkod.engine.bool.Int) + */ + @Override + public Int and(Int other) { + validate(other); + final int width = StrictMath.max(width(), other.width()); + final BooleanValue[] and = new BooleanValue[width]; + for(int i = 0; i < width; i++) { + and[i] = factory.and(bit(i), other.bit(i)); + } + Set unionVars = unionVars(this, other); + return new TwosComplementInt(factory, and, unionVars, FALSE, mergeOverflows(other, FALSE)); + } + + /** + * {@inheritDoc} + * @see kodkod.engine.bool.Int#or(kodkod.engine.bool.Int) + */ + @Override + public Int or(Int other) { + validate(other); + final int width = StrictMath.max(width(), other.width()); + final BooleanValue[] or = new BooleanValue[width]; + for(int i = 0; i < width; i++) { + or[i] = factory.or(bit(i), other.bit(i)); + } + return new TwosComplementInt(factory, or, unionVars(this, other), FALSE, mergeOverflows(other, FALSE)); + } + + /** + * {@inheritDoc} + * @see kodkod.engine.bool.Int#xor(kodkod.engine.bool.Int) + */ + @Override + public Int xor(Int other) { + validate(other); + final int width = StrictMath.max(width(), other.width()); + final BooleanValue[] xor = new BooleanValue[width]; + for(int i = 0; i < width; i++) { + xor[i] = factory.xor(bit(i), other.bit(i)); + } + return new TwosComplementInt(factory, xor, unionVars(this, other), FALSE, mergeOverflows(other, FALSE)); + } + + /** + * {@inheritDoc} + * @see kodkod.engine.bool.Int#shl(kodkod.engine.bool.Int) + */ + @Override + public Int shl(Int other) { + validate(other); + final int width = factory.bitwidth; + final TwosComplementInt shifted = new TwosComplementInt(factory, extend(width), unionVars(this, other), FALSE, FALSE); + final int max = 32 - Integer.numberOfLeadingZeros(width - 1); + BooleanAccumulator acc = BooleanAccumulator.treeGate(Nary.OR); + for(int i = 0; i < width; i++) { + int shift = 1 << i; + BooleanValue bit = other.bit(i); + // overflow: check if the bits being pushed out is different from the one immediately to the right of it + for(int x = 0; x < shift; x++) { + BooleanValue b1 = width-x-1 < 0 ? FALSE : shifted.bit(width-x-1); + BooleanValue b2 = width-x-2 < 0 ? FALSE : shifted.bit(width-x-2); + acc.add(factory.ite(bit, factory.xor(b1, b2), FALSE)); + } + if (i < max) { + for(int j = width-1; j >= 0; j--) { + shifted.bits[j] = factory.ite(bit, j < shift ? FALSE : shifted.bit(j-shift), shifted.bits[j]); + } + } + } + BooleanValue overflow = factory.accumulate(acc); + BooleanValue accumOF = mergeOverflows(other, overflow); + shifted.defCond().setOverflows(overflow, accumOF); + return shifted; + } + + /** + * Performs a right shift with the given extension. + */ + private Int shr(Int other, BooleanValue sign) { + validate(other); + final int width = factory.bitwidth; + final TwosComplementInt shifted = new TwosComplementInt(factory, extend(width), unionVars(this, other), FALSE, FALSE); + final int max = 32 - Integer.numberOfLeadingZeros(width - 1); + for(int i = 0; i < max; i++) { + int shift = 1 << i; + int fill = width - shift; + BooleanValue bit = other.bit(i); + for(int j = 0; j < width; j++) { + shifted.bits[j] = factory.ite(bit, j < fill ? shifted.bit(j+shift) : sign, shifted.bits[j]); + } + } + shifted.defCond().setOverflows(FALSE, mergeOverflows(other, FALSE)); + return shifted; + } + + /** + * {@inheritDoc} + * @see kodkod.engine.bool.Int#shr(kodkod.engine.bool.Int) + */ + @Override + public Int shr(Int other) { + return shr(other, FALSE); + } + + /** + * {@inheritDoc} + * @see kodkod.engine.bool.Int#sha(kodkod.engine.bool.Int) + */ + @Override + public Int sha(Int other) { + return shr(other, bits[bits.length-1]); + } + + /** + * {@inheritDoc} + * @see kodkod.engine.bool.Int#negate() + */ + @Override + public Int negate() { + return (new TwosComplementInt(factory, new BooleanValue[]{FALSE}, FALSE, FALSE)).minus(this); + } + + /** + * {@inheritDoc} + * @see kodkod.engine.bool.Int#not() + */ + @Override + public Int not() { + final int width = width(); + final BooleanValue[] inverse = new BooleanValue[width]; + for(int i = 0 ; i < width; i++) { + inverse[i] = factory.not(bits[i]); + } + return new TwosComplementInt(factory, inverse, defCond().vars(), FALSE, defCond().getAccumOverflow()); + } + + /** + * {@inheritDoc} + * @see kodkod.engine.bool.Int#abs() + */ + @Override + public Int abs() { + Int negated = negate(); + return choice(factory.not(bits[bits.length-1]), negated); + } + + /** + * {@inheritDoc} + * @see kodkod.engine.bool.Int#sgn() + */ + @Override + public Int sgn() { + final BooleanValue[] sgn = new BooleanValue[2]; + sgn[0] = factory.accumulate(BooleanAccumulator.treeGate(Operator.OR, bits)); + sgn[1] = bits[bits.length-1]; + return new TwosComplementInt(factory, sgn, defCond().vars(), FALSE, defCond().getAccumOverflow()); + } + + /** + * {@inheritDoc} + * @see java.lang.Object#toString() + */ + public String toString() { + return "b" + Arrays.toString(bits); + } + + /** + * If the plus flag is true, returns a sum of this and other ints, + * with a cascade of adders or logarithmic depth. If the plus flag + * is false, returns a product of this and other ints, with a cascade + * of multipliers of logarithmic depth. + * @return plus => PLUS(this, others) else MULTIPLY(this, others) + */ + private Int apply(boolean plus, Int...others) { + final Int[] ints = Containers.copy(others, 0, new Int[others.length+1], 1, others.length); + ints[0] = this; + for(int part = ints.length; part > 1; part -= part/2) { + final int max = part-1; + for(int i = 0; i < max; i += 2) { + ints[i/2] = plus ? ints[i].plus(ints[i+1]) : ints[i].multiply(ints[i+1]); + } + if (max%2==0) { // even max => odd number of entries + ints[max/2] = ints[max]; + } + } + return ints[0]; + } + + /** + * {@inheritDoc} + * @see kodkod.engine.bool.Int#plus(kodkod.engine.bool.Int[]) + */ + @Override + public Int plus(Int... others) { return apply(true, others); } + + /** + * {@inheritDoc} + * @see kodkod.engine.bool.Int#multiply(kodkod.engine.bool.Int[]) + */ + @Override + public Int multiply(Int... others) { return apply(false, others); } + + /** + * Applies the given nary operator to this and the given ints. + * @return op(this, others) + */ + private Int apply(Operator.Nary op, Int...others) { + int width = width(); + for(Int other : others) { + validate(other); + width = Math.max(width, other.width()); + } + final BooleanValue[] bits = new BooleanValue[width]; + final BooleanValue shortCircuit = op.shortCircuit(); + for(int i = 0; i < width; i++) { + final BooleanAccumulator acc = BooleanAccumulator.treeGate(op, bit(i)); + for(Int other : others) { + if (acc.add(other.bit(i))==shortCircuit) break; + } + bits[i] = factory.accumulate(acc); + } + + final BooleanValue[] allOverflows = new BooleanValue[others.length + 1]; + final Set allVars = new HashSet(); + allOverflows[0] = defCond().getAccumOverflow(); + allVars.addAll(defCond().vars()); + for (int i = 1; i < allOverflows.length; i++) { + allOverflows[i] = others[i-1].defCond().getAccumOverflow(); + allVars.addAll(others[i-1].defCond().vars()); + } + BooleanAccumulator overflowAcc = BooleanAccumulator.treeGate(op, allOverflows); + return new TwosComplementInt(factory, bits, allVars, FALSE, factory.accumulate(overflowAcc)); + } + + /** + * {@inheritDoc} + * @see kodkod.engine.bool.Int#and(kodkod.engine.bool.Int[]) + */ + @Override + public Int and(Int... others) { return apply(AND, others); } + + /** + * {@inheritDoc} + * @see kodkod.engine.bool.Int#or(kodkod.engine.bool.Int[]) + */ + @Override + public Int or(Int... others) { return apply(OR, others); } + + private static Set unionVars(Int int1, Int int2) { + Set union = new HashSet(); + union.addAll(int1.defCond().vars()); + union.addAll(int2.defCond().vars()); + return union; + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/bool/package.html b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/bool/package.html new file mode 100644 index 00000000..2bac8bb0 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/bool/package.html @@ -0,0 +1,42 @@ + + + + + + + + +Provides classes for constructing and composing boolean matrices, boolean circuits, and +boolean representations of integers. + +

Package Specification

+ +

Contains classes that represent {@linkplain kodkod.engine.bool.BooleanMatrix boolean matrices}, +{@linkplain kodkod.engine.bool.BooleanValue boolean circuits}, and {@linkplain kodkod.engine.bool.Int boolean +representation of integers}. Matrices, circuits, and integers are constructed via factory methods of the + {@linkplain kodkod.engine.bool.BooleanFactory} class.

+ +

Related Documentation

+ +@see kodkod.engine.bool.BooleanFactory +@see kodkod.engine.bool.BooleanValue +@see kodkod.engine.bool.BooleanMatrix +@see kodkod.engine.bool.Int + + + diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/config/AbstractReporter.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/config/AbstractReporter.java new file mode 100644 index 00000000..1d00e72d --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/config/AbstractReporter.java @@ -0,0 +1,95 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.engine.config; + + +import java.util.List; +import java.util.Set; + +import kodkod.ast.Decl; +import kodkod.ast.Formula; +import kodkod.ast.Relation; +import kodkod.engine.bool.BooleanFormula; +import kodkod.instance.Bounds; +import kodkod.util.ints.IntSet; + +/** + * A skeleton implementation of the {@link Reporter} interface. + * The default implementation for each method has an empty body.s + * @author Emina Torlak + */ +public abstract class AbstractReporter implements Reporter { + + /** + * Constructs a new abstract reporter. + */ + protected AbstractReporter() {} + + /** + * {@inheritDoc} + * @see kodkod.engine.config.Reporter#detectingSymmetries(kodkod.instance.Bounds) + */ + public void detectingSymmetries(Bounds bounds){} + + /** + * {@inheritDoc} + * @see kodkod.engine.config.Reporter#detectedSymmetries(java.util.Set) + */ + public void detectedSymmetries(Set parts) {} + + /** + * @see kodkod.engine.config.Reporter#generatingSBP() + */ + public void generatingSBP() {} + + /** + * @see kodkod.engine.config.Reporter#flattening(kodkod.engine.bool.BooleanFormula) + */ + public void flattening(BooleanFormula circuit) {} + + /** + * {@inheritDoc} + * @see kodkod.engine.config.Reporter#skolemizing(kodkod.ast.Decl, kodkod.ast.Relation, java.util.List) + */ + public void skolemizing(Decl decl, Relation skolem, List context) {} + + /** + * @see kodkod.engine.config.Reporter#solvingCNF(int, int, int) + */ + public void solvingCNF(int primaryVars, int vars, int clauses) {} + + /** + * @see kodkod.engine.config.Reporter#optimizingBoundsAndFormula() + */ + public void optimizingBoundsAndFormula() {} + + /** + * @see kodkod.engine.config.Reporter#translatingToBoolean(kodkod.ast.Formula, kodkod.instance.Bounds) + */ + public void translatingToBoolean(Formula formula, Bounds bounds) {} + + /** + * @see kodkod.engine.config.Reporter#translatingToCNF(kodkod.engine.bool.BooleanFormula) + */ + public void translatingToCNF(BooleanFormula circuit) {} + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/config/ConsoleReporter.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/config/ConsoleReporter.java new file mode 100644 index 00000000..36c1ce50 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/config/ConsoleReporter.java @@ -0,0 +1,123 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.engine.config; + + +import java.util.List; +import java.util.Set; + +import kodkod.ast.Decl; +import kodkod.ast.Formula; +import kodkod.ast.Relation; +import kodkod.engine.bool.BooleanFormula; +import kodkod.instance.Bounds; +import kodkod.util.ints.IntSet; + +/** + * An implementation of the reporter interface that prints messages + * to the standard output stream. + * @author Emina Torlak + */ +public final class ConsoleReporter implements Reporter { + + /** + * Constructs a new instance of the ConsoleReporter. + */ + public ConsoleReporter() {} + + /** + * @see kodkod.engine.config.Reporter#generatingSBP() + */ + public void generatingSBP() { + System.out.println("generating lex-leader symmetry breaking predicate ..."); + } + + /** + * @see kodkod.engine.config.Reporter#flattening(kodkod.engine.bool.BooleanFormula) + */ + public void flattening(BooleanFormula circuit) { + System.out.println("flattening ..."); + } + + /** + * {@inheritDoc} + * @see kodkod.engine.config.Reporter#skolemizing(kodkod.ast.Decl, kodkod.ast.Relation, java.util.List) + */ + public void skolemizing(Decl decl, Relation skolem, List context) { + System.out.println("skolemizing " + decl + ": skolem relation=" + skolem + ", arity=" + skolem.arity()); + } + + /** + * @see kodkod.engine.config.Reporter#solvingCNF(int, int, int) + */ + public void solvingCNF(int primaryVars, int vars, int clauses) { + System.out.println("solving p cnf " + vars + " " + clauses); + } + + + /** + * {@inheritDoc} + * @see kodkod.engine.config.Reporter#detectingSymmetries(kodkod.instance.Bounds) + */ + public void detectingSymmetries(Bounds bounds){ + System.out.println("detecting symmetries ..."); + } + + /** + * {@inheritDoc} + * @see kodkod.engine.config.Reporter#detectedSymmetries(java.util.Set) + */ + public void detectedSymmetries(Set parts) { +// System.out.println("detected symmetries: " + parts); + } + + /** + * @see kodkod.engine.config.Reporter#optimizingBoundsAndFormula() + */ + public void optimizingBoundsAndFormula() { + System.out.println("optimizing bounds and formula (breaking predicate symmetries, inlining, skolemizing) ..."); + } + + /** + * @see kodkod.engine.config.Reporter#translatingToBoolean(kodkod.ast.Formula, kodkod.instance.Bounds) + */ + public void translatingToBoolean(Formula formula, Bounds bounds) { + System.out.println("translating to boolean ..."); + } + + /** + * @see kodkod.engine.config.Reporter#translatingToCNF(kodkod.engine.bool.BooleanFormula) + */ + public void translatingToCNF(BooleanFormula circuit) { + System.out.println("translating to cnf ..."); + } + + /** + * @see java.lang.Object#toString() + */ + public String toString() { + return "ConsoleReporter"; + } + + + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/config/Options.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/config/Options.java new file mode 100644 index 00000000..be453f46 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/config/Options.java @@ -0,0 +1,414 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.engine.config; + +import kodkod.engine.satlab.SATFactory; +import kodkod.util.ints.IntRange; +import kodkod.util.ints.Ints; + +/** + * Stores information about various + * user-level translation and analysis options. It can be + * used to choose the SAT solver, control symmetry breaking, etc. + * + * @specfield solver: SATFactory // SAT solver factory to use + * @specfield reporter: Reporter // reporter to use + * @specfield symmetryBreaking: int // the amount of symmetry breaking to perform + * @specfield sharing: int // the depth to which circuits should be checked for equivalence during translation + * @specfield intEncoding: IntEncoding // encoding to use for translating int expressions + * @specfield bitwidth: int // the bitwidth to use for integer representation / arithmetic + * @specfield skolemDepth: int // skolemization depth + * @specfield nof: boolean // no overflow (detect and forbid overflows) + * @specfield flatten: boolean // eliminate intermediate variables when possible? default is false. + * @specfield logTranslation: [0..2] // log translation events, default is 0 (no logging) + * @specfield coreGranularity: [0..3] // unsat core granularity, default is 0 (only top-level conjuncts are considered) + * @author Emina Torlak + */ +public final class Options { + private Reporter reporter = new AbstractReporter(){}; + private SATFactory solver = SATFactory.DefaultSAT4J; + private int symmetryBreaking = 20; + private IntEncoding intEncoding = IntEncoding.TWOSCOMPLEMENT; + private int bitwidth = 4; + private int sharing = 3; + private boolean nof = false; + private int skolemDepth = 0; + private boolean flatten = false; + private int logTranslation = 0; + private int coreGranularity = 0; + + //[AM] + public static boolean isDebug() { + return false; //TODO: read from the environment or something + } + + /** + * Constructs an Options object initalized with + * default values. + * @ensures this.solver' = SATFactory.DefaultSAT4J + * this.reporter' is silent (no messages reported) + * this.symmetryBreaking' = 20 + * this.sharing' = 3 + * this.intEncoding' = BINARY + * this.bitwidth' = 4 + * this.skolemDepth' = 0 + * this.flatten' = false + * this.logTranslation' = 0 + * this.coreGranularity' = 0 + */ + public Options() {} + + /** + * Returns the value of the solver options. + * The default is SATSolver.DefaultSAT4J. + * @return this.solver + */ + public SATFactory solver() { + return solver; + } + + /** + * Sets the solver option to the given value. + * @ensures this.solver' = solver + * @throws NullPointerException - solver = null + */ + public void setSolver(SATFactory solver) { + if (solver==null) + throw new NullPointerException(); + this.solver = solver; + } + + /** + * Returns this.reporter. + * @return this.reporter + */ + public Reporter reporter() { + return reporter; + } + + /** + * Sets this.reporter to the given reporter. + * @requires reporter != null + * @ensures this.reporter' = reporter + * @throws NullPointerException - reporter = null + */ + public void setReporter(Reporter reporter) { + if (reporter==null) + throw new NullPointerException(); + this.reporter = reporter; + } + + /** + * Returns the noOverflow flag + */ + public boolean noOverflow() { return nof; } + + /** + * Sets the noOverflow flag + */ + public void setNoOverflow(boolean noOverflow) { this.nof = noOverflow; } + + /** + * @throws IllegalArgumentException - arg !in [min..max] + */ + private void checkRange(int arg, int min, int max) { + if (arg < min || arg > max) + throw new IllegalArgumentException(arg + " !in [" + min + ".." + max + "]"); + } + + + + /** + * Returns the integer encoding that will be used for translating {@link kodkod.ast.IntExpression int nodes}. + * The default is BINARY representation, which allows negative numbers. UNARY representation is best suited to + * problems with small scopes, in which cardinalities are only compared (and possibly added to each other or + * non-negative numbers). + * @return this.intEncoding + */ + public IntEncoding intEncoding() { + return intEncoding; + } + + /** + * Sets the intEncoding option to the given value. + * @ensures this.intEncoding' = encoding + * @throws NullPointerException - encoding = null + * @throws IllegalArgumentException - this.bitwidth is not a valid bitwidth for the specified encoding + */ + public void setIntEncoding(IntEncoding encoding) { + if (encoding.maxAllowedBitwidth() 32 + */ + public void setBitwidth(int bitwidth) { + checkRange(bitwidth, 1, intEncoding.maxAllowedBitwidth()); + this.bitwidth = bitwidth; + } + + /** + * Returns the range of integers that can be encoded + * using this.intEncoding and this.bitwidth. + * @return range of integers that can be encoded + * using this.intEncoding and this.bitwidth. + */ + public IntRange integers() { + return intEncoding.range(bitwidth); + } + + /** + * Returns the value of the flattening flag, which specifies whether + * to eliminate extraneous intermediate variables. The flag is false by default. + * Flattening must be off if translation logging is enabled. + * @return this.flatten + */ + public boolean flatten() { + return flatten; + } + + /** + * Sets the flattening option to the given value. + * @ensures this.flatten' = flatten + * @throws IllegalArgumentException - this.logTranslation>0 && flatten + */ + public void setFlatten(boolean flatten) { + if (logTranslation>0 && flatten) + throw new IllegalStateException("logTranslation enabled: flattening must be off."); + this.flatten = flatten; + } + + /** + * Returns the 'amount' of symmetry breaking to perform. + * If a non-symmetric solver is chosen for this.solver, + * this value controls the maximum length of the generated + * lex-leader symmetry breaking predicate. If a symmetric + * solver is chosen, this value controls the amount of + * symmetry information to pass to the solver. (For example, + * if a formula has 10 relations on which symmetry can be broken, + * and the symmetryBreaking option is set to 5, then symmetry information + * will be computed for only 5 of the 10 relations.) In general, + * the higher this value, the more symmetries will be broken, and the + * faster the formula will be solved. But, setting the value too high + * may have the opposite effect and slow down the solving. The default + * value for this property is 20. + * @return this.symmetryBreaking + */ + public int symmetryBreaking() { + return symmetryBreaking; + } + + /** + * Sets the symmetryBreaking option to the given value. + * @ensures this.symmetryBreaking' = symmetryBreaking + * @throws IllegalArgumentException - symmetryBreaking !in [0..Integer.MAX_VALUE] + */ + public void setSymmetryBreaking(int symmetryBreaking) { + checkRange(symmetryBreaking, 0, Integer.MAX_VALUE); + this.symmetryBreaking = symmetryBreaking; + } + + /** + * Returns the depth to which circuits are checked for equivalence during translation. + * The default depth is 3, and the minimum allowed depth is 1. Increasing the sharing + * may result in a smaller CNF, but at the cost of slower translation times. + * @return this.sharing + */ + public int sharing() { + return sharing; + } + + /** + * Sets the sharing option to the given value. + * @ensures this.sharing' = sharing + * @throws IllegalArgumentException - sharing !in [1..Integer.MAX_VALUE] + */ + public void setSharing(int sharing) { + checkRange(sharing, 1, Integer.MAX_VALUE); + this.sharing = sharing; + } + + /** + * Returns the depth to which existential quantifiers are skolemized. + * A negative depth means that no skolemization is performed. + * The default depth of 0 means that only existentials that are not nested + * within a universal quantifiers are skolemized. A depth of 1 means that + * existentials nested within a single universal are also skolemized, etc. + * @return this.skolemDepth + */ + public int skolemDepth() { + return skolemDepth; + } + + /** + * Sets the skolemDepth to the given value. + * @ensures this.skolemDepth' = skolemDepth + */ + public void setSkolemDepth(int skolemDepth) { + this.skolemDepth = skolemDepth; + } + + /** + * Returns the translation logging level (0, 1, or 2), where 0 + * means logging is not performed, 1 means only the translations of + * top level formulas are logged, and 2 means all formula translations + * are logged. This is necessary for determining which formulas occur in the unsat core of an + * unsatisfiable formula. Flattening must be off whenever + * logging is enabled. Logging is off by default, since + * it incurs a non-trivial time overhead. + * @return this.logTranslation + */ + public int logTranslation() { + return logTranslation; + } + + /** + * Sets the translation logging level. If the level is above 0, + * flattening is automatically disabled. + * @requires logTranslation in [0..2] + * @ensures this.logTranslation' = logTranslation && + * logTranslation>0 => this.flatten' = false + * @throws IllegalArgumentException - logTranslation !in [0..2] + */ + public void setLogTranslation(int logTranslation) { + checkRange(logTranslation, 0, 2); + if (logTranslation>0) { + flatten = false; + } + this.logTranslation = logTranslation; + } + + /** + * Returns the core granularity level. The default is 0, which means that top-level + * conjuncts of the input formula are used as "roots" for the purposes of core minimization and extraction. Granularity + * of 1 means that the top-level conjuncts of the input formula's negation normal form (NNF) are + * used as roots; granularity of 2 means that the top-level conjuncts of the formula's skolemized + * NNF (SNNF) are used as roots; and, finally, a granularity of 3 means that the universal quantifiers of the formula's + * SNNF are broken up and that the resulting top-level conjuncts are then used as roots for core minimization and extraction. + * + *

Note that the finer granularity (that is, a larger value of this.coreGranularity) will provide better information at + * the cost of slower core extraction and, in particular, minimization.

+ * + * @return this.coreGranularity + */ + public int coreGranularity() { + return coreGranularity; + } + + /** + * Sets the core granularity level. + * @requires coreGranularity in [0..3] + * @ensures this.coreGranularity' = coreGranularity + * @throws IllegalArgumentException - coreGranularity !in [0..3] + */ + public void setCoreGranularity(int coreGranularity) { + checkRange(coreGranularity, 0, 3); + this.coreGranularity = coreGranularity; + } + + /** + * Returns a string representation of this Options object. + * @return a string representation of this Options object. + */ + public String toString() { + StringBuilder b = new StringBuilder(); + b.append("Options:"); + b.append("\n solver: "); + b.append(solver); + b.append("\n reporter: "); + b.append(reporter); + b.append("\n intEncoding: "); + b.append(intEncoding); + b.append("\n bitwidth: "); + b.append(bitwidth); + b.append("\n sharing: "); + b.append(sharing); + b.append("\n flatten: "); + b.append(flatten); + b.append("\n symmetryBreaking: "); + b.append(symmetryBreaking); + b.append("\n noOverflow: "); + b.append(nof); + b.append("\n skolemDepth: "); + b.append(skolemDepth); + b.append("\n logTranslation: "); + b.append(logTranslation); + b.append("\n coreGranularity: "); + b.append(coreGranularity); + return b.toString(); + } + + /** + * Integer encoding options for the translation of + * {@link kodkod.ast.IntExpression int expressions}. + */ + public static enum IntEncoding { + /** + * Two's-complement encoding of integers supports + * comparisons, addition, subtraction, multiplication, + * division, and all low-level bit operations + * (shifting, and, or, not, etc.). Maximum allowed + * bitwidth for this encoding is 32 bits. + */ + TWOSCOMPLEMENT { + @Override + int maxAllowedBitwidth() { return 32; } + @Override + IntRange range(int bitwidth) { + final int shift = bitwidth-1; + return Ints.range(-1< 0 + * @return range of integers representable with + * this encoding using the given number of bits. + */ + abstract IntRange range(int bitwidth) ; + } + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/config/Reporter.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/config/Reporter.java new file mode 100644 index 00000000..219578d1 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/config/Reporter.java @@ -0,0 +1,108 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.engine.config; + + +import java.util.List; +import java.util.Set; + +import kodkod.ast.Decl; +import kodkod.ast.Formula; +import kodkod.ast.Relation; +import kodkod.engine.bool.BooleanFormula; +import kodkod.instance.Bounds; +import kodkod.util.ints.IntSet; + +/** + * Enables passing of messages between the kodkod engine + * and the client about the following stages of the analysis: + *
    + *
  1. symmetry detection + *
  2. bounds and formula optimization (breaking of predicate symmetries, predicate inlining and skolemization)
  3. + *
  4. translation to a boolean circuit
  5. + *
  6. symmetry breaking predicate (SBP) generation
  7. + *
  8. circuit flattening
  9. + *
  10. translation to cnf
  11. + *
  12. running a sat solver on the generated cnf
  13. + *
+ * Some of these stages may not be executed, depending on the + * {@link Options options} used for analysis. + * @author Emina Torlak + */ +public interface Reporter { + + /** + * Reports that symmetry detection started on the given bounds. + * The given bounds must not be mutated. + */ + public void detectingSymmetries(Bounds bounds); + + /** + * Reports the symmetry partitions that were detected. + * The given partitions must not be mutated. + */ + public void detectedSymmetries(Set parts); + + /** + * Reports that bounds optimization is in progress (stage 2). + */ + public void optimizingBoundsAndFormula(); + + /** + * Reports that the given declaration is being skolemized using the + * given skolem relation. The context list contains non-skolemizable + * quantified declarations on which the given decl depends, in the order of declaration + * (most recent decl is last in the list). + */ + public void skolemizing(Decl decl, Relation skolem, List context); + + /** + * Reports that the analysis of the given (optimized) formula + * and bounds is in stage 3. The given bounds must not be mutated. + * @ensures bounds' = bounds + */ + public void translatingToBoolean(Formula formula, Bounds bounds); + + /** + * Reports that the analysis is in stage 4. + */ + public void generatingSBP(); + + /** + * Reports that the stage 5 of the analysis is + * being performed on the given boolean formula. + */ + public void flattening(BooleanFormula circuit); + + /** + * Reports that the given (optimized) + * circuit is being translated to CNF (stage 6 of the analysis). + */ + public void translatingToCNF(BooleanFormula circuit); + + /** + * Reports that the cnf generated in stage 7, consisting of the + * given number of variables and clauses, is being analyzed by + * a sat solver (stage 8 of the analysis). + */ + public void solvingCNF(int primaryVars, int vars, int clauses); +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/config/package.html b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/config/package.html new file mode 100644 index 00000000..50a3a06b --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/config/package.html @@ -0,0 +1,42 @@ + + + + + + + + +Provides a mechanism for configuring the kodkod engine and for passing messages +between the engine and the client. + +

Package Specification

+ +

Provides a mechanism for configuring the kodkod engine and for passing messages +between the engine and the client. The class {@linkplain kodkod.engine.config.Options} +stores information about various user-level translation and analysis options. It can be +used to choose the SAT solver, control symmetry breaking, etc. The interface +{@linkplain kodkod.engine.config.Reporter} enables passing of messages between the kodkod engine +and the client via callback methods.

+ +

Related Documentation

+ +@see kodkod.engine.config.Options +@see kodkod.engine.config.Reporter + + + diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/fol2sat/Bool2CNFTranslator.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/fol2sat/Bool2CNFTranslator.java new file mode 100644 index 00000000..01a00543 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/fol2sat/Bool2CNFTranslator.java @@ -0,0 +1,302 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.engine.fol2sat; + +import static kodkod.engine.bool.Operator.AND; +import kodkod.engine.bool.BooleanFormula; +import kodkod.engine.bool.BooleanVariable; +import kodkod.engine.bool.BooleanVisitor; +import kodkod.engine.bool.ITEGate; +import kodkod.engine.bool.MultiGate; +import kodkod.engine.bool.NotGate; +import kodkod.engine.bool.Operator; +import kodkod.engine.satlab.SATFactory; +import kodkod.engine.satlab.SATSolver; +import kodkod.util.ints.IntSet; +import kodkod.util.ints.Ints; + +/** + * Transforms a boolean circuit into a formula in conjunctive + * normal form. + * + * @author Emina Torlak + */ +final class Bool2CNFTranslator implements BooleanVisitor { + + /** + * Creates a new instance of SATSolver using the provided factory + * and uses it to translate the given circuit into conjunctive normal form + * using the definitional translation algorithm. + * The third parameter is required to contain the number of primary variables + * allocated during translation from FOL to boolean. + * @return a SATSolver instance returned by the given factory and initialized + * to contain the CNF translation of the given circuit. + */ + static SATSolver translate(BooleanFormula circuit, SATFactory factory, int numPrimaryVariables) { + final SATSolver solver = factory.instance(); + final Bool2CNFTranslator translator = new Bool2CNFTranslator(solver, numPrimaryVariables, circuit); +// System.out.println("--------------transls2-------------"); + if (circuit.op()==Operator.AND) { + for(BooleanFormula input : circuit) { +// System.out.println(input); +// solver.addClause(input.accept(translator,null)); + input.accept(translator, null); + } + for(BooleanFormula input : circuit) { + translator.unaryClause[0] = input.label(); + solver.addClause(translator.unaryClause); + } + } else { + solver.addClause(circuit.accept(translator,null)); + } + return solver; + } + + /** + * Helper visitor that performs definitional translation to cnf . + * @specfield root: BooleanFormula // the translated circuit + */ + + private final SATSolver solver; + private final IntSet visited; + private final PolarityDetector pdetector; + private final int[] unaryClause = new int[1]; + private final int[] binaryClause = new int[2]; + private final int[] ternaryClause = new int[3]; + + /** + * Constructs a translator for the given circuit. + * @ensures this.root' = circuit + */ + private Bool2CNFTranslator(SATSolver solver, int numPrimaryVars, BooleanFormula circuit) { + final int maxLiteral = StrictMath.abs(circuit.label()); + this.solver = solver; + this.solver.addVariables(StrictMath.max(numPrimaryVars, maxLiteral)); + this.pdetector = (new PolarityDetector(numPrimaryVars, maxLiteral)).apply(circuit); + this.visited = Ints.bestSet(pdetector.offset, StrictMath.max(pdetector.offset, maxLiteral)); + } + + /** @return 0->lit */ + final int[] clause(int lit) { + unaryClause[0] = lit; + return unaryClause; + } + /** @return 0->lit0 + 1->lit1 */ + final int[] clause(int lit0, int lit1) { + binaryClause[0] = lit0; binaryClause[1] = lit1; + return binaryClause; + } + /** @return 0->lit0 + 1->lit1 + 2->lit2 */ + final int[] clause(int lit0, int lit1, int lit2) { + ternaryClause[0] = lit0; ternaryClause[1] = lit1; ternaryClause[2] = lit2; + return ternaryClause; + } + + /** + * Adds translation clauses to the solver and returns an array containing the + * gate's literal. The CNF clauses are generated according to the standard SAT to CNF translation: + * o = AND(i1, i2, ... ik) ---> (i1 | !o) & (i2 | !o) & ... & (ik | !o) & (!i1 | !i2 | ... | !ik | o), + * o = OR(i1, i2, ... ik) ---> (!i1 | o) & (!i2 | o) & ... & (!ik | o) & (i1 | i2 | ... | ik | !o). + * @return o: int[] | o.length = 1 && o.[0] = multigate.literal + * @ensures if the multigate has not yet been visited, its children are visited + * and the clauses are added to the solver connecting the multigate's literal to + * its input literal, as described above. + */ + public int[] visit(MultiGate multigate, Object arg) { + final int oLit = multigate.label(); + if (visited.add(oLit)) { + final int sgn; final boolean p, n; + if (multigate.op()==AND) { + sgn = 1; p = pdetector.positive(oLit); n = pdetector.negative(oLit); + } else { // multigate.op()==OR + sgn = -1; n = pdetector.positive(oLit); p = pdetector.negative(oLit); + } + final int[] lastClause = n ? new int[multigate.size()+1] : null; + final int output = oLit * -sgn; + int i = 0; + for(BooleanFormula input : multigate) { + int iLit = input.accept(this, arg)[0]; + if (p) { + solver.addClause(clause(iLit * sgn, output)); + } + if (n) { + lastClause[i++] = iLit * -sgn; + } + } + if (n) { + lastClause[i] = oLit * sgn; + solver.addClause(lastClause); + } + } + return clause(oLit); + } + + /** + * Adds translation clauses to the solver and returns an array containing the + * gate's literal. The CNF clauses are generated according to the standard SAT to CNF translation: + * o = ITE(i, t, e) ---> (!i | !t | o) & (!i | t | !o) & (i | !e | o) & (i | e | !o) + * @return o: int[] | o.length = 1 && o.[0] = itegate.literal + * @ensures if the itegate has not yet been visited, its children are visited + * and the clauses are added to the solver connecting the multigate's literal to + * its input literal, as described above. + */ + public int[] visit(ITEGate itegate, Object arg) { + final int oLit = itegate.label(); + if (visited.add(oLit)) { + final int i = itegate.input(0).accept(this, arg)[0]; + final int t = itegate.input(1).accept(this, arg)[0]; + final int e = itegate.input(2).accept(this, arg)[0]; + final boolean p = pdetector.positive(oLit), n = pdetector.negative(oLit); + if (p) { + solver.addClause(clause(-i, t, -oLit)); + solver.addClause(clause(i, e, -oLit)); + // redundant clause that strengthens unit propagation + solver.addClause(clause(t, e, -oLit)); + } + if (n) { + solver.addClause(clause(-i, -t, oLit)); + solver.addClause(clause(i, -e, oLit)); + // redundant clause that strengthens unit propagation + solver.addClause(clause(-t, -e, oLit)); + } + } + return clause(oLit); + } + + /** + * Returns the negation of the result of visiting negation.input, wrapped in + * an array. + * @return o: int[] | o.length = 1 && o[0] = - translate(negation.inputs)[0] + * */ + public int[] visit(NotGate negation, Object arg) { + return clause(-negation.input(0).accept(this, arg)[0]); + } + + /** + * Returns the variable's literal wrapped in a an array. + * @return o: int[] | o.length = 1 && o[0] = variable.literal + */ + public int[] visit(BooleanVariable variable, Object arg) { + return clause(variable.label()); + } + + + /** + * Helper visitor that detects pdetector of subformulas. + * @specfield root: BooleanFormula // the root of the DAG for whose components we are storing pdetector information + */ + private static final class PolarityDetector implements BooleanVisitor { + final int offset; + /** + * @invariant all i : [0..polarity.length) | + * pdetector[i] = 0 <=> formula with label offset + i has not been visited, + * pdetector[i] = 1 <=> formula with label offset + i has been visited with positive pdetector only, + * pdetector[i] = 2 <=> formula with label offset + i has been visited with negative pdetector only, + * pdetector[i] = 3 <=> formula with label offset + i has been visited with both polarities + */ + private final int[] polarity; + private final Integer[] ints = { Integer.valueOf(3), Integer.valueOf(1), Integer.valueOf(2) }; + + /** + * Creates a new pdetector detector and applies it to the given circuit. + * @requires maxLiteral = |root.label()| + */ + PolarityDetector(int numPrimaryVars, int maxLiteral) { + this.offset = numPrimaryVars+1; + this.polarity = new int[StrictMath.max(0, maxLiteral-numPrimaryVars)]; + } + + /** + * Applies this detector to the given formula, and returns this. + * @requires this.root = root + * @ensures this.visit(root) + * @return this + */ + PolarityDetector apply(BooleanFormula root) { + root.accept(this, ints[1]); + return this; + } + + /** + * Returns true if the formula with the given label occurs positively in this.root. + * @requires this visitor has been applied to this.root + * @requires label in (MultiGate + ITEGate).label + * @return true if the formula with the given label occurs positively in this.root. + */ + boolean positive(int label) { + return (polarity[label-offset] & 1) > 0; + } + + /** + * Returns true if the formula with the given label occurs negatively in this.root. + * @requires this visitor has been applied to this.root + * @requires label in (MultiGate + ITEGate).label + * @return true if the formula with the given label occurs negatively in this.root. + */ + boolean negative(int label) { + return (polarity[label-offset] & 2) > 0; + } + + /** + * Returns true if the given formula has been visited with the specified + * pdetector (1 = positive, 2 = negative, 3 = both). Otherwise records the visit and returns false. + * @requires formula in (MultiGate + ITEGate) + * @requires pdetector in this.ints + * @return true if the given formula has been visited with the specified + * pdetector. Otherwise records the visit and returns false. + */ + private boolean visited(BooleanFormula formula, Integer polarity) { + final int index = formula.label() - offset; + final int value = this.polarity[index]; + return (this.polarity[index] = value | polarity) == value; + } + + public Object visit(MultiGate multigate, Integer arg) { + if (!visited(multigate, arg)) { + for(BooleanFormula input : multigate) { + input.accept(this, arg); + } + } + return null; + } + + public Object visit(ITEGate ite, Integer arg) { + if (!visited(ite, arg)) { + // the condition occurs both positively and negative in an ITE gate + ite.input(0).accept(this, ints[0]); + ite.input(1).accept(this, arg); + ite.input(2).accept(this, arg); + } + return null; + } + + public Object visit(NotGate negation, Integer arg) { + return negation.input(0).accept(this, ints[3-arg]); + } + + public Object visit(BooleanVariable variable, Integer arg) { + return null; // nothing to do + } + + } + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/fol2sat/BooleanFormulaFlattener.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/fol2sat/BooleanFormulaFlattener.java new file mode 100644 index 00000000..120b3e07 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/fol2sat/BooleanFormulaFlattener.java @@ -0,0 +1,230 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.engine.fol2sat; + +import java.util.IdentityHashMap; +import java.util.Iterator; +import java.util.Map; + +import kodkod.engine.bool.BooleanAccumulator; +import kodkod.engine.bool.BooleanFactory; +import kodkod.engine.bool.BooleanFormula; +import kodkod.engine.bool.BooleanValue; +import kodkod.engine.bool.BooleanVariable; +import kodkod.engine.bool.BooleanVisitor; +import kodkod.engine.bool.ITEGate; +import kodkod.engine.bool.MultiGate; +import kodkod.engine.bool.NotGate; +import kodkod.engine.bool.Operator; +import kodkod.util.ints.IntSet; +import kodkod.util.ints.Ints; + +/** + *

Given a {@link kodkod.engine.bool.BooleanValue boolean value}, v, and a + * {@link kodkod.engine.bool.BooleanFactory factory}, F, + * a BooleanFormulaFlattener eliminates as many + * intermediate gates as possible from v, and stores the flattened tree of v in F. + * An intermediate gate's inputs are absorbed into the parent's iff the + * the gate's fanout is 1 and the gate and its parent are MultiGates with the same operator. + * For example, suppose that the root corresponds to the formula + * ((1 || 2) || 3 || !(4 || 5) || (6 & (7 & 8))), and that the components of this formula are + * assigned the following labels: (1 || 2) ---> 9, (4 || 5) ---> 10, !(4 || 5) ---> -10, (7 & 8) ---> 11, + * (6 & (7 & 8)) ---> 12, and ((1 || 2) || 3 || !(4 || 5) || (6 & 7 & 8)) ---> 13. + * Calling this.flatten(root) will flatten the root to (1 || 2 || 3 || !(4 || 5) || (6 & 7 & 8)), + * re-assigning the labels as follows: (4 || 5) ---> 9, !(4 || 5) ---> -9, (6 & 7 & 8) ---> 10, and + * (1 || 2 || 3 || !(4 || 5) || (6 & 7 & 8)) ---> 11. + * + * @author Emina Torlak + */ +final class BooleanFormulaFlattener implements BooleanVisitor { + + + + /** + * Flattens the given value using f and returns it. + * The method assumes that all variables at the leaves of + * the root are components of f. + * @requires root.*inputs in f.components + * @ensures f.components' = f.components & Variable + flatRoot.*inputs + * @return {flatRoot : BooleanValue | [[flatRoot]] = [[root]] && + * no d, p: flatRoot.*inputs & MultiGate | d in p.inputs && d.op = p.op && inputs.d != p } + */ + static final BooleanValue flatten(BooleanFormula root, BooleanFactory f) { + final int oldCompDepth = f.comparisonDepth(); + f.setComparisonDepth(1); + f.clear(); // remove everything but the variables from the factory + final BooleanFormulaFlattener flattener = new BooleanFormulaFlattener(root, f); + final BooleanValue flatRoot = root.accept(flattener, null); + f.setComparisonDepth(oldCompDepth); + return flatRoot; + } + + private final BooleanFactory factory; + private final IntSet flattenable; + private final Map cache; + + /** + * Constructs a new FlatteningVisitor. The returned visitor can only be applied to the specified + * root value. All the variables at the leaves of the given root must have been created by the + * given factory. + * @requires (root.*inputs & Variable) in factory.components + */ + private BooleanFormulaFlattener(BooleanFormula root, BooleanFactory factory) { + this.factory = factory; + final FlatteningDataGatherer dataGatherer = new FlatteningDataGatherer(root); + root.accept(dataGatherer, null); + this.flattenable = dataGatherer.flattenable; + dataGatherer.visited.removeAll(flattenable); + this.cache = new IdentityHashMap(dataGatherer.visited.size()); + } + + /** + * If p is null, returns v. Otherwise, adds v to p and + * returns the result. + */ + private final BooleanValue addToParent(BooleanValue v, BooleanAccumulator parent) { + return parent==null ? v : parent.add(v); + } + + /** + * Flattens the given multigate into its parent, if they have the multigate is not shared + * and it has the same operator as the parent. + * @return flattened gate + * @see kodkod.engine.bool.BooleanVisitor#visit(kodkod.engine.bool.MultiGate, java.lang.Object) + */ + public BooleanValue visit(MultiGate multigate, BooleanAccumulator parent) { + final Operator.Nary op = multigate.op(); + if (flattenable.contains(multigate.label())) { // multigate's inputs are absorbed into its parent's inputs +// System.out.println("Flattenable: " + multigate); + for(Iterator inputs = multigate.iterator(); inputs.hasNext();) { + if (inputs.next().accept(this, parent)==op.shortCircuit()) + return op.shortCircuit(); + } + return parent; + } else { // construct a gate that corresponds to the multigate +// System.out.println("Unflattenable: " + multigate); + BooleanValue replacement = cache.get(multigate); + + if (replacement == null) { + final BooleanAccumulator newGate = BooleanAccumulator.treeGate(op); + for(Iterator inputs = multigate.iterator(); inputs.hasNext();) { + if (inputs.next().accept(this,newGate)==op.shortCircuit()) { + return op.shortCircuit(); + } + } + replacement = factory.accumulate(newGate); + cache.put(multigate, replacement); + } + + return addToParent(replacement, parent); + } + } + + /** + * Returns the ite gate. + * @return itegate + * @see kodkod.engine.bool.BooleanVisitor#visit(kodkod.engine.bool.ITEGate, java.lang.Object) + */ + public BooleanValue visit(ITEGate itegate, BooleanAccumulator parent) { + return addToParent(factory.ite(itegate.input(0).accept(this,null), itegate.input(1).accept(this,null), + itegate.input(2).accept(this,null)), parent); + } + + /** + * Returns the not gate. + * @return negation + * @see kodkod.engine.bool.BooleanVisitor#visit(kodkod.engine.bool.NotGate, java.lang.Object) + */ + public BooleanValue visit(NotGate negation, BooleanAccumulator parent) { + return addToParent(factory.not(negation.input(0).accept(this,null)), parent); + } + + /** + * Returns the variable. + * @see kodkod.engine.bool.BooleanVisitor#visit(kodkod.engine.bool.BooleanVariable, java.lang.Object) + */ + public BooleanValue visit(BooleanVariable variable, BooleanAccumulator parent) { + return addToParent(variable, parent); + } + + + + /** + * A visitor that determins which gates can be flattened. Specifically, when + * applied to a given root, the flattenable field of the visitor contains the + * labels of all m such that m is a MultiGate descendent of the root and + * #inputs.m = 1 && (inputs.m).op = m.op => s.contains(m.label). That is, + * flattenable = {i: int | some m: root.^inputs & MultiGate | #inputs.m = 1 && (inputs.m).op = m.op && m.label = i} + */ + private static final class FlatteningDataGatherer implements BooleanVisitor { + /* contains the labels of all the flattenable multi gates */ + final IntSet flattenable; + /* contains the labels of all the visited multi gates */ + final IntSet visited; + + /** + * Constructs a new flattenning data gatherer. The returned visitor can only be + * applied to the specified root value. + */ + private FlatteningDataGatherer(BooleanFormula root) { + final int maxLit = StrictMath.abs(root.label()); + this.flattenable = Ints.bestSet(maxLit+1); + this.visited = Ints.bestSet(maxLit+1); + } + + public Object visit(MultiGate multigate, Operator parentOp) { + final int label = multigate.label(); + if (visited.contains(label)) { // we've seen this node already + flattenable.remove(label); + } else { // haven't seen it yet + visited.add(label); + if (parentOp == multigate.op()) flattenable.add(label); + // visit children + for(Iterator inputs = multigate.iterator(); inputs.hasNext();) { + inputs.next().accept(this, multigate.op()); + } + } + + return null; + } + + public Object visit(ITEGate itegate, Operator parentOp) { + if (visited.add(itegate.label())) { // not visited + itegate.input(0).accept(this,null); + itegate.input(1).accept(this,null); + itegate.input(2).accept(this,null); + } + return null; + } + + public Object visit(NotGate negation, Operator parentOp) { + negation.input(0).accept(this,null); + return null; + } + + public Object visit(BooleanVariable variable, Operator arg) { + return null; + } + + + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/fol2sat/Environment.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/fol2sat/Environment.java new file mode 100644 index 00000000..8929b00e --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/fol2sat/Environment.java @@ -0,0 +1,190 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.engine.fol2sat; + +import kodkod.ast.Variable; + + + +/** + * Represents a variable binding environment as a + * map from a {@link kodkod.ast.Variable variable} to a + * {@link java.lang.Object value}. An environment has + * a (possibly empty) parent environment to which unsuccessful lookups + * are delegated. + * + * @specfield variable: lone Variable + * @specfield value: lone T + * @specfield type: lone E + * @specfield parent: Environment + * @invariant this = parent => no variable + * @author Emina Torlak + */ +public final class Environment { + private final Variable variable; + private final T value; + private final E type; + private final Environment parent; + private final Object envType; // may be null, but will typically contain Quantifier.ALL or Quantifier.SOME + + private boolean negated; + + /** + * Constructs the empty environment. + */ + private Environment() { + this.variable = null; + this.value = null; + this.type = null; + this.parent = this; + this.envType = null; + this.negated = false; + } + + /** + * Constructs a new environment with the specified parent + * and mapping. + * + * @ensures this.parent' = parent && this.variable' = variable && this.value' = value + */ + private Environment(Environment parent, Variable variable, E type, T value, Object quant, boolean negated) { + this.variable = variable; + this.value = value; + this.type = type; + this.parent = parent; + this.envType = quant; + this.negated = negated; + } + + /** + * Returns the empty environment. + * @return the empty environment. + */ + public static Environment empty() { + return new Environment(); + } + + /** + * Returns the parent environment of this, or null if this + * does not have a parent. + * + * @return this.parent + */ + public Environment parent() { return parent; } + + /** + * Returns a new environment that extends this environment with the specified + * mapping. + * @requires variable != null + * @return e : Environment | e.parent = this && e.variable = variable && e.value = value + */ + public Environment extend(Variable variable, E type, T value) { return extend(variable, type, value, null); } + public Environment extend(Variable variable, E type, T value, Object envType) { + return new Environment(this, variable, type, value, envType, false); + } + + public void negate() { + negated = !negated; + } + + /** + * Returns this.variable. + * @return this.variable + */ + public Variable variable() { + return this.variable; + } + + /** + * Return this.isInt. + * @return this.isInt + */ + public E type() { + return this.type; + } + + /** + * Returns this.value. + * @return this.value + */ + public T value() { + return this.value; + } + + /** + * Returns this.quant. + * @return this.quant + */ + public Object envType() { + return envType; + } + + public boolean isNegated() { + return negated; + } + + /** + * Returns true if this is the empty (root) environment; + * otherwise returns false. + * @return this.parent = this + */ + public boolean isEmpty() { + return this==this.parent; + } + + /** + * Looks up the given variable in this environment and its + * ancestors. If the variable is not bound in this + * environment or any of its ancestors, null is returned. + * If the variable is bound in multiple environments, + * the first found binding is returned. Note that null + * will also be returned if the variable is bound to null. + * @return variable = this.variable => this.value, this.parent.lookup(variable) + */ + public T lookup(Variable variable) { + Environment p = this; + // ok to use == for testing variable equality: + // see kodkod.ast.LeafExpression#equals + while(!p.isEmpty() && p.variable!=variable) { + p = p.parent; + } + return p.value; + } + + public E lookupType(Variable variable) { + Environment p = this; + // ok to use == for testing variable equality: + // see kodkod.ast.LeafExpression#equals + while(!p.isEmpty() && p.variable!=variable) { + p = p.parent; + } + return p.type; + } + + /** + * @see java.lang.Object#toString() + */ + public String toString() { + return (parent.isEmpty() ? "[]" : parent.toString()) + "["+variable+"="+value+"]"; + } + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/fol2sat/FOL2BoolCache.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/fol2sat/FOL2BoolCache.java new file mode 100644 index 00000000..5f216a3f --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/fol2sat/FOL2BoolCache.java @@ -0,0 +1,272 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.engine.fol2sat; + +import java.util.IdentityHashMap; +import java.util.Map; +import java.util.Set; + +import kodkod.ast.Expression; +import kodkod.ast.Node; +import kodkod.ast.Variable; +import kodkod.engine.bool.BooleanConstant; +import kodkod.engine.bool.BooleanMatrix; +import kodkod.util.nodes.AnnotatedNode; + + +/** + * Manages the caching policy for a translation from FOL to boolean. + * In particular it determines which translations + * to cache, when to throw them out of the cache, etc. + * + * @specfield node: Node // node being translated + * @specfield cached: node.*children // the nodes whose translations are cached + * @specfield cache: cached -> (Object ->lone Environment) + * @author Emina Torlak + */ +final class FOL2BoolCache { + private final Map cache; + + /** + * Constructs a new translation cache for the given annotated node. + * @ensures this.node' = annotated.node + */ + FOL2BoolCache(AnnotatedNode annotated) { + final CacheCollector collector = new CacheCollector(annotated.sharedNodes()); + annotated.node().accept(collector); + + this.cache = new IdentityHashMap(collector.cache().size()); + for(Map.Entry> e : collector.cache().entrySet()) { + Set freeVars = e.getValue(); + if (freeVars.isEmpty()) + this.cache.put(e.getKey(), new NoVarRecord()); + else + this.cache.put(e.getKey(), new MultiVarRecord(freeVars)); + } + } + + + /** + * If the translation of the given node, with its free variables + * bound as they are in the given environment, has been cached, + * the cached value is returned. Otherwise, null is returned. + * @return this.cache[node][Object] in env.map => + * this.cache[node].map, null + */ + @SuppressWarnings("unchecked") + T lookup(Node node, Environment env) { + final Record info = cache.get(node); + return info==null ? null : (T) info.get(env); + } + + /** + * Caches the given translation for the specified node, if the given node is + * in this.cached. Otherwise does nothing. + * The method returns the specified translation. + * @ensures node in this.cached => + * this.cache' = this.cache ++ node->translation->env, + * this.cache' = this.cache + * @return translation + */ + final T cache(Node node, T translation, Environment env) { + final Record info = cache.get(node); + if (info != null) { + info.set(translation, env); + } + return translation; + } + + /** + * Collects the free variables of the nodes in a given AST whose + * translations should be cached. + * @specfield root: Node + * @specfield cached: root.*children -> Set + * @invariant all c: root.*children | some cached[c] => cached[c] = freeVariables(c) + * @author Emina Torlak + */ + private static final class CacheCollector extends FreeVariableCollector { + + /** + * Constructs a new cache collector. + */ + protected CacheCollector(Set cached) { + super(cached); + } + + /** + * Returns this.cache. This method should be called *after* the + * visitor has been applied to this.root. + * @return this.cache + */ + final Map> cache() { + return cache; + } + + /** + * We record the set of free variables for the given node if the node is shared, + * or if it has free variables, none of which is the most recently declared variable. + * @ensures node in sharedNodes || + * ((node.^(~children) in (QuantifiedFormula + Comprehension)) && + * (some varsInScope.top() => !freeVars.contains(varsInScope.top()))) => + * this.cache' = this.cache ++ node->varsInScope, + * this.cache' = this.cache + * @return freeVars + */ + @Override + protected final Set cache(Node node, Set freeVars) { + if (cached.contains(node) || !varsInScope.empty() && !freeVars.contains(varsInScope.peek())) { + cache.put(node, reduce(freeVars)); + } + return freeVars; + } + + } + + /** + * A container class that stores the translation of a shared node + * (BooleanValue for formulas and BooleanMatrix for expressions) + * and bindings for the node's free variables which were used to + * generate the translation. + * Storing the bindings is necessary for proper handling of + * sharing within quantified formulas and comprehensions. + * This implementation assumes that each free variable is + * mapped to a BooleanMatrix of density one, whose sole entry + * is the BooleanConstant TRUE. + * @specfield varBinding: Variable -> lone int + * @specfield translation: lone Object + */ + private static abstract class Record { + Object translation; + /** + * Returns this.translation if the given environment + * has the same mappings for the free variables of + * the translated node as the ones used to generate + * this.translation. Otherwise returns null. + * @requires all v: varBinding.int | some e.lookup(v) + * @return all v: varBinding.int | e.lookup(v).get(varBinding[v])=TRUE => this.translation, null + * @throws NullPointerException - e = null + */ + abstract Object get(Environment e); + + /** + * Sets this.translation to the given translation + * and sets the free variable bindings to those + * given by the specified environment. + * @requires all v: varBinding.int | some env.lookup(v) + * @ensures this.translation' = translation && + * this.varBinding' = + * {v: this.varBinding.int, tupleIndex: int | + * tupleIndex = env.lookup(v).iterator().next().index() } + */ + abstract void set(Object transl, Environment env); + } + + /** + * A TranslationInfo for a node with one or more free variables. + */ + private static final class MultiVarRecord extends Record { + final Variable[] vars; + final int[] tuples; + + /** + * Constructs a translation unit for a node which + * has the given set of free variables. + * @ensures this.freeVariables' = vars && + * no this.translation' + */ + MultiVarRecord(Set freeVariables) { + this.vars = freeVariables.toArray(new Variable[freeVariables.size()]); + this.tuples = new int[freeVariables.size()]; + } + + /** + * @see kodkod.engine.fol2sat.FOL2BoolCache.Record#get(kodkod.engine.fol2sat.Environment) + */ + Object get(Environment e) { + if (translation==null) return null; + for(int i = 0; i < vars.length; i++) { + if (e.lookup(vars[i]).get(tuples[i])!=BooleanConstant.TRUE) + return null; + } + return translation; + } + + /** + * @see kodkod.engine.fol2sat.FOL2BoolCache.Record#set(java.lang.Object, kodkod.engine.fol2sat.Environment) + */ + void set(Object transl, Environment env) { + translation = transl; + for(int i = 0; i < vars.length; i++) { + final BooleanMatrix varVal = env.lookup(vars[i]); + tuples[i] = varVal.iterator().next().index(); + if (transl==varVal) { + translation = varVal.clone(); + } + } + } + + /** + * @see java.lang.Object#toString() + */ + public String toString() { + final StringBuilder b = new StringBuilder("{"); + b.append(String.valueOf(translation)); + for(int i = 0; i < vars.length; i++) + { + b.append(" ("); + b.append(vars[i]); + b.append(", "); + b.append(tuples[i]); + b.append(")"); + } + b.append("}"); + return b.toString(); + } + } + + /** + * A TranslationInfo for a node with no free variables. + */ + private static final class NoVarRecord extends Record { + + /** + * @see kodkod.engine.fol2sat.FOL2BoolCache.Record#get(kodkod.engine.fol2sat.Environment) + */ + Object get(Environment e) { + return translation; + } + + /** + * @see kodkod.engine.fol2sat.FOL2BoolCache.Record#set(java.lang.Object, kodkod.engine.fol2sat.Environment) + */ + void set(Object transl, Environment env) { + translation = transl; + } + + /** + * @see java.lang.Object#toString() + */ + public String toString() { + return "{" + translation+ "}"; + } + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/fol2sat/FOL2BoolTranslator.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/fol2sat/FOL2BoolTranslator.java new file mode 100644 index 00000000..7d269168 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/fol2sat/FOL2BoolTranslator.java @@ -0,0 +1,1070 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.engine.fol2sat; + +import java.util.ArrayList; +import java.util.List; + +import kodkod.ast.BinaryExpression; +import kodkod.ast.BinaryFormula; +import kodkod.ast.BinaryIntExpression; +import kodkod.ast.ComparisonFormula; +import kodkod.ast.Comprehension; +import kodkod.ast.ConstantExpression; +import kodkod.ast.ConstantFormula; +import kodkod.ast.Decl; +import kodkod.ast.Decls; +import kodkod.ast.ExprToIntCast; +import kodkod.ast.Expression; +import kodkod.ast.Formula; +import kodkod.ast.IfExpression; +import kodkod.ast.IfIntExpression; +import kodkod.ast.IntComparisonFormula; +import kodkod.ast.IntConstant; +import kodkod.ast.IntExpression; +import kodkod.ast.IntToExprCast; +import kodkod.ast.MultiplicityFormula; +import kodkod.ast.NaryExpression; +import kodkod.ast.NaryFormula; +import kodkod.ast.NaryIntExpression; +import kodkod.ast.Node; +import kodkod.ast.NotFormula; +import kodkod.ast.ProjectExpression; +import kodkod.ast.QuantifiedFormula; +import kodkod.ast.Relation; +import kodkod.ast.RelationPredicate; +import kodkod.ast.SumExpression; +import kodkod.ast.UnaryExpression; +import kodkod.ast.UnaryIntExpression; +import kodkod.ast.Variable; +import kodkod.ast.operator.ExprCompOperator; +import kodkod.ast.operator.ExprOperator; +import kodkod.ast.operator.FormulaOperator; +import kodkod.ast.operator.Multiplicity; +import kodkod.ast.operator.Quantifier; +import kodkod.ast.visitor.ReturnVisitor; +import kodkod.engine.bool.BooleanAccumulator; +import kodkod.engine.bool.BooleanConstant; +import kodkod.engine.bool.BooleanFactory; +import kodkod.engine.bool.BooleanMatrix; +import kodkod.engine.bool.BooleanValue; +import kodkod.engine.bool.Dimensions; +import kodkod.engine.bool.Int; +import kodkod.engine.bool.Operator; +import kodkod.util.ints.IndexedEntry; +import kodkod.util.ints.IntIterator; +import kodkod.util.ints.IntSet; +import kodkod.util.nodes.AnnotatedNode; +import kodkod.util.nodes.Nodes; + +/** + * Translates an annotated node to boolean representation. + * @specfield node: AnnotatedNode // node to translate + * @specfield interpreter: LeafInterpreter // the interpreter used for translation + * @specfield env: Environment // current environment + * @author Emina Torlak + */ +abstract class FOL2BoolTranslator implements ReturnVisitor { + + /** + * Translates the given annotated formula or expression into a boolean + * formula or matrix, using the provided interpreter. + * @requires interpreter.relations = AnnotatedNode.relations(annotated) + * @return {transl: T | + * annotated.node in Formula => transl in BooleanValue, + * annotated.node in Expression => transl in BooleanMatrix, + * annotated.node in IntExpression => transl in Int} + * @throws HigherOrderDeclException - annotated.node contains a higher order declaration + * @throws UnboundLeafException - annotated.node refers to an undeclared variable + **/ + @SuppressWarnings("unchecked") + static final T translate(AnnotatedNode annotated, LeafInterpreter interpreter) { + final FOL2BoolCache cache = new FOL2BoolCache(annotated); + final FOL2BoolTranslator translator = new FOL2BoolTranslator(cache, interpreter) {}; + return (T) annotated.node().accept(translator); + } + + /** + * Translates the given annotated formula into a boolean + * accumulator with respect to the given interpreter and logs the translation events to the given logger. + * @requires interpreter.relations = AnnotatedNode.relations(annotated) + * @requires annotated.source[annotated.sourceSensitiveRoots()] = Nodes.roots(annotated.source[annotated.node]) + * @return BooleanAccumulator that is the meaning of the given annotated formula with respect to the given interpreter + * @ensures log.records' contains the translation events that occurred while generating the returned value + * @throws HigherOrderDeclException - annotated.node contains a higher order declaration + * @throws UnboundLeafException - annotated.node refers to an undeclared variable + **/ + static final BooleanAccumulator translate(final AnnotatedNode annotated, LeafInterpreter interpreter, final TranslationLogger logger) { + final FOL2BoolCache cache = new FOL2BoolCache(annotated); + final FOL2BoolTranslator translator = new FOL2BoolTranslator(cache, interpreter) { + BooleanValue cache(Formula formula, BooleanValue translation) { + logger.log(formula, translation, super.env); + return super.cache(formula, translation); + } + }; + final BooleanAccumulator acc = BooleanAccumulator.treeGate(Operator.AND); + + for(Formula root : Nodes.conjuncts(annotated.node())) { + acc.add(root.accept(translator)); + } + logger.close(); + return acc; + } + + /** + * Translates the given annotated expression into a boolean + * matrix that is a least sound upper bound on the expression's + * value, given the leaf and variable bindings in the + * the provided interpreter and environment. + * @requires interpreter.relations = AnnotatedNode.relations(annotated) + * @return a boolean matrix that is a least sound upper bound on the expression's value + * @throws HigherOrderDeclException - annotated.node contains a higher order declaration + * @throws UnboundLeafException - annotated.node refers to a variable that neither declared nor bound in env + **/ + static final BooleanMatrix approximate(AnnotatedNode annotated, LeafInterpreter interpreter, Environment env) { + final FOL2BoolTranslator approximator = new FOL2BoolTranslator(new FOL2BoolCache(annotated), interpreter, env) { + public final BooleanMatrix visit(BinaryExpression binExpr) { + final BooleanMatrix ret = lookup(binExpr); + if (ret!=null) return ret; + switch(binExpr.op()){ + case DIFFERENCE : return cache(binExpr, binExpr.left().accept(this)); + case OVERRIDE : return cache(binExpr, binExpr.left().accept(this).or(binExpr.right().accept(this))); + default : return super.visit(binExpr); + } + } + public final BooleanMatrix visit(Comprehension cexpr) { + final BooleanMatrix ret = lookup(cexpr); + return ret!=null ? ret : cache(cexpr, super.visit((Comprehension)Formula.TRUE.comprehension(cexpr.decls()))); + } + public BooleanMatrix visit(IfExpression ifExpr) { + final BooleanMatrix ret = lookup(ifExpr); + return ret!=null ? ret : cache(ifExpr, ifExpr.thenExpr().accept(this).or(ifExpr.elseExpr().accept(this))); + } + public BooleanMatrix visit(IntToExprCast castExpr) { + BooleanMatrix ret = lookup(castExpr); + if (ret!=null) return ret; + switch(castExpr.op()) { + case INTCAST : + ret = Expression.INTS.accept(this); + break; + case BITSETCAST : + final BooleanFactory factory = super.interpreter.factory(); + ret = factory.matrix(Dimensions.square(super.interpreter.universe().size(), 1)); + final IntSet ints = super.interpreter.ints(); + final int msb = factory.bitwidth()-1; + // handle all bits but the sign bit + for(int i = 0; i < msb; i++) { + int pow2 = 1< env; + + private final FOL2BoolCache cache; + + /* Holds variables discovered while visiting an expression to be cast to Int. + * (because, for the new "overflow" semantics of quantifiers, we want to know the + * variables that contribute to every Int */ + //[AM] + private NestedSet vars = NestedSet.empty(); + + /** + * Constructs a new translator that will use the given translation cache + * and interpreter to perform the translation. + * @ensures this.node' = manager.node + */ + private FOL2BoolTranslator(FOL2BoolCache cache, LeafInterpreter interpreter) { + this.cache = cache; + this.interpreter = interpreter; + this.env = Environment.empty(); + } + + /** + * Constructs a new translator that will use the given translation cache, + * interpreter and environment to perform the translation. + * @ensures this.node' = manager.node + */ + private FOL2BoolTranslator(FOL2BoolCache cache, LeafInterpreter interpreter, Environment env) { + this.interpreter = interpreter; + this.env = env; + this.cache = cache; + } + + /** + * Retrieves the cached translation for the given node, if any. + * Otherwise returns null. + * @return the cached translation for the given node, if any. + * Otherwise returns null. + */ + @SuppressWarnings("unchecked") + final T lookup(Node node) { + return (T) cache.lookup(node, env); + } + + /** + * The translation is cached, if necessary, and returned. + * @return translation + * @ensures the translation may be cached + */ + final T cache(Node node, T translation) { + return cache.cache(node, translation, env); + } + + /** + * The translation is cached, if necessary, and returned. + * @return translation + * @ensures the translation may be cached + */ + BooleanValue cache(Formula formula, BooleanValue translation) { + return cache.cache(formula, translation, env); + } + + /** + * Calls lookup(decls) and returns the cached value, if any. + * If a translation has not been cached, translates decls into a list + * of translations of its children, + * calls cache(...) on it and returns it. + * @return let t = lookup(decls) | + * some t => t, cache(decl, decls.declarations.expression.accept(this)) + */ + public final List visit(Decls decls) { + List ret = lookup(decls); + if (ret!=null) return ret; + ret = new ArrayList(decls.size()); + for(Decl decl : decls) { + ret.add(visit(decl)); + } + return cache(decls, ret); + } + + /** + * Calls lookup(decl) and returns the cached value, if any. + * If a translation has not been cached, translates decl.expression, + * calls cache(...) on it and returns it. + * @return let t = lookup(decl) | + * some t => t, cache(decl, decl.expression.accept(this)) + */ + public final BooleanMatrix visit(Decl decl) { + BooleanMatrix matrix = lookup(decl); + if (matrix!=null) return matrix; + if (decl.multiplicity()!=Multiplicity.ONE) + throw new HigherOrderDeclException(decl); + return cache(decl, decl.expression().accept(this)); + } + + /** + * Calls this.env.lookup(variable) and returns the current binding for the + * given variable and adds it to the current level of nested variables (cars). + * If no binding is found, an UnboundLeafException is thrown. + * @return this.env.lookup(variable) + * @throws UnboundLeafException - no this.env.lookup(variable) + */ + public final BooleanMatrix visit(Variable variable) { + final BooleanMatrix ret = env.lookup(variable); + if (ret == null) + throw new UnboundLeafException("Unbound variable", variable); + vars.add(variable); + return ret; + } + + /** + * Returns this.interpreter.interpret(relation). + * @return this.interpreter.interpret(relation) + */ + public final BooleanMatrix visit(Relation relation) { + return interpreter.interpret(relation); + } + + /** + * Returns this.interpreter.interpret(constExpr). + * @return this.interpreter.interpret(constExpr). + */ + public final BooleanMatrix visit(ConstantExpression constExpr) { + return interpreter.interpret(constExpr); + } + + /** + * Calls lookup(binExpr) and returns the cached value, if any. + * If a translation has not been cached, translates the expression, + * calls cache(...) on it and returns it. + * @return let t = lookup(binExpr) | some t => t, + * let op = (binExpr.op).(UNION->or + INTERSECTION->and + DIFFERENCE->difference + OVERRIDE->override + JOIN->dot + PRODUCT->cross) | + * cache(binExpr, op(binExpr.left.accept(this), binExpr.right.accept(this))) + */ + public BooleanMatrix visit(BinaryExpression binExpr) { + BooleanMatrix ret = lookup(binExpr); + if (ret!=null) return ret; + + final BooleanMatrix left = binExpr.left().accept(this); + final BooleanMatrix right = binExpr.right().accept(this); + final ExprOperator op = binExpr.op(); + + switch(op) { + case UNION : ret = left.or(right); break; + case INTERSECTION : ret = left.and(right); break; + case DIFFERENCE : ret = left.difference(right); break; + case OVERRIDE : ret = left.override(right); break; + case JOIN : ret = left.dot(right); break; + case PRODUCT : ret = left.cross(right); break; + default : + throw new IllegalArgumentException("Unknown operator: " + op); + } + + return cache(binExpr, ret); + } + + /** + * Calls lookup(expr) and returns the cached value, if any. + * If a translation has not been cached, translates the expression, + * calls cache(...) on it and returns it. + * @return let t = lookup(expr) | some t => t, + * let op = (expr.op).(UNION->or + INTERSECTION->and + DIFFERENCE->difference + OVERRIDE->override + JOIN->dot + PRODUCT->cross) | + * cache(expr, op(expr.left.accept(this), expr.right.accept(this))) + */ + public BooleanMatrix visit(NaryExpression expr) { + BooleanMatrix ret = lookup(expr); + if (ret!=null) return ret; + + final ExprOperator op = expr.op(); + final BooleanMatrix first = expr.child(0).accept(this); + final BooleanMatrix[] rest = new BooleanMatrix[expr.size()-1]; + for(int i = 0; i < rest.length; i++) { rest[i] = expr.child(i+1).accept(this); } + + switch(op) { + case UNION : ret = first.or(rest); break; + case INTERSECTION : ret = first.and(rest); break; + case OVERRIDE : ret = first.override(rest); break; + case PRODUCT : ret = first.cross(rest); break; + default : + throw new IllegalArgumentException("Unknown associative operator: " + op); + } + + return cache(expr, ret); + } + + /** + * Calls lookup(unaryExpr) and returns the cached value, if any. + * If a translation has not been cached, translates the expression, + * calls cache(...) on it and returns it. + * @return let t = lookup(unaryExpr) | some t => t, + * let op = (unaryExpr.op).(TRANSPOSE->transpose + CLOSURE->closure + REFLEXIVE_CLOSURE->(lambda(m)(m.closure().or(iden))) | + * cache(unaryExpr, op(unaryExpr.child)) + */ + public final BooleanMatrix visit(UnaryExpression unaryExpr) { + BooleanMatrix ret = lookup(unaryExpr); + if (ret!=null) return ret; + + final BooleanMatrix child = unaryExpr.expression().accept(this); + final ExprOperator op = unaryExpr.op(); + + switch(op) { + case TRANSPOSE : ret = child.transpose(); break; + case CLOSURE : ret = child.closure(); break; + case REFLEXIVE_CLOSURE : ret = child.closure().or(visit((ConstantExpression)Expression.IDEN)); break; + default : + throw new IllegalArgumentException("Unknown operator: " + op); + } + return cache(unaryExpr,ret); + } + + /** + * Translates the given comprehension as follows + * (where A_0...A_|A| stand for boolean variables that represent the + * tuples of the expression A, etc.): + * let comprehension = "{ a: A, b: B, ..., x: X | F(a, b, ..., x) }" | + * { a: A, b: B, ..., x: X | a in A && b in B && ... && x in X && F(a, b, ..., x) }. + * @param decls the declarations comprehension + * @param param formula the body of the comprehension + * @param currentDecl currently processed declaration; should be 0 initially + * @param declConstraints the constraints implied by the declarations; should be Boolean.TRUE intially + * @param partialIndex partial index into the provided matrix; should be 0 initially + * @param matrix boolean matrix that will retain the final results; should be an empty matrix of dimensions universe.size^decls.length initially + * @ensures the given matrix contains the translation of the comprehension "{ decls | formula }" + */ + private final void comprehension(Decls decls, Formula formula, int currentDecl, + BooleanValue declConstraints, int partialIndex, BooleanMatrix matrix) { + final BooleanFactory factory = interpreter.factory(); + + if (currentDecl==decls.size()) { + //TODO: what about this and overflow??? + matrix.set(partialIndex, factory.and(declConstraints, formula.accept(this))); + return; + } + + final Decl decl = decls.get(currentDecl); + final BooleanMatrix declTransl = visit(decl); + final int position = (int)StrictMath.pow(interpreter.universe().size(), decls.size()-currentDecl-1); + final BooleanMatrix groundValue = factory.matrix(declTransl.dimensions()); + env = env.extend(decl.variable(), decl.expression(), groundValue); + for(IndexedEntry entry : declTransl) { + groundValue.set(entry.index(), BooleanConstant.TRUE); + comprehension(decls, formula, currentDecl+1, factory.and(entry.value(), declConstraints), + partialIndex + entry.index()*position, matrix); + groundValue.set(entry.index(), BooleanConstant.FALSE); + } + env = env.parent(); + } + + /** + * Calls lookup(cexpr) and returns the cached value, if any. + * If a translation has not been cached, translates the expression, + * calls cache(...) on it and returns it. + * @return let t = lookup(cexpr) | some t => t, + * cache(cexpr, translate(cexpr)) + */ + public BooleanMatrix visit(Comprehension cexpr) { + BooleanMatrix ret = lookup(cexpr); + if (ret!=null) return ret; + + ret = interpreter.factory().matrix(Dimensions.square(interpreter.universe().size(), cexpr.decls().size())); + comprehension(cexpr.decls(), cexpr.formula(), 0, BooleanConstant.TRUE, 0, ret); + + return cache(cexpr,ret); + } + + /** + * Calls lookup(ifExpr) and returns the cached value, if any. + * If a translation has not been cached, translates the expression, + * calls cache(...) on it and returns it. + * @return let t = lookup(ifExpr) | some t => t, + * cache(ifExpr, ifExpr.condition.accept(this).choice(ifExpr.then.accept(this), ifExpr.else.accept(this))) + */ + public BooleanMatrix visit(IfExpression ifExpr) { + BooleanMatrix ret = lookup(ifExpr); + if (ret!=null) return ret; + + final BooleanValue condition = ifExpr.condition().accept(this); + final BooleanMatrix thenExpr = ifExpr.thenExpr().accept(this); + final BooleanMatrix elseExpr = ifExpr.elseExpr().accept(this); + ret = thenExpr.choice(condition, elseExpr); + + return cache(ifExpr,ret); + } + + /** + * Calls lookup(project) and returns the cached value, if any. + * If a translation has not been cached, translates the expression, + * calls cache(...) on it and returns it. + * @return let t = lookup(project) | some t => t, + * cache(project, project.expression.accept(this).project(translate(project.columns)) + */ + public final BooleanMatrix visit(ProjectExpression project) { + BooleanMatrix ret = lookup(project); + if (ret!=null) return ret; + + final Int[] cols = new Int[project.arity()]; + for(int i = 0, arity = project.arity(); i < arity; i++) { + cols[i] = project.column(i).accept(this); + } + + return cache(project, project.expression().accept(this).project(cols)); + } + + + /** + * @return constant = ConstantFormula.TRUE => BooleanConstant.TRUE, BooleanConstant.FALSE + */ + public final BooleanValue visit(ConstantFormula constant) { + return cache(constant, BooleanConstant.constant(constant.booleanValue())); + } + + /** + * Translates the given universally quantified formula as follows + * (where A_0...A_|A| stand for boolean variables that represent the + * tuples of the expression A, etc.): + * + * let quantFormula = "all a: A, b: B, ..., x: X | F(a, b, ..., x)" | + * (A_0 && B_0 && ... && X_0 => translate(F(A_0, B_0, ..., X_0))) && ... && + * (A_|A| && B_|B| && ... && X_|X| => translate(F(A_|A|, B_|B|, ..., X_|X|))) + * + * If the noOverflow option is specified, then the translation looks like: + * + * let quantFormula = "all a: A, b: B, ..., x: X | F(a, b, ..., x)" | + * (A_0 && B_0 && ... && X_0 => (!of(F(A_0, B_0, ..., X_0)) => translate(F(A_0, B_0, ..., X_0)))) && ... && + * (A_|A| && B_|B| && ... && X_|X| => (!of(F(A_|A|, B_|B|, ..., X_|X|)) => translate(F(A_|A|, B_|B|, ..., X_|X|)))) + * + * where of(F(A_|a|, B_|b|, ..., X_|x|)) is the portion of the overflow circuit generated by the translation of + * F(A_|a|, B_|b|, ..., X_|x|) contributed by arithmetic operations over only the integer variables of this quantifier + * + * @param decls formula declarations + * @param formula the formula body + * @param currentDecl currently processed declaration; should be 0 initially + * @param declConstraints the constraints implied by the declarations; should be Boolean.FALSE initially + * @param acc the accumulator that contains the top level conjunction; should be an empty AND accumulator initially + * @ensures the given accumulator contains the translation of the formula "all decls | formula" + */ + private void all(Decls decls, Formula formula, int currentDecl, BooleanValue declConstraints, BooleanAccumulator acc) { + if (acc.isShortCircuited()) return; + final BooleanFactory factory = interpreter.factory(); + + if (decls.size()==currentDecl) { + BooleanValue formulaCircuit = formula.accept(this); + BooleanValue finalCircuit = factory.or(declConstraints, formulaCircuit); + acc.add(finalCircuit); + return; + } + + final Decl decl = decls.get(currentDecl); + final BooleanMatrix declTransl = visit(decl); + final BooleanMatrix groundValue = factory.matrix(declTransl.dimensions()); + env = env.extend(decl.variable(), decl.expression(), groundValue, Quantifier.ALL); + for(IndexedEntry entry : declTransl) { + groundValue.set(entry.index(), BooleanConstant.TRUE); + all(decls, formula, currentDecl+1, factory.or(factory.not(entry.value()), declConstraints), acc); + groundValue.set(entry.index(), BooleanConstant.FALSE); + } + env = env.parent(); + } + + /** + * Translates the given existentially quantified formula as follows + * (where A_0...A_|A| stand for boolean variables that represent the + * tuples of the expression A, etc.): + * + * let quantFormula = "some a: A, b: B, ..., x: X | F(a, b, ..., x)" | + * (A_0 && B_0 && ... && X_0 && translate(F(A_0, B_0, ..., X_0))) || ... || + * (A_|A| && B_|B| && ... && X_|X| && translate(F(A_|A|, B_|B|, ..., X_|X|)) + * + * If the noOverflow option is specified, then the translation looks like: + * + * let quantFormula = "some a: A, b: B, ..., x: X | F(a, b, ..., x)" | + * (A_0 && B_0 && ... && X_0 && !of(F(A_0, B_0, ..., X_0)) && translate(F(A_0, B_0, ..., X_0))) || ... || + * (A_|A| && B_|B| && ... && X_|X| && !of(F(A_|A|, B_|B|, ..., X_|X|)) && translate(F(A_|A|, B_|B|, ..., X_|X|)) + * + * where of(F(A_|a|, B_|b|, ..., X_|x|)) is the portion of the overflow circuit generated by the translation of + * F(A_|a|, B_|b|, ..., X_|x|) contributed by arithmetic operations over only the integer variables of this quantifier + * + * @param decls formula declarations + * @param formula the formula body + * @param currentDecl currently processed declaration; should be 0 initially + * @param declConstraints the constraints implied by the declarations; should be Boolean.TRUE intially + * @param acc the accumulator that contains the top level conjunction; should be an empty OR accumulator initially + * @ensures the given accumulator contains the translation of the formula "some decls | formula" + */ + private void some(Decls decls, Formula formula, int currentDecl, BooleanValue declConstraints, BooleanAccumulator acc) { + if (acc.isShortCircuited()) return; + final BooleanFactory factory = interpreter.factory(); + + if (decls.size()==currentDecl) { + BooleanValue formulaCircuit = formula.accept(this); + BooleanValue finalCircuit = factory.and(declConstraints, formulaCircuit); + acc.add(finalCircuit); + return; + } + + final Decl decl = decls.get(currentDecl); + final BooleanMatrix declTransl = visit(decl); + final BooleanMatrix groundValue = factory.matrix(declTransl.dimensions()); + env = env.extend(decl.variable(), decl.expression(), groundValue, Quantifier.SOME); + for(IndexedEntry entry : declTransl) { + groundValue.set(entry.index(), BooleanConstant.TRUE); + some(decls, formula, currentDecl+1, factory.and(entry.value(), declConstraints), acc); + groundValue.set(entry.index(), BooleanConstant.FALSE); + } + env = env.parent(); + } + + /** + * Calls lookup(quantFormula) and returns the cached value, if any. + * If a translation has not been cached, translates the formula, + * calls cache(...) on it and returns it. + * @return let t = lookup(quantFormula) | some t => t, + * cache(quantFormula, translate(quantFormula)) + */ + public final BooleanValue visit(QuantifiedFormula quantFormula) { + BooleanValue ret = lookup(quantFormula); + if (ret!=null) return ret; + + final Quantifier quantifier = quantFormula.quantifier(); + switch(quantifier) { + case ALL : + final BooleanAccumulator and = BooleanAccumulator.treeGate(Operator.AND); + all(quantFormula.decls(), quantFormula.formula(), 0, BooleanConstant.FALSE, and); + ret = interpreter.factory().accumulate(and); + break; + case SOME : + final BooleanAccumulator or = BooleanAccumulator.treeGate(Operator.OR); + some(quantFormula.decls(), quantFormula.formula(), 0, BooleanConstant.TRUE, or); + ret = interpreter.factory().accumulate(or); + break; + default : + throw new IllegalArgumentException("Unknown quantifier: " + quantifier); + } + return cache(quantFormula,ret); + } + + /** + * Calls lookup(formula) and returns the cached value, if any. + * If a translation has not been cached, translates the formula, + * calls cache(...) on it and returns it. + * @return let t = lookup(formula) | some t => t, + * cache(formula, formula.op(formula.left.accept(this), formula.right.accept(this)) + */ + public final BooleanValue visit(NaryFormula formula) { + final BooleanValue ret = lookup(formula); + if (ret!=null) return ret; + + final FormulaOperator op = formula.op(); + final Operator.Nary boolOp; + + switch(op) { + case AND : boolOp = Operator.AND; break; + case OR : boolOp = Operator.OR; break; + default : throw new IllegalArgumentException("Unknown nary operator: " + op); + } + + final BooleanAccumulator acc = BooleanAccumulator.treeGate(boolOp); + final BooleanValue shortCircuit = boolOp.shortCircuit(); + for(Formula child : formula) { + if (acc.add(child.accept(this))==shortCircuit) + break; + } + + return cache(formula, interpreter.factory().accumulate(acc)); + } + + + + /** + * Calls lookup(binFormula) and returns the cached value, if any. + * If a translation has not been cached, translates the formula, + * calls cache(...) on it and returns it. + * @return let t = lookup(binFormula) | some t => t, + * cache(binFormula, binFormula.op(binFormula.left.accept(this), binFormula.right.accept(this)) + */ + public final BooleanValue visit(BinaryFormula binFormula) { + BooleanValue ret = lookup(binFormula); + if (ret!=null) return ret; + + final BooleanValue left = binFormula.left().accept(this); + final BooleanValue right = binFormula.right().accept(this); + final FormulaOperator op = binFormula.op(); + final BooleanFactory f = interpreter.factory(); + + switch(op) { + case AND : ret = f.and(left, right); break; + case OR : ret = f.or(left, right); break; + case IMPLIES : ret = f.implies(left, right); break; + case IFF : ret = f.iff(left, right); break; + default : + throw new IllegalArgumentException("Unknown operator: " + op); + } + return cache(binFormula, ret); + } + + /** + * Calls lookup(not) and returns the cached value, if any. + * If a translation has not been cached, translates the formula, + * calls cache(...) on it and returns it. + * @return let t = lookup(not) | some t => t, + * cache(not, !not.formula.accept(this)) + */ + public final BooleanValue visit(NotFormula not) { + BooleanValue ret = lookup(not); + if (ret != null) + return ret; + env.negate(); + ret = cache(not, interpreter.factory().not(not.formula().accept(this))); + env.negate(); + return ret; + } + + /** + * Calls lookup(compFormula) and returns the cached value, if any. + * If a translation has not been cached, translates the formula, + * calls cache(...) on it and returns it. + * @return let t = lookup(compFormula) | some t => t, + * let op = (binExpr.op).(SUBSET->subset + EQUALS->eq) | + * cache(compFormula, op(compFormula.left.accept(this), compFormula.right.accept(this))) + */ + public final BooleanValue visit(ComparisonFormula compFormula) { + BooleanValue ret = lookup(compFormula); + if (ret!=null) return ret; + + final BooleanMatrix left = compFormula.left().accept(this); + final BooleanMatrix right = compFormula.right().accept(this); + final ExprCompOperator op = compFormula.op(); + + switch(op) { + case SUBSET : ret = left.subset(right, env); break; + case EQUALS : ret = left.eq(right, env); break; + default : + throw new IllegalArgumentException("Unknown operator: " + compFormula.op()); + } + return cache(compFormula,ret); + } + + /** + * Calls lookup(multFormula) and returns the cached value, if any. + * If a translation has not been cached, translates the formula, + * calls cache(...) on it and returns it. + * @return let t = lookup(multFormula) | some t => t, + * let op = (multFormula.mult).(NO->none + SOME->some + ONE->one + LONE->lone) | + * cache(multFormula, op(multFormula.expression.accept(this))) + */ + public final BooleanValue visit(MultiplicityFormula multFormula) { + BooleanValue ret = lookup(multFormula); + if (ret!=null) return ret; + + final BooleanMatrix child = multFormula.expression().accept(this); + final Multiplicity mult = multFormula.multiplicity(); + + switch(mult) { + case NO : ret = child.none(env); break; + case SOME : ret = child.some(env); break; + case ONE : ret = child.one(env); break; + case LONE : ret = child.lone(env); break; + default : + throw new IllegalArgumentException("Unknown multiplicity: " + mult); + } + + return cache(multFormula, ret); + } + + /** + * Calls lookup(pred) and returns the cached value, if any. + * If a translation has not been cached, translates the expression, + * calls cache(...) on it and returns it. + * @return let t = lookup(pred) | some t => t, + * cache(pred, pred.toConstraints().accept(this)) + */ + public final BooleanValue visit(RelationPredicate pred) { + BooleanValue ret = lookup(pred); + return ret != null ? ret : cache(pred, pred.toConstraints().accept(this)); + } + + /** + * Calls lookup(castExpr) and returns the cached value, if any. + * If a translation has not been cached, translates the expression, + * calls cache(...) on it and returns it. + * @return let t = lookup(castExpr) | some t => t, + * cache(castExpr, translate(castExpr)) + */ + public BooleanMatrix visit(IntToExprCast castExpr) { + BooleanMatrix ret = lookup(castExpr); + if (ret!=null) return ret; + + final Int child = castExpr.intExpr().accept(this); + final BooleanFactory factory = interpreter.factory(); + final IntSet ints = interpreter.ints(); + + ret = factory.matrix(Dimensions.square(interpreter.universe().size(), 1)); + + switch(castExpr.op()) { + case INTCAST : + for(IntIterator iter = ints.iterator(); iter.hasNext(); ) { + int i = iter.next(); + int atomIndex = interpreter.interpret(i); + ret.set(atomIndex, factory.or(ret.get(atomIndex), child.eq(factory.integer(i)))); + } + ret.setDefCond(child.defCond()); + break; + case BITSETCAST : + final List twosComplement = child.twosComplementBits(); + final int msb = twosComplement.size()-1; + // handle all bits but the sign bit + for(int i = 0; i < msb; i++) { + int pow2 = 1< t, + * cache(intExpr, intExpr.condition.accept(this).choice(intExpr.then.accept(this), intExpr.else.accept(this))) + */ + public final Int visit(IfIntExpression intExpr) { + Int ret = lookup(intExpr); + if (ret!=null) return ret; + + final BooleanValue condition = intExpr.condition().accept(this); + final Int thenExpr = intExpr.thenExpr().accept(this); + final Int elseExpr = intExpr.elseExpr().accept(this); + ret = thenExpr.choice(condition, elseExpr); + return cache(intExpr, ret); + } + + /** + * Returns an Int that represents the sum of all the integers that + * correspond to non-FALSE entries in the given matrix. + * @param iter an iterator over all the bound integers. Initial should be this.manager.ints().iterator(). + * @param lo the first element of the current partial sum. Initial should be 0. + * @param hi the last element of the current partial sum. Initial should be size-1, where size is the total + * number of elements returned by the iterator. + * @return an Int that represents the sum of all the integers that + * correspond to non-FALSE entries in the given matrix. + */ + private final Int sum(BooleanMatrix m, IntIterator iter, int low, int high) { + if (low > high) + return interpreter.factory().integer(0); + else if (low==high) { + int i = iter.next(); + return interpreter.factory().integer(i, m.get(interpreter.interpret(i))); + } else { + final int mid = (low + high) / 2; + final Int lsum = sum(m, iter, low, mid); + final Int hsum = sum(m, iter, mid+1, high); + return lsum.plus(hsum); + } + } + + /** + * Calls lookup(intExpr) and returns the cached value, if any. + * If a translation has not been cached, translates the expression, + * calls cache(...) on it and returns it. + * @return let t = lookup(intExpr) | some t => t, + * cache(intExpr, translate(intExpr)) + */ + public final Int visit(ExprToIntCast intExpr) { + Int ret = lookup(intExpr); + if (ret!=null) return ret; + vars = vars.createNested(); + BooleanMatrix expr = intExpr.expression().accept(this); + switch(intExpr.op()) { + case CARDINALITY : + ret = expr.cardinality(); break; + case SUM : + final IntSet ints = interpreter.ints(); + ret = sum(expr, ints.iterator(), 0, ints.size()-1); break; + default: + throw new IllegalArgumentException("unknown operator: " + intExpr.op()); + } + for (Variable v : vars) ret.defCond().addVar(v); + vars = vars.parent(); + return cache(intExpr, ret); + } + + /** + * Calls lookup(intExpr) and returns the cached value, if any. + * If a translation has not been cached, translates the expression, + * calls cache(...) on it and returns it. + * @return let t = lookup(intExpr) | some t => t, + * cache(intExpr, intExpr.left.accept(this) intExpr.op intExpr.right.accept(this)) + */ + public final Int visit(BinaryIntExpression intExpr) { + Int ret = lookup(intExpr); + if (ret!=null) return ret; + final Int left = intExpr.left().accept(this); + final Int right = intExpr.right().accept(this); + switch(intExpr.op()) { + case PLUS : ret = left.plus(right); break; + case MINUS : ret = left.minus(right); break; + case MULTIPLY : ret = left.multiply(right); break; + case DIVIDE : ret = left.divide(right); break; + case MODULO : ret = left.modulo(right); break; + case AND : ret = left.and(right); break; + case OR : ret = left.or(right); break; + case XOR : ret = left.xor(right); break; + case SHL : ret = left.shl(right); break; + case SHR : ret = left.shr(right); break; + case SHA : ret = left.sha(right); break; + default : + throw new IllegalArgumentException("Unknown operator: " + intExpr.op()); + } + return cache(intExpr, ret); + } + + /** + * Calls lookup(intExpr) and returns the cached value, if any. + * If a translation has not been cached, translates the expression, + * calls cache(...) on it and returns it. + * @return let t = lookup(intExpr) | some t => t, + * cache(intExpr, intExpr.left.accept(this) intExpr.op intExpr.right.accept(this)) + */ + public final Int visit(NaryIntExpression intExpr) { + Int ret = lookup(intExpr); + if (ret!=null) return ret; + final Int first = intExpr.child(0).accept(this); + final Int[] rest = new Int[intExpr.size()-1]; + for(int i = 0; i < rest.length; i++) { rest[i] = intExpr.child(i+1).accept(this); } + switch(intExpr.op()) { + case PLUS : ret = first.plus(rest); break; + case MULTIPLY : ret = first.multiply(rest); break; + case AND : ret = first.and(rest); break; + case OR : ret = first.or(rest); break; + default : + throw new IllegalArgumentException("Unknown nary operator: " + intExpr.op()); + } + return cache(intExpr, ret); + } + + /** + * Calls lookup(intExpr) and returns the cached value, if any. + * If a translation has not been cached, translates the expression, + * calls cache(...) on it and returns it. + * @return let t = lookup(intExpr) | some t => t, + * cache(intExpr, intExpr.op(intExpr.expression.accept(this))) + */ + public final Int visit(UnaryIntExpression intExpr) { + Int ret = lookup(intExpr); + if (ret!=null) return ret; + final Int child = intExpr.intExpr().accept(this); + switch(intExpr.op()) { + case NEG : ret = child.negate(); break; + case NOT : ret = child.not(); break; + case ABS : ret = child.abs(); break; + case SGN : ret = child.sgn(); break; + default : + throw new IllegalArgumentException("Unknown operator: " + intExpr.op()); + } + return cache(intExpr, ret); + } + + /** + * Translates the given sum expression as follows + * (where A_0...A_|A| stand for boolean variables that represent the + * tuples of the expression A, etc.): + * let sum = "sum a: A, b: B, ..., x: X | IE(a, b, ..., x) " | + * sum a: A, b: B, ..., x: X | if (a in A && b in B && ... && x in X) then IE(a, b, ..., x) else 0 }. + * @param decls intexpr declarations + * @param formula the formula body + * @param currentDecl currently processed declaration; should be 0 initially + * @param declConstraints the constraints implied by the declarations; should be Boolean.TRUE intially + * @param values integer values computed so far + */ + private final void sum(Decls decls, IntExpression expr, int currentDecl, BooleanValue declConstraints, + List values) { + final BooleanFactory factory = interpreter.factory(); + if (decls.size()==currentDecl) { + Int intExpr = expr.accept(this); + Int newInt = intExpr.choice(declConstraints, factory.integer(0)); + values.add(newInt); + return; + } + + final Decl decl = decls.get(currentDecl); + final BooleanMatrix declTransl = visit(decl); + final BooleanMatrix groundValue = factory.matrix(declTransl.dimensions()); + env = env.extend(decl.variable(), decl.expression(), groundValue); + for(IndexedEntry entry : declTransl) { + groundValue.set(entry.index(), BooleanConstant.TRUE); + sum(decls, expr, currentDecl+1, factory.and(entry.value(), declConstraints), values); + groundValue.set(entry.index(), BooleanConstant.FALSE); + } + env = env.parent(); + } + + /** + * Calls lookup(intExpr) and returns the cached value, if any. + * If a translation has not been cached, translates the expression, + * calls cache(...) on it and returns it. + * @return let t = lookup(intExpr) | some t => t, + * cache(intExpr, translate(intExpr)) + */ + public final Int visit(SumExpression intExpr) { + final Int ret = lookup(intExpr); + if (ret!=null) return ret; + final List values = new ArrayList(); + sum(intExpr.decls(), intExpr.intExpr(), 0, BooleanConstant.TRUE, values); + for(int sums = values.size(); sums > 1; sums -= sums/2) { + final int max = sums-1; + for(int i = 0; i < max; i += 2) { + values.set(i/2, values.get(i).plus(values.get(i+1))); + } + if (max%2==0) { // even max => odd number of entries + values.set(max/2, values.get(max)); + } + } + if (values.isEmpty()) { + return cache(intExpr, interpreter.factory().integer(0)); + } else { + Int sumValue = values.get(0); + return cache(intExpr, sumValue); + } + } + + /** + * Calls lookup(intComp) and returns the cached value, if any. + * If a translation has not been cached, translates the formula, + * calls cache(...) on it and returns it. + * + * This is the only place where Ints are turned into + * formulas, so that's where the overflow circuits of individual + * Ints are built into the translated formula. + * + * @return let t = lookup(intComp) | some t => t, + * cache(intComp, intComp.left.accept(this) intComp.op intComp.right.accept(this)) + */ + public final BooleanValue visit(IntComparisonFormula intComp) { + BooleanValue ret = lookup(intComp); + if (ret!=null) return ret; + final Int left = intComp.left().accept(this); + final Int right = intComp.right().accept(this); + switch(intComp.op()) { + case EQ : ret = left.eq(right, env); break; + case NEQ : ret = left.neq(right, env); break; + case LT : ret = left.lt(right, env); break; + case LTE : ret = left.lte(right, env); break; + case GT : ret = left.gt(right, env); break; + case GTE : ret = left.gte(right, env); break; + default: + throw new IllegalArgumentException("Unknown operator: " + intComp.op()); + } + return cache(intComp, ret); + } +} + diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/fol2sat/FileLogger.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/fol2sat/FileLogger.java new file mode 100644 index 00000000..c592443d --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/fol2sat/FileLogger.java @@ -0,0 +1,337 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.engine.fol2sat; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.Collections; +import java.util.IdentityHashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; + +import kodkod.ast.ConstantFormula; +import kodkod.ast.Expression; +import kodkod.ast.Formula; +import kodkod.ast.Node; +import kodkod.ast.Variable; +import kodkod.engine.Solver; +import kodkod.engine.bool.BooleanMatrix; +import kodkod.engine.bool.BooleanValue; +import kodkod.instance.Bounds; +import kodkod.instance.TupleFactory; +import kodkod.instance.TupleSet; +import kodkod.util.collections.Containers; +import kodkod.util.collections.FixedMap; +import kodkod.util.ints.Ints; +import kodkod.util.nodes.AnnotatedNode; +import kodkod.util.nodes.Nodes; + +/** + * A file-based translation logger that logs translation events + * to a temporary file. + * @specfield originalFormula: Formula // the {@linkplain Solver#solve(Formula, kodkod.instance.Bounds) original} formula, provided by the user + * @specfield originalBounds: Bounds // the {@linkplain Solver#solve(Formula, kodkod.instance.Bounds) original} bounds, provided by the user + * @specfield formula: Formula // desugaring of this.formula that was translated + * @specfield bounds: Bounds // translation bounds + * @specfield records: (formula.*children & Formula) -> BooleanValue -> Environment + * @invariant Solver.solve(formula, bounds).instance() == null iff Solver.solve(originalFormula, originalBounds).instance() == null + * @author Emina Torlak + */ +final class FileLogger extends TranslationLogger { + + private final FixedMap logMap; + private final AnnotatedNode annotated; + private final File file; + private DataOutputStream out; + private final Bounds bounds; + /** + * Constructs a new file logger from the given annotated formula. + * @ensures this.formula' = annotated.node + * @ensures this.originalFormula' = annotated.source[annotated.node] + * @ensures this.bounds' = bounds + * @ensures this.log().roots() = Nodes.conjuncts(annotated) + * @ensures no this.records' + */ + FileLogger(final AnnotatedNode annotated, Bounds bounds) { + this.annotated = annotated; + try { + this.file = File.createTempFile("kodkod", ".log"); + this.out = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(file))); + } catch (IOException e1) { + throw new RuntimeException(e1); + } + + final Map> freeVarMap = freeVars(annotated); + final Variable[] empty = new Variable[0]; + + this.logMap = new FixedMap(freeVarMap.keySet()); + + for(Map.Entry e : logMap.entrySet()) { + Set vars = freeVarMap.get(e.getKey()); + int size = vars.size(); + if (size==0) { + e.setValue(empty); + } else { + e.setValue(Containers.identitySort(vars.toArray(new Variable[size]))); + } + } + this.bounds = bounds.unmodifiableView(); + } + + /** + * Returns a map from all formulas in the given annotated node to their free variables. + * @return a map from all formulas in the given annotated node to their free variables. + */ + @SuppressWarnings("unchecked") + private static Map> freeVars(final AnnotatedNode annotated) { + final Map> freeVarMap = new IdentityHashMap>(); + final FreeVariableCollector collector = new FreeVariableCollector(annotated.sharedNodes()) { + protected Set cache(Node node, Set freeVars) { + if (node instanceof Formula) { + freeVarMap.put((Formula)node, freeVars); + } + return super.cache(node, freeVars); + } + public Set visit(ConstantFormula constant) { + return cache(constant, Collections.EMPTY_SET); + } + + }; + annotated.node().accept(collector); + return freeVarMap; + } + + /** + * @see kodkod.engine.fol2sat.TranslationLogger#close() + */ + @Override + void close() { + try { + if (out!=null) { out.close(); } + } catch (IOException e1) { + /* unused */ + } finally { out = null; } + } + + /** + * Records the translation of the source of the + * given transformed formula to the given boolean value + * in the specified environment. + * @requires some this.transforms.f + * @ensures this.records' = this.records + this.transforms.f -> translation -> freeVariables(f)<:env + * @throws IllegalArgumentException - no this.transforms.f + * @throws IllegalStateException - this log has been closed + */ + @Override + void log(Formula f, BooleanValue v, Environment env) { + if (out==null) throw new IllegalStateException(); + + final int index = logMap.indexOf(f); + if (index < 0) throw new IllegalArgumentException(); + + final Variable[] vars = logMap.get(index); + + try { + out.writeInt(index); + out.writeInt(v.label()); + for(Variable var : vars) { + out.writeInt(env.lookup(var).denseIndices().min()); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + /** + * @see kodkod.engine.fol2sat.TranslationLogger#log() + */ + @Override + TranslationLog log() { + return new FileLog(annotated, logMap, file, bounds); + } + + /** + * @see java.lang.Object#finalize() + */ + protected final void finalize() { + close(); + } + + /** + * A file-based translation log, written by a FileLogger. + * @author Emina Torlak + */ + private static final class FileLog extends TranslationLog { + private final Set roots; + private final Node[] original; + private final Formula[] translated; + private final Variable[][] freeVars; + private final File file; + private final Bounds bounds; + + /** + * Constructs a new file log for the sources of the given annotated formula, + * using the provided fixed map, file, and tuplefactory. + * @requires all f: annotated.node.*children & Formula | logMap.get(f) = freeVariables(f) + * @requires the file was written by a FileLogger using the given map + */ + FileLog(AnnotatedNode annotated, FixedMap logMap, File file, Bounds bounds) { + this.file = file; + this.bounds = bounds; + this.roots = Nodes.conjuncts(annotated.node()); + + final int size = logMap.entrySet().size(); + this.original = new Node[size]; + this.translated = new Formula[size]; + this.freeVars = new Variable[size][]; + int index = 0; + for(Map.Entry e : logMap.entrySet()) { + translated[index] = e.getKey(); + original[index] = annotated.sourceOf(e.getKey()); + freeVars[index] = e.getValue(); + index++; + } + } + + /** + * Deletes the log file. + * @see java.lang.Object#finalize() + */ + protected final void finalize() { + file.delete(); + } + + /** + * {@inheritDoc} + * @see kodkod.engine.fol2sat.TranslationLog#roots() + */ + public Set roots() { return roots; } + + /** + * {@inheritDoc} + * @see kodkod.engine.fol2sat.TranslationLog#bounds() + */ + public Bounds bounds() { return bounds; } + + /** + * {@inheritDoc} + * @see kodkod.engine.fol2sat.TranslationLog#replay(kodkod.engine.fol2sat.RecordFilter) + */ + public Iterator replay(final RecordFilter filter) { + try { + return new Iterator() { + final TupleFactory factory = bounds.universe().factory(); + final DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream(file))); + final MutableRecord current = new MutableRecord(), next = new MutableRecord(); + long remaining = file.length(); + + public boolean hasNext() { + while(remaining > 0 && next.node == null) { + try { + final long indexLiteral = in.readLong(); + final int literal = (int) (indexLiteral); + final int index = (int) (indexLiteral>>>32); + final Variable[] freeVars = FileLog.this.freeVars[index]; + final Map env; + if (freeVars.length==0) { + env = Collections.emptyMap(); + } else { + env = new FixedMap(freeVars); + for(int i = 0; i < freeVars.length; i++) { + env.put(freeVars[i], factory.setOf(1, Ints.singleton(in.readInt()))); + } + } + if (filter.accept(original[index], translated[index], literal, env)) { + next.setAll(original[index], translated[index], literal, env); + } + remaining -= (8 + (freeVars.length << 2)); + + } catch (IOException e) { + throw new RuntimeException(e); + } + } + if (next.node==null) { + try { + in.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + return false; + } else { + return true; + } + } + + public TranslationRecord next() { + if (!hasNext()) throw new NoSuchElementException(); + return current.setAll(next); + } + + public void remove() { throw new UnsupportedOperationException(); } + + protected final void finalize() { + try { in.close(); } catch (IOException e) { /* unused */ } + } + }; + } catch (FileNotFoundException e) { + throw new RuntimeException(e); + } + } + } + + /** + * A mutable translation record. + * @author Emina Torlak + */ + private static final class MutableRecord extends TranslationRecord { + Node node = null; + Formula translated = null; + int literal = 0; + Map env = null; + + public Map env() { return env; } + public int literal() { return literal; } + public Node node() { return node; } + void setAll(Node node, Formula translated, int literal, Map env) { + this.node = node; + this.translated = translated; + this.literal = literal; + this.env = env; + } + TranslationRecord setAll(MutableRecord other) { + setAll(other.node, other.translated, other.literal, other.env); + other.setAll(null,null,0,null); + return this; + } + public Formula translated() { return translated;} + } + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/fol2sat/FormulaFlattener.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/fol2sat/FormulaFlattener.java new file mode 100644 index 00000000..9bdab9b1 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/fol2sat/FormulaFlattener.java @@ -0,0 +1,275 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.engine.fol2sat; + +import static kodkod.ast.operator.FormulaOperator.AND; +import static kodkod.ast.operator.FormulaOperator.IFF; +import static kodkod.ast.operator.FormulaOperator.IMPLIES; +import static kodkod.ast.operator.FormulaOperator.OR; +import static kodkod.ast.operator.Quantifier.ALL; +import static kodkod.ast.operator.Quantifier.SOME; + +import java.util.ArrayList; +import java.util.IdentityHashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import kodkod.ast.BinaryFormula; +import kodkod.ast.ComparisonFormula; +import kodkod.ast.ConstantFormula; +import kodkod.ast.Decls; +import kodkod.ast.Formula; +import kodkod.ast.IntComparisonFormula; +import kodkod.ast.MultiplicityFormula; +import kodkod.ast.NaryFormula; +import kodkod.ast.Node; +import kodkod.ast.NotFormula; +import kodkod.ast.QuantifiedFormula; +import kodkod.ast.RelationPredicate; +import kodkod.ast.operator.FormulaOperator; +import kodkod.ast.operator.Quantifier; +import kodkod.ast.visitor.AbstractVoidVisitor; +import kodkod.util.nodes.AnnotatedNode; + +/** + * Flattens a given formula by putting into negation normal form and, optionally, + * by breaking up universally quantifier formulas whenever possible. + * + * @author Emina Torlak + */ +final class FormulaFlattener extends AbstractVoidVisitor { + + /** + * Flattens the given formula into a set of conjuncts + * by pushing negations through quantifier-free formulas, if breakupQuantifiers is false. + * Otherwise, pushes the negations through all formulas, breaking up universal quantifiers + * whenever possible. The source map of the returned annotated node reflects the source relationships + * from the descendants of the returned formula to the sources of the corresponding descendants of annotated.node. + * @return a map that binds each flattened conjuncts to the corresponding + * subformula of annotated.node + */ + public static AnnotatedNode flatten(AnnotatedNode annotated, boolean breakupQuantifiers) { + final FormulaFlattener flat = new FormulaFlattener(annotated.sharedNodes(), breakupQuantifiers); + annotated.node().accept(flat); + final List roots = new ArrayList(flat.conjuncts.size()); + roots.addAll(flat.conjuncts.keySet()); + for(Iterator> itr = flat.conjuncts.entrySet().iterator(); itr.hasNext(); ) { + final Map.Entry entry = itr.next(); + final Node source = annotated.sourceOf(entry.getValue()); + if (entry.getKey()==source) { itr.remove(); } + else { entry.setValue(source); } + } + return AnnotatedNode.annotate(Formula.and(roots), flat.conjuncts); + } + + + + private Map conjuncts; + private final Map visited; + private final Set shared; + private boolean negated; + private final boolean breakupQuantifiers; + /** + * Constructs a flattener for a formula in which the given nodes are shared. + */ + private FormulaFlattener(Set shared, boolean breakupQuantifiers) { + this.conjuncts = new LinkedHashMap(); + this.shared = shared; + this.visited = new IdentityHashMap(); + this.negated = false; + this.breakupQuantifiers = breakupQuantifiers; + } + + /** + * Returns the result of applying this visitor to the given annotated formula. + * @return the result of applying this visitor to the given annotated formula. + */ + final AnnotatedNode apply(AnnotatedNode annotated) { + annotated.node().accept(this); + final List roots = new ArrayList(conjuncts.size()); + roots.addAll(conjuncts.keySet()); + for(Iterator> itr = conjuncts.entrySet().iterator(); itr.hasNext(); ) { + final Map.Entry entry = itr.next(); + final Node source = annotated.sourceOf(entry.getValue()); + if (entry.getKey()==source) { itr.remove(); } + else { entry.setValue(source); } + } + return AnnotatedNode.annotate(Formula.and(roots), conjuncts); + } + + /** + * {@inheritDoc} + * @see kodkod.ast.visitor.AbstractVoidVisitor#visited(kodkod.ast.Node) + */ + @Override + protected boolean visited(Node n) { + if (shared.contains(n)) { + if (visited.containsKey(n)) { + final Boolean val = visited.get(n); + if (val==null || val.booleanValue()==negated) { + return true; + } else { + visited.put(n, null); + return false; + } + } else { + visited.put(n, Boolean.valueOf(negated)); + return false; + } + } + return false; + } + + + + /** + * Calls nf.formula.accept(this) after flipping the negation flag. + * @see kodkod.ast.visitor.AbstractVoidVisitor#visit(kodkod.ast.NotFormula) + */ + public final void visit(NotFormula nf) { + if (visited(nf)) return; + + final Map oldConjuncts = conjuncts; + conjuncts = new LinkedHashMap(); + negated = !negated; + nf.formula().accept(this); + negated = !negated; + if (conjuncts.size()>1) { // was broken down further + oldConjuncts.putAll(conjuncts); + conjuncts = oldConjuncts; + } else { // wasn't broken down further + conjuncts = oldConjuncts; + conjuncts.put(negated ? nf.formula() : nf, nf); + } + } + + /** + * Adds the given formula (or its negation, depending on the value of the negated flag) + * to this.conjuncts. + */ + private final void addConjunct(Formula conjunct) { + conjuncts.put(negated ? conjunct.not() : conjunct, conjunct); + } + + /** + * Visits the formula's children with appropriate settings + * for the negated flag if bf has not been visited before. + * @see kodkod.ast.visitor.AbstractVoidVisitor#visit(kodkod.ast.BinaryFormula) + */ + public final void visit(BinaryFormula bf) { + if (visited(bf)) return; + final FormulaOperator op = bf.op(); + if (op==IFF || (negated && op==AND) || (!negated && (op==OR || op==IMPLIES))) { // can't break down further in these cases + addConjunct(bf); + } else { // will break down further + if (negated && op==IMPLIES) { // !(a => b) = !(!a || b) = a && !b + negated = !negated; + bf.left().accept(this); + negated = !negated; + bf.right().accept(this); + } else { + bf.left().accept(this); + bf.right().accept(this); + } + } + } + + /** + * Visits the formula's children with appropriate settings + * for the negated flag if bf has not been visited before. + * @see kodkod.ast.visitor.AbstractVoidVisitor#visit(kodkod.ast.NaryFormula) + */ + public final void visit(NaryFormula nf) { + if (visited(nf)) return; + final FormulaOperator op = nf.op(); + if ((negated && op==AND) || (!negated && op==OR)) { // can't break down further in these cases + addConjunct(nf); + } else { // will break down further + for(Formula f : nf) { + f.accept(this); + } + } + } + + /** + * {@inheritDoc} + * @see kodkod.ast.visitor.AbstractVoidVisitor#visit(kodkod.ast.QuantifiedFormula) + */ + public final void visit(QuantifiedFormula qf) { + if (visited(qf)) return; + + if (breakupQuantifiers) { + + final Quantifier quant = qf.quantifier(); + + if ((!negated && quant==ALL) || (negated && quant==SOME)) { // may break down further + final Map oldConjuncts = conjuncts; + conjuncts = new LinkedHashMap(); + qf.formula().accept(this); + if (conjuncts.size()>1) { // was broken down further + final Decls decls = qf.decls(); + for(Map.Entry entry : conjuncts.entrySet()) { + oldConjuncts.put(entry.getKey().forAll(decls), entry.getValue()); + } + conjuncts = oldConjuncts; + return; + } else { // wasn't broken down further + conjuncts = oldConjuncts; + } + } // won't break down further + } + + addConjunct(qf); + + } + + /** + * Adds f (resp. f.not()) to this.conjuncts if the negated flag is false (resp. true) and + * the given node has not been visited; otherwise does nothing. + * @ensures !this.visited(f) => + * (this.conjuncts' = conjuncts + (negated => f.not() else f)) else + * (this.conjuncts' = this.conjuncts) + */ + final void visitFormula(Formula f) { + if (visited(f)) return; + addConjunct(f); + } + + /** @see #visitFormula(Formula) */ + public final void visit(ComparisonFormula cf) { visitFormula(cf); } + + /** @see #visitFormula(Formula) */ + public final void visit(IntComparisonFormula cf) { visitFormula(cf); } + + /** @see #visitFormula(Formula) */ + public final void visit(MultiplicityFormula mf) { visitFormula(mf); } + + /** @see #visitFormula(Formula) */ + public final void visit(ConstantFormula constant) { visitFormula(constant); } + + /** @see #visitFormula(Formula) */ + public final void visit(RelationPredicate pred) { visitFormula(pred); } + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/fol2sat/FreeVariableCollector.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/fol2sat/FreeVariableCollector.java new file mode 100644 index 00000000..3322a485 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/fol2sat/FreeVariableCollector.java @@ -0,0 +1,180 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.engine.fol2sat; + +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Set; + +import kodkod.ast.Comprehension; +import kodkod.ast.Decl; +import kodkod.ast.Decls; +import kodkod.ast.Node; +import kodkod.ast.QuantifiedFormula; +import kodkod.ast.SumExpression; +import kodkod.ast.Variable; +import kodkod.ast.visitor.AbstractCollector; +import kodkod.util.collections.ArrayStack; +import kodkod.util.collections.Stack; + +/** + * Collects free variables in a given Node. Subclasses + * can customize the collection policy by overriding the + * cache method. By default, the cache + * will contain only the free variables of the shared nodes. The + * default implementation of {@link #newSet()} ensures that + * the collected free variables will be returned in the order + * in which they were encountered during traversal. + * @specfield cached: set Node + * @specfield cache: Node -> lone Set + * @specfield varsInScope: Stack // variables currently in scope + * @author Emina Torlak + */ +abstract class FreeVariableCollector extends AbstractCollector { + /* Holds the variables that are currently in scope, with the + * variable at the top of the stack being the last declared variable. */ + protected final Stack varsInScope; + + /** + * Constructs a new collector using the given structural information. + * The given set is required to contain the syntactically shared subtrees of the + * node for which we are computing caching information. + */ + protected FreeVariableCollector(Set cached) { + super(cached); + this.varsInScope = new ArrayStack(); + } + + /** + * @see kodkod.ast.visitor.AbstractCollector#newSet() + */ + @Override + protected Set newSet() { + return new LinkedHashSet(2); + } + + /** + * Visits the given comprehension, quantified formula, or sum expression. + * The method returns a set that contains all + * the free variables in the declarations and the body, minus the variables that are + * actually bound in the declarations. + */ + @SuppressWarnings("unchecked") + private Set visit(Node creator, Decls decls, Node body) { + Set ret = lookup(creator); + if (ret!=null) return ret; + + ret = newSet(); + final Set boundVars = newSet(); + + // add the declared variables to the scoped variables stack; + // compute free vars for each decl, and the difference of the + // computed set and previously bound variables to ret + for(Decl decl : decls) { + for(Variable v : visit(decl)) { + if (!boundVars.contains(v)) + ret.add(v); + } + varsInScope.push(decl.variable()); + boundVars.add(decl.variable()); + } + + // add to ret the free variables in the body, minus the bound variables + for(Variable v: (Set) body.accept(this)) { + if (!boundVars.contains(v)) + ret.add(v); + } + + // remove the declared variables from the in-scope stack + for(int i = decls.size(); i > 0; i--) { + varsInScope.pop(); + } + + return cache(creator, ret); + } + + /** + * Returns the free variables in the given declaration. + * @return freeVars(decl.expression) + */ + public Set visit(Decl decl) { + final Set ret = lookup(decl); + return ret != null ? ret : cache(decl, decl.expression().accept(this)); + } + + /** + * Returns the singleton set containing the given variable. + * @return {variable} + */ + @Override + public Set visit(Variable variable) { + return cache(variable, Collections.singleton(variable)); + } + + /** + * Calls lookup(comprehension) and returns the cached value, if any. + * If no cached value exists, computes, caches and returns the set + * of free variables in comprehension. + * @return let x = lookup(comprehension), d = comprehension.declarations, f = comprehension.formula | + * x != null => x, + * cache(comprehension, + * (f.accept(this) - d.children.variable) + + * {v: Variable | some i: [0..d.size) | + * v in d.declarations[i].accept(this) - d.declarations[0..i).variable } ) + */ + @Override + public Set visit(Comprehension comprehension) { + return visit(comprehension, comprehension.decls(), comprehension.formula()); + } + + /** + * Calls lookup(intExpr) and returns the cached value, if any. + * If no cached value exists, computes, caches and returns the set + * of free variables in intExpr. + * @return let x = lookup(intExpr), d = intExpr.declarations, e = intExpr.intExpr | + * x != null => x, + * cache(intExpr, + * (e.accept(this) - d.children.variable) + + * {v: Variable | some i: [0..d.size) | + * v in d.declarations[i].accept(this) - d.declarations[0..i).variable } ) + */ + @Override + public Set visit(SumExpression intExpr) { + return visit(intExpr, intExpr.decls(), intExpr.intExpr()); + } + + /** + * Calls lookup(quantFormula) and returns the cached value, if any. + * If no cached value exists, computes, caches and returns the set + * of free variables in quantFormula. + * @return let x = lookup(quantFormula), d = quantFormula.declarations, f = quantFormula.formula | + * x != null => x, + * cache(quantFormula, + * (f.accept(this) - d.children.variable) + + * {v: Variable | some i: [0..d.size) | + * v in d.declarations[i].accept(this) - d.declarations[0..i).variable } ) + */ + @Override + public Set visit(QuantifiedFormula quantFormula) { + return visit(quantFormula, quantFormula.decls(), quantFormula.formula()); + } +} \ No newline at end of file diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/fol2sat/FullNegationPropagator.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/fol2sat/FullNegationPropagator.java new file mode 100644 index 00000000..f340aea1 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/fol2sat/FullNegationPropagator.java @@ -0,0 +1,360 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.engine.fol2sat; + +import static kodkod.ast.operator.FormulaOperator.AND; +import static kodkod.ast.operator.FormulaOperator.OR; + +import java.util.ArrayList; +import java.util.IdentityHashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import kodkod.ast.BinaryFormula; +import kodkod.ast.ComparisonFormula; +import kodkod.ast.ConstantFormula; +import kodkod.ast.Formula; +import kodkod.ast.IntComparisonFormula; +import kodkod.ast.MultiplicityFormula; +import kodkod.ast.NaryFormula; +import kodkod.ast.Node; +import kodkod.ast.NotFormula; +import kodkod.ast.QuantifiedFormula; +import kodkod.ast.RelationPredicate; +import kodkod.ast.operator.FormulaOperator; +import kodkod.ast.visitor.AbstractVoidVisitor; +import kodkod.util.nodes.AnnotatedNode; + +/** + * Propagates negations all the way down to the leafs, but without crossing + * quantification boundaries. It also eliminates negations wherever possible + * (e.g. double negation, !(a>b) --> a<=b, etc.) + * + * Breaks up all implications (=>) and two-way implications (<=>), so that + * the resulting formula only contains the following boolean operators: + * AND (&&), OR (||), and NOT (!) at the leaf positions. + */ +final class FullNegationPropagator extends AbstractVoidVisitor { + + /** + * + */ + public static AnnotatedNode flatten(AnnotatedNode annotated) { + final FullNegationPropagator flat = new FullNegationPropagator(annotated.sharedNodes()); + annotated.node().accept(flat); + final List roots = new ArrayList(flat.annotations.size()); + roots.addAll(flat.annotations.keySet()); + for(Iterator> itr = flat.annotations.entrySet().iterator(); itr.hasNext(); ) { + final Map.Entry entry = itr.next(); + final Node source = annotated.sourceOf(entry.getValue()); + if (entry.getKey()==source) { itr.remove(); /* TODO: what is this for? */ } + else { entry.setValue(source); } + } + return AnnotatedNode.annotate(Formula.and(flat.conjuncts), flat.annotations); + } + + private List conjuncts; + private Map annotations; + private final Map visited; + private final Set shared; + private boolean negated; + private boolean hasChanged; + + /** + * Constructs a flattener for a formula in which the given nodes are shared. + */ + private FullNegationPropagator(Set shared) { + this(shared, new LinkedHashMap(), new IdentityHashMap()); + } + + private FullNegationPropagator(Set shared, Map annotations, Map visited) { + this.conjuncts = new LinkedList(); + this.annotations = annotations; + this.shared = shared; + this.visited = visited; + this.negated = false; + } + + /** + * {@inheritDoc} + * @see kodkod.ast.visitor.AbstractVoidVisitor#visited(kodkod.ast.Node) + */ + @Override + protected boolean visited(Node n) { + if (shared.contains(n)) { + if (visited.containsKey(n)) { + final Boolean val = visited.get(n); + if (val==null || val.booleanValue()==negated) { + return true; + } else { + visited.put(n, null); + return false; + } + } else { + visited.put(n, Boolean.valueOf(negated)); + return false; + } + } + return false; + } + + /** + * Calls nf.formula.accept(this) after flipping the negation flag. + * @see kodkod.ast.visitor.AbstractVoidVisitor#visit(kodkod.ast.NotFormula) + */ + public final void visit(NotFormula nf) { + if (visited(nf)) return; + + FullNegationPropagator fne = new FullNegationPropagator(shared, annotations, visited); + fne.negated = !negated; + nf.formula().accept(fne); + if (fne.hasChanged) { + addConjunct(Formula.and(fne.conjuncts), false, nf); + hasChanged = true; + } else { + addConjunct(nf); + } + } + + /** + * Adds the given formula (or its negation, depending on the value of the negated flag) + * to this.conjuncts. + */ + private final void addConjunct(Formula conjunct) { + Formula f = negated ? conjunct.not() : conjunct; + conjuncts.add(f); + annotations.put(f, conjunct); + } + private final void addConjunct(Formula conjunct, boolean neg, Node source) { + Formula f = neg ? conjunct.not() : conjunct; + conjuncts.add(f); + annotations.put(f, source); + } + + /** + * Visits the formula's children with appropriate settings + * for the negated flag if bf has not been visited before. + * @see kodkod.ast.visitor.AbstractVoidVisitor#visit(kodkod.ast.BinaryFormula) + */ + public final void visit(BinaryFormula bf) { + if (visited(bf)) return; + final FormulaOperator op = bf.op(); + switch (op) { + case AND: + if (!negated) { + // left && right + bf.left().accept(this); + bf.right().accept(this); + } else { + // !(left && right) --> !left || !right + FullNegationPropagator fne1 = new FullNegationPropagator(shared, annotations, visited); + bf.left().not().accept(fne1); + + FullNegationPropagator fne2 = new FullNegationPropagator(shared, annotations, visited); + bf.right().not().accept(fne2); + + addConjunct(Formula.and(fne1.conjuncts).or(Formula.and(fne2.conjuncts)), false, bf); + hasChanged = true; + } + break; + case OR: + if (!negated) { + // left || right + FullNegationPropagator fne1 = new FullNegationPropagator(shared, annotations, visited); + bf.left().accept(fne1); + + FullNegationPropagator fne2 = new FullNegationPropagator(shared, annotations, visited); + bf.right().accept(fne2); + + if (!fne1.hasChanged && !fne2.hasChanged) { + addConjunct(bf); + } else { + addConjunct(Formula.and(fne1.conjuncts).or(Formula.and(fne2.conjuncts)), false, bf); + hasChanged = true; + } + } else { + // !(left || right) --> !left && !right + bf.left().accept(this); + bf.right().accept(this); + hasChanged = true; + } + break; + case IMPLIES: + if (!negated) { + // left => right --> !left || right + FullNegationPropagator fne1 = new FullNegationPropagator(shared, annotations, visited); + bf.left().not().accept(fne1); + + FullNegationPropagator fne2 = new FullNegationPropagator(shared, annotations, visited); + bf.right().accept(fne2); + + addConjunct(Formula.and(fne1.conjuncts).or(Formula.and(fne2.conjuncts)), false, bf); + } else { + // !(left => right) --> left && !right + negated = false; + bf.left().accept(this); + negated = true; + bf.right().accept(this); + } + hasChanged = true; + break; + case IFF: + FullNegationPropagator fne1 = new FullNegationPropagator(shared, annotations, visited); + FullNegationPropagator fne2 = new FullNegationPropagator(shared, annotations, visited); + if (!negated) { + // a = b --> (a && b) || (!a && !b) + bf.left().and(bf.right()).accept(fne1); + bf.left().not().and(bf.right().not()).accept(fne2); + } else { + // !(a = b) --> (a && !b) || (!a && b) + bf.left().and(bf.right().not()).accept(fne1); + bf.left().not().and(bf.right()).accept(fne2); + } + addConjunct(Formula.and(fne1.conjuncts).or(Formula.and(fne2.conjuncts)), false, bf); + hasChanged = true; + break; + default: + addConjunct(bf); + } + } + + /** + * Visits the formula's children with appropriate settings + * for the negated flag if bf has not been visited before. + * @see kodkod.ast.visitor.AbstractVoidVisitor#visit(kodkod.ast.NaryFormula) + */ + public final void visit(NaryFormula nf) { + if (visited(nf)) return; + final FormulaOperator op = nf.op(); + if (negated && op==AND) { + List formulas = new LinkedList(); + for (Formula f : nf) { + FullNegationPropagator fne = new FullNegationPropagator(shared, annotations, visited); + f.not().accept(fne); + formulas.add(Formula.and(fne.conjuncts)); + } + addConjunct(Formula.or(formulas), false, nf); + } else if (!negated && op==OR) { + List formulas = new LinkedList(); + boolean changed = false; + for (Formula f : nf) { + FullNegationPropagator fne = new FullNegationPropagator(shared, annotations, visited); + f.accept(fne); + changed = changed || fne.hasChanged; + formulas.add(Formula.and(fne.conjuncts)); + } + if (changed) { + addConjunct(Formula.or(formulas), false, nf); + hasChanged = true; + } else { + addConjunct(nf); + } + } else { + for(Formula f : nf) { + f.accept(this); + } + } + } + + /** + * Adds f (resp. f.not()) to this.conjuncts if the negated flag is false (resp. true) and + * the given node has not been visited; otherwise does nothing. + * @ensures !this.visited(f) => + * (this.conjuncts' = conjuncts + (negated => f.not() else f)) else + * (this.conjuncts' = this.conjuncts) + */ + final void visitFormula(Formula f) { + if (visited(f)) return; + addConjunct(f); + } + + /** + * {@inheritDoc} + * @see kodkod.ast.visitor.AbstractVoidVisitor#visit(kodkod.ast.QuantifiedFormula) + */ + public final void visit(QuantifiedFormula qf) { + if (visited(qf)) return; + FullNegationPropagator fne = new FullNegationPropagator(shared, annotations, visited); + qf.formula().accept(fne); + if (fne.hasChanged) { + Formula f = Formula.and(fne.conjuncts); + addConjunct(f.quantify(qf.quantifier(), qf.decls()), negated, qf); + hasChanged = true; + } else { + addConjunct(qf); + } + } + + /** @see #visitFormula(Formula) */ + public final void visit(ComparisonFormula cf) { visitFormula(cf); } + + /** @see #visitFormula(Formula) */ + public final void visit(IntComparisonFormula cf) { + if (visited(cf)) return; + if (!negated) { + addConjunct(cf); + } else { + switch (cf.op()) { + case GT: + addConjunct(cf.left().lte(cf.right()), false, cf); + hasChanged = true; + break; + case GTE: + addConjunct(cf.left().lt(cf.right()), false, cf); + hasChanged = true; + break; + case LT: + addConjunct(cf.left().gte(cf.right()), false, cf); + hasChanged = true; + break; + case LTE: + addConjunct(cf.left().gt(cf.right()), false, cf); + hasChanged = true; + break; + case EQ: + addConjunct(cf.left().neq(cf.right()), false, cf); + hasChanged = true; + break; + case NEQ: + addConjunct(cf.left().eq(cf.right()), false, cf); + hasChanged = true; + break; + default: + addConjunct(cf); + } + } + } + + /** @see #visitFormula(Formula) */ + public final void visit(MultiplicityFormula mf) { visitFormula(mf); } + + /** @see #visitFormula(Formula) */ + public final void visit(ConstantFormula constant) { visitFormula(constant); } + + /** @see #visitFormula(Formula) */ + public final void visit(RelationPredicate pred) { visitFormula(pred); } + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/fol2sat/HigherOrderDeclException.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/fol2sat/HigherOrderDeclException.java new file mode 100644 index 00000000..eabc7051 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/fol2sat/HigherOrderDeclException.java @@ -0,0 +1,57 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.engine.fol2sat; + +import kodkod.ast.Decl; +import kodkod.ast.operator.Multiplicity; + +/** + * Thrown when a node contains a higher order declaration that cannot + * be skolemized, or it can be skolemized but skolemization is disabled. + * + * @specfield decl: Decl // higher order decl that caused the exception to be thrown + * @author Emina Torlak + */ +public final class HigherOrderDeclException extends RuntimeException { + private final Decl decl; + private static final long serialVersionUID = 1892780864484615171L; + + /** + * Constructs a HigherOrderDeclException for the given decl. + * @requires decl.multiplicity != ONE + * @ensures this.decl' = decl + */ + HigherOrderDeclException(Decl decl) { + super("Higher order declaration: " + decl); + assert decl.multiplicity() != Multiplicity.ONE; + this.decl = decl; + } + + /** + * Returns this.decl + * @return this.decl + */ + public Decl decl() { + return decl; + } + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/fol2sat/LeafInterpreter.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/fol2sat/LeafInterpreter.java new file mode 100644 index 00000000..0e041b7f --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/fol2sat/LeafInterpreter.java @@ -0,0 +1,260 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.engine.fol2sat; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +import kodkod.ast.ConstantExpression; +import kodkod.ast.Expression; +import kodkod.ast.Relation; +import kodkod.engine.bool.BooleanFactory; +import kodkod.engine.bool.BooleanMatrix; +import kodkod.engine.bool.Dimensions; +import kodkod.engine.config.Options; +import kodkod.instance.Bounds; +import kodkod.instance.Instance; +import kodkod.instance.TupleSet; +import kodkod.instance.Universe; +import kodkod.util.ints.IntIterator; +import kodkod.util.ints.IntRange; +import kodkod.util.ints.IntSet; +import kodkod.util.ints.Ints; +import kodkod.util.ints.SparseSequence; + +/** + *

Interprets the unquantified leaf expressions of a kodkod ast, {@link kodkod.ast.Relation relations} and + * {@link kodkod.ast.ConstantExpression constant expressions}, as {@link kodkod.engine.bool.BooleanMatrix matrices} of {@link kodkod.engine.bool.BooleanValue + * boolean values}, and primitive integers as corresponding to particular atoms in the {@link kodkod.instance.Universe universe + * of discourse}

+ * + * @specfield universe: Universe + * @specfield relations: set Relation + * @specfield ints: set int + * @specfield lbounds: relations ->one TupleSet + * @specfield ubounds: relations ->one TupleSet + * @specfield ibounds: ints -> one TupleSet + * @specfield factory: BooleanFactory + * @specfield vars: relations -> set BooleanVariable + * @invariant all r: relations | r.arity = lbounds[r].arity = ubounds[r].arity && ubounds[r].containsAll(lbounds[r]) + * @invariant all r: relations | lbounds[r].atoms + ubounds[r] in universe + * @invariant all r: relations | #vars[r] = ubounds[r].size() - lbounds[r].size() + * @invariant all i: ints | ibounds[i].arity = ibounds[i].size() = 1 + * @invariant vars[relations] in factory.components + * + * @author Emina Torlak + */ +final class LeafInterpreter { + private final BooleanFactory factory; + private final Universe universe; + private final Map vars; + private final Map lowers, uppers; + private final SparseSequence ints; + + /** + * Constructs a new LeafInterpreter using the given values. + * @requires lowers.keySet() = uppers.keySet() + * @ensures this.universe' = universe && this.relations' = lowers.keySet() && + * this.ints' = ints.indices && this.factory' = factory && + * this.ubounds' = uppers && this.lbounds' = lowers && + * this.ibounds' = ints + */ + private LeafInterpreter(Universe universe, Map lowers, Map uppers, + SparseSequence ints, BooleanFactory factory, Map vars) { + this.universe = universe; + this.lowers = lowers; + this.uppers = uppers; + this.ints = ints; + this.factory = factory; + this.vars = vars; + } + + + /** + * Constructs a new LeafInterpreter using the given values. + * @requires lowers.keySet() = uppers.keySet() + * @ensures this.universe' = universe && this.relations' = lowers.keySet() && + * this.ints' = ints.indices && this.factory' = factory && + * this.ubounds' = uppers && this.lbounds' = lowers && + * this.ibounds' = ints + */ + @SuppressWarnings("unchecked") + private LeafInterpreter(Universe universe, Map rbound, SparseSequence ints, Options options) { + this(universe, rbound, rbound, ints, BooleanFactory.constantFactory(options), Collections.EMPTY_MAP); + } + + /** + * Returns an exact leaf interpreter based on the given instance and options. + * @return { l: LeafInterpreter | l.universe = instance.universe && l.relations = instance.relations() && + * l.ints = instance.ints() && l.lbounds = l.ubounds = instance.relationTuples() && + * l.ibounds = instance.intTuples && l.factory = BooleanFactory.constantFactory(options) && no l.vars } + */ + static final LeafInterpreter exact(Instance instance, Options options) { + return new LeafInterpreter(instance.universe(), instance.relationTuples(), instance.intTuples(), options); + } + + /** + * Returns an exact interpreter for the given bounds and options. + * @return { l: LeafInterpreter | l.universe = bounds.universe && l.relations = bounds.relations() && + * l.ints = bounds.ints() && l.lbounds = bounds.lowerBound && l.ubounds = bounds.upperBound && + * l.ibounds = bounds.intBound && + * l.factory = BooleanFactory.factory(sum(r: l.relations | #(l.ubounds[r]-l.lbounds[r]))-1, options) && + * l.vars[relations] = l.factory & BooleanVariable} + */ + static final LeafInterpreter exact(Bounds bounds, Options options) { + final Map vars = new LinkedHashMap(); + int maxLit = 1; + for(Relation r : bounds.relations()) { + int rLits = bounds.upperBound(r).size() - bounds.lowerBound(r).size(); + if (rLits > 0) { + vars.put(r, Ints.range(maxLit, maxLit + rLits - 1)); + maxLit += rLits; + } + } + return new LeafInterpreter(bounds.universe(), bounds.lowerBounds(), bounds.upperBounds(), + bounds.intBounds(), BooleanFactory.factory(maxLit-1, options), vars); + } + + /** + * Returns an overapproximating interpreter for the given bounds and options. + * @return { l: LeafInterpreter | l.universe = bounds.universe && l.relations = bounds.relations() && + * l.ints = bounds.ints() && l.lbounds = l.ubounds = bounds.upperBound && + * l.ibounds = bounds.intBound && l.factory = BooleanFactory.constantFactory(options) && no l.vars } + */ + static final LeafInterpreter overapproximating(Bounds bounds, Options options) { + return new LeafInterpreter(bounds.universe(), bounds.upperBounds(), bounds.intBounds(), options); + } + + /** + * Returns this.factory. + * @return this.factory. + */ + public final BooleanFactory factory() { + return this.factory; + } + + /** + * Returns the universe of discourse. + * @return this.universe + */ + public final Universe universe() { + return universe; + } + + /** + * Returns this.vars. + * @return this.vars. + */ + public final Map vars() { + final Map ret = new LinkedHashMap((vars.size() * 4)/3); + for(Map.Entry e: vars.entrySet()) { + ret.put(e.getKey(), Ints.rangeSet(e.getValue())); + } + return ret; + } + + /** + * Returns a {@link kodkod.engine.bool.BooleanMatrix matrix} m of + * {@link kodkod.engine.bool.BooleanValue boolean formulas} representing + * the specified relation. + * @requires r in this.relations + * @return { m: BooleanMatrix | let lset = (this.rBounds[r].TupleSet).tuples.index, + * hset = (this.rBounds[r][TupleSet]).tuples.index, dset = [0..this.universe.size()^r.arity) | + * m.dimensions.dimensions = [0..r.arity) ->one this.universe.size() && + * m.elements[lset] = TRUE && m.elements[dset-hset] = FALSE && + * all disj i, j: hset-lset | m.elements[i]+m.elements[j] in this.vars[r] && + * m.elements[i].label < m.elements[j].label <=> i < j } + * @throws UnboundLeafException - r !in this.relations + */ + public final BooleanMatrix interpret(Relation r) { + if (!lowers.containsKey(r)) + throw new UnboundLeafException("Unbound relation: ", r); + final IntSet lowerBound = lowers.get(r).indexView(); + final IntSet upperBound = uppers.get(r).indexView(); + + final BooleanMatrix m = factory.matrix(Dimensions.square(universe().size(), r.arity()), upperBound, lowerBound); + + if (upperBound.size() > lowerBound.size()) { + int varId = vars.get(r).min(); + for (IntIterator indeces = upperBound.iterator(); indeces.hasNext();) { + int tupleIndex = indeces.next(); + if (!lowerBound.contains(tupleIndex)) + m.set(tupleIndex, factory.variable(varId++)); + } + } + return m; + } + + /** + * Returns a {@link kodkod.engine.bool.BooleanMatrix matrix} m of + * {@link kodkod.engine.bool.BooleanValue boolean formulas} representing + * the specified constant expression. + * @return { m: BooleanMatrix | let dset = [0..this.universe.size()^c.arity) | + * m.dimensions.dimensions = [0..c.arity) ->one this.universe.size() && + * c = UNIV => m.elements[dset] = TRUE, c = NONE => m.elements[dset] = FALSE, + * c = IDEN => (all i: dset | (some j: int | i = j*(1+this.universe.size())) => m.elements[i] = TRUE, m.elements[i] = FALSE), + * c = INT => (all i: dset | (some j: int | this.interpret(j)=i) => m.elements[i] = TRUE, m.elements[i] = FALSE } + */ + public final BooleanMatrix interpret(ConstantExpression c) { + final int univSize = universe().size(); + if (c==Expression.UNIV) { + final IntSet all = Ints.rangeSet(Ints.range(0, univSize-1)); + return factory().matrix(Dimensions.square(univSize, 1), all, all); + } else if (c==Expression.IDEN) { + final Dimensions dim2 = Dimensions.square(univSize, 2); + final IntSet iden = Ints.bestSet(dim2.capacity()); + for(int i = 0; i < univSize; i++) { + iden.add(i*univSize + i); + } + return factory().matrix(dim2, iden, iden); + } else if (c==Expression.NONE) { + return factory().matrix(Dimensions.square(univSize, 1), Ints.EMPTY_SET, Ints.EMPTY_SET); + } else if (c==Expression.INTS) { + final IntSet ints = Ints.bestSet(univSize); + for(IntIterator iter = ints().iterator(); iter.hasNext(); ) { + ints.add(interpret(iter.next())); + } + return factory().matrix(Dimensions.square(univSize, 1), ints, ints); + } else { + throw new IllegalArgumentException("unknown constant expression: " + c); + } + } + + /** + * Returns the set of all integers corresponding to some + * atom in this.universe. + * @return this.ints + */ + public final IntSet ints() { + return ints.indices(); + } + + /** + * Returns the index of the atom from this.universe which represents the given integer. + * @requires i in this.ints + * @return this.ibounds[i].indexView().min() + */ + public final int interpret(int i) { + return ints.get(i).indexView().min(); + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/fol2sat/MemoryLogger.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/fol2sat/MemoryLogger.java new file mode 100644 index 00000000..99f472b7 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/fol2sat/MemoryLogger.java @@ -0,0 +1,192 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.engine.fol2sat; + +import java.util.Collections; +import java.util.Iterator; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; + +import kodkod.ast.Expression; +import kodkod.ast.Formula; +import kodkod.ast.Node; +import kodkod.ast.Variable; +import kodkod.engine.Solver; +import kodkod.engine.bool.BooleanMatrix; +import kodkod.engine.bool.BooleanValue; +import kodkod.instance.Bounds; +import kodkod.instance.TupleSet; +import kodkod.util.collections.FixedMap; +import kodkod.util.nodes.AnnotatedNode; +import kodkod.util.nodes.Nodes; + +/** + * A translation logger that logs translation events for the {@linkplain Nodes#conjuncts(Formula) conjuncts} of a given formula to memory. + * In other words, this logger only logs the translations for the children of the given formula, if the formula is a conjunction. Otherwise, + * it simply logs the translation for the formula itself. The translation events for the conjuncts' descendants are ignored. + * @specfield originalFormula: Formula // the {@linkplain Solver#solve(Formula, kodkod.instance.Bounds) original} formula, provided by the user + * @specfield originalBounds: Bounds // the {@linkplain Solver#solve(Formula, kodkod.instance.Bounds) original} bounds, provided by the user + * @specfield formula: Formula // desugaring of this.formula that was translated + * @specfield bounds: Bounds // translation bounds + * @specfield records: (formula.*children & Formula) -> BooleanValue -> Environment + * @invariant Solver.solve(formula, bounds).instance() == null iff Solver.solve(originalFormula, originalBounds).instance() == null + * @author Emina Torlak + */ +final class MemoryLogger extends TranslationLogger { + private final FixedMap logMap; + private final AnnotatedNode annotated; + private final Bounds bounds; + + /** + * Constructs a new memory logger from the given annotated formula. + * @ensures this.formula' = annotated.node + * @ensures this.bounds' = bounds + * @ensures no this.records' + * @ensures this.log().roots() = Nodes.conjuncts(annotated) + */ + MemoryLogger(final AnnotatedNode annotated, Bounds bounds) { + this.annotated = annotated; + this.bounds = bounds; + this.logMap = new FixedMap(Nodes.conjuncts(annotated.node())); + } + + /** + * {@inheritDoc} + * @see kodkod.engine.fol2sat.TranslationLogger#close() + */ + @Override + void close() {} + + /** + * Logs the translation of the given formula if and only if f is a root of this.formula. + * @ensures f in Nodes.conjuncts(this.formula) and no this.records[f] => + * this.records' = this.records ++ f -> translation -> env + * @throws IllegalArgumentException - some this.records[f] and this.records[f] != translation -> env + * @see kodkod.engine.fol2sat.TranslationLogger#log(kodkod.ast.Formula, kodkod.engine.bool.BooleanValue, kodkod.engine.fol2sat.Environment) + */ + @Override + void log(Formula f, BooleanValue translation, Environment env) { + if (logMap.containsKey(f)) { + //assert env.isEmpty(); + final BooleanValue old = logMap.put(f, translation); + if (old!=null && old!=translation) + throw new IllegalArgumentException("translation of root corresponding to the formula has already been logged: " + f); + } + } + + /** + * {@inheritDoc} + * @see kodkod.engine.fol2sat.TranslationLogger#log() + */ + @Override + TranslationLog log() { return new MemoryLog(annotated,logMap,bounds); } + + /** + * A memory-based translation log, written by a MemoryLogger. + * @author Emina Torlak + */ + private static class MemoryLog extends TranslationLog { + private final Set roots; + private final Bounds bounds; + private final Node[] original; + private final int[] transl; + + /** + * Constructs a new memory log out of the given node and its corresponding log map. + */ + MemoryLog(AnnotatedNode annotated, FixedMap logMap, Bounds bounds) { + this.bounds = bounds; + this.roots = Nodes.conjuncts(annotated.node()); + assert roots.size() == logMap.size(); + this.transl = new int[roots.size()]; + this.original = new Node[roots.size()]; + final Iterator itr = roots.iterator(); + for(int i = 0; i < transl.length; i++) { + final Formula root = itr.next(); + transl[i] = logMap.get(root).label(); + original[i] = annotated.sourceOf(root); + } + } + + /** + * {@inheritDoc} + * @see kodkod.engine.fol2sat.TranslationLog#bounds() + */ + public Bounds bounds() { return bounds; } + + /** + * {@inheritDoc} + * @see kodkod.engine.fol2sat.TranslationLog#replay(kodkod.engine.fol2sat.RecordFilter) + */ + @Override + public Iterator replay(final RecordFilter filter) { + return new Iterator() { + final Iterator itr = roots.iterator(); + boolean ready = false; + int index = -1; + Formula root = null; + final TranslationRecord current = new TranslationRecord() { + @Override + public Map env() { return Collections.emptyMap(); } + @Override + public int literal() { return transl[index]; } + @Override + public Node node() { return original[index]; } + @Override + public Formula translated() { return root; } + + }; + + @SuppressWarnings("unchecked") + public boolean hasNext() { + while(!ready && itr.hasNext()) { + root = itr.next(); + index++; + if (filter.accept(original[index], root, transl[index], Collections.EMPTY_MAP)) { + ready = true; + break; + } + } + return ready; + } + + public TranslationRecord next() { + if (!hasNext()) throw new NoSuchElementException(); + ready = false; + return current; + } + + public void remove() { throw new UnsupportedOperationException(); } + }; + } + + /** + * {@inheritDoc} + * @see kodkod.engine.fol2sat.TranslationLog#roots() + */ + @Override + public Set roots() { return roots; } + + } + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/fol2sat/NNFConverter.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/fol2sat/NNFConverter.java new file mode 100644 index 00000000..3d1ec809 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/fol2sat/NNFConverter.java @@ -0,0 +1,271 @@ +package kodkod.engine.fol2sat; + +import static kodkod.ast.operator.FormulaOperator.AND; +import static kodkod.ast.operator.FormulaOperator.OR; + +import java.util.ArrayList; +import java.util.IdentityHashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import kodkod.ast.BinaryFormula; +import kodkod.ast.ComparisonFormula; +import kodkod.ast.ConstantFormula; +import kodkod.ast.Formula; +import kodkod.ast.IntComparisonFormula; +import kodkod.ast.IntExpression; +import kodkod.ast.MultiplicityFormula; +import kodkod.ast.NaryFormula; +import kodkod.ast.Node; +import kodkod.ast.NotFormula; +import kodkod.ast.QuantifiedFormula; +import kodkod.ast.RelationPredicate; +import kodkod.ast.operator.FormulaOperator; +import kodkod.ast.visitor.AbstractReplacer; +import kodkod.util.nodes.AnnotatedNode; + +public class NNFConverter extends AbstractReplacer { + + public static AnnotatedNode flatten(AnnotatedNode annotated) { + final NNFConverter flat = new NNFConverter(annotated.sharedNodes()); + Formula f = annotated.node().accept(flat); + final List roots = new ArrayList(flat.annotations.size()); + roots.addAll(flat.annotations.keySet()); + for(Iterator> itr = flat.annotations.entrySet().iterator(); itr.hasNext(); ) { + final Map.Entry entry = itr.next(); + final Node source = annotated.sourceOf(entry.getValue()); + if (entry.getKey()==source) { itr.remove(); /* TODO: what is this for? */ } + else { entry.setValue(source); } + } + return AnnotatedNode.annotate(f, flat.annotations); + } + + private Map annotations; + private boolean negated; + + protected NNFConverter(Set shared) { + this(shared, new LinkedHashMap(), new IdentityHashMap()); + } + + protected NNFConverter(Set shared, Map annotations, Map visited) { + super(shared); + this.annotations = annotations; + this.negated = false; + } + + protected Formula addMapping(Formula f, Node source) { + annotations.put(f, source); + return f; + } + + /** + * Calls nf.formula.accept(this) after flipping the negation flag. + * @see kodkod.ast.visitor.AbstractVoidVisitor#visit(kodkod.ast.NotFormula) + */ + public final Formula visit(NotFormula nf) { + //if (visited(bf)) return; + negated = !negated; + Formula f = nf.formula().accept(this); + negated = !negated; + if (f instanceof NotFormula) { + if (((NotFormula) f).formula() == nf.formula()) { + return addMapping(nf, nf); + } + } + return addMapping(f, nf); + } + + /** + * Visits the formula's children with appropriate settings + * for the negated flag if bf has not been visited before. + * @see kodkod.ast.visitor.AbstractVoidVisitor#visit(kodkod.ast.BinaryFormula) + */ + public final Formula visit(BinaryFormula bf) { + //if (visited(bf)) return; + final FormulaOperator op = bf.op(); + switch (op) { + case AND: + if (!negated) { + // left && right + Formula lf = bf.left().accept(this); + Formula rf = bf.right().accept(this); + if (lf == bf.left() && rf == bf.right()) { + return addMapping(bf, bf); + } else { + return addMapping(lf.and(rf), bf); + } + } else { + // !(left && right) --> !left || !right + Formula lf = bf.left().accept(this); + Formula rf = bf.right().accept(this); + return addMapping(lf.or(rf), bf); + } + case OR: + if (!negated) { + // left || right + Formula lf = bf.left().accept(this); + Formula rf = bf.right().accept(this); + if (lf == bf.left() && rf == bf.right()) { + return addMapping(bf, bf); + } else { + return addMapping(lf.or(rf), bf); + } + } else { + // !(left || right) --> !left && !right + Formula lf = bf.left().accept(this); + Formula rf = bf.right().accept(this); + return addMapping(lf.or(rf), bf); + } + case IMPLIES: + if (!negated) { + // left => right --> !left || right + Formula lf = bf.left().not().accept(this); + Formula rf = bf.right().accept(this); + return addMapping(lf.or(rf), bf); + } else { + // !(left => right) --> left && !right + negated = false; + Formula lf = bf.left().accept(this); + negated = true; + Formula rf = bf.right().accept(this); + return addMapping(lf.and(rf), bf); + } + case IFF: + if (!negated) { + // a = b --> (a && b) || (!a && !b) + Formula lf = bf.left().and(bf.right()).accept(this); + Formula rf = bf.left().not().and(bf.right().not()).accept(this); + return addMapping(lf.or(rf), bf); + } else { + // !(a = b) --> (a && !b) || (!a && b) + negated = false; + Formula lf = bf.left().and(bf.right().not()).accept(this); + Formula rf = bf.left().not().and(bf.right()).accept(this); + negated = true; + return addMapping(lf.or(rf), bf); + } + default: + return addMapping(bf, bf); + } + } + + /** + * Visits the formula's children with appropriate settings + * for the negated flag if bf has not been visited before. + * @see kodkod.ast.visitor.AbstractVoidVisitor#visit(kodkod.ast.NaryFormula) + */ + //TODO: probably don't needed + public final Formula visit(NaryFormula nf) { + //if (visited(nf)) return; + final FormulaOperator op = nf.op(); + if (negated && op==AND) { + List formulas = new LinkedList(); + for (Formula f : nf) { + formulas.add(f.accept(this)); + } + return addMapping(Formula.or(formulas), nf); + } else if (negated && op==OR) { + List formulas = new LinkedList(); + for (Formula f : nf) { + formulas.add(f.accept(this)); + } + return addMapping(Formula.and(formulas), nf); + } else { + List formulas = new LinkedList(); + boolean changed = false; + for (Formula f : nf) { + Formula ff = f.accept(this); + changed = changed || ff != f; + formulas.add(ff); + } + if (changed) { + if (op==AND) + return addMapping(Formula.and(formulas), nf); + else + return addMapping(Formula.or(formulas), nf); + } else { + return addMapping(nf, nf); + } + } + } + + /** @see #visitFormula(Formula) */ + public final Formula visit(IntComparisonFormula cf) { + //if (visited(cf)) return; + IntExpression lh = cf.left().accept(this); + IntExpression rh = cf.right().accept(this); + if (!negated) { + if (lh == cf.left() && rh == cf.right()) { + return addMapping(cf, cf); + } else { + return addMapping(lh.compare(cf.op(), rh), cf); + } + } else { + switch (cf.op()) { + case GT: + return addMapping(lh.lte(rh), cf); + case GTE: + return addMapping(lh.lt(rh), cf); + case LT: + return addMapping(lh.gte(rh), cf); + case LTE: + return addMapping(lh.gt(rh), cf); + case EQ: + return addMapping(lh.neq(rh), cf); + case NEQ: + return addMapping(lh.eq(rh), cf); + default: + return addMapping(cf, cf); + } + } + } + + protected Formula addFormula(Formula f, Node src, boolean negOld) { + negated = negOld; + if (negated) { + return addMapping(f.not(), src); + } else { + return addMapping(f, src); + } + } + + @Override + public Formula visit(ConstantFormula constant) { + boolean negOld = negated; + negated = false; + return addFormula(super.visit(constant), constant, negOld); + } + + @Override + public Formula visit(QuantifiedFormula quantFormula) { + boolean negOld = negated; + negated = false; + return addFormula(super.visit(quantFormula), quantFormula, negOld); + } + + @Override + public Formula visit(ComparisonFormula compFormula) { + boolean negOld = negated; + negated = false; + return addFormula(super.visit(compFormula), compFormula, negOld); + } + + @Override + public Formula visit(MultiplicityFormula multFormula) { + boolean negOld = negated; + negated = false; + return addFormula(super.visit(multFormula), multFormula, negOld); + } + + @Override + public Formula visit(RelationPredicate pred) { + boolean negOld = negated; + negated = false; + return addFormula(super.visit(pred), pred, negOld); + } + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/fol2sat/NestedSet.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/fol2sat/NestedSet.java new file mode 100644 index 00000000..b6ce065b --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/fol2sat/NestedSet.java @@ -0,0 +1,41 @@ +package kodkod.engine.fol2sat; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +/** + * Implements a data structure that contains sets of values at different nesting levels. + * + * @author aleks + */ +@SuppressWarnings("unchecked") +public class NestedSet implements Iterable { + + private final NestedSet parent; + private final Set elems; + + private NestedSet(NestedSet parent) { + this(parent, new HashSet()); + } + + private NestedSet(NestedSet parent, Set elems) { + this.parent = parent; + this.elems = elems; + } + + public NestedSet createNested() { + return new NestedSet(this); + } + + public void add(T elem) { this.elems.add(elem); } + public void addAll(Collectionelems) { this.elems.addAll(elems); } + public NestedSet parent() { return parent; } + @Override public Iterator iterator() { return elems.iterator(); } + + @SuppressWarnings("rawtypes") + private static final NestedSet EMPTY = new NestedSet(null); + public static NestedSet empty() { return (NestedSet) EMPTY; } + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/fol2sat/RecordFilter.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/fol2sat/RecordFilter.java new file mode 100644 index 00000000..81969fb4 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/fol2sat/RecordFilter.java @@ -0,0 +1,35 @@ +package kodkod.engine.fol2sat; + +import java.util.Map; + +import kodkod.ast.Formula; +import kodkod.ast.Node; +import kodkod.ast.Variable; +import kodkod.instance.TupleSet; + +/** + * A filter for TranslationRecords, based on the value of a record's node and literal fields. + **/ +public interface RecordFilter { + /** + * Returns true if the records with the given node, formula derived from the node, literal, and environment + * should be returned by iterators produced by the {@linkplain TranslationLog#replay()} method. + * @return true if the records with the given node, formula derived from the node, literal, and environment + * should be returned by iterators produced by {@linkplain TranslationLog#replay()}. + */ + public abstract boolean accept(Node node, Formula translated, int literal, Map env); + + /** + * A record filter that accepts all records. + */ + public static RecordFilter ALL = new RecordFilter() { + /** + * Returns true. + * @return true + */ + public boolean accept(Node node, Formula translated, int literal, Map env) { + return true; + } + }; + +} \ No newline at end of file diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/fol2sat/Skolemizer.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/fol2sat/Skolemizer.java new file mode 100644 index 00000000..7e3c736e --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/fol2sat/Skolemizer.java @@ -0,0 +1,563 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.engine.fol2sat; + +import static kodkod.ast.operator.FormulaOperator.AND; +import static kodkod.ast.operator.FormulaOperator.IFF; +import static kodkod.ast.operator.FormulaOperator.IMPLIES; +import static kodkod.ast.operator.FormulaOperator.OR; +import static kodkod.ast.operator.Quantifier.ALL; +import static kodkod.ast.operator.Quantifier.SOME; +import static kodkod.util.nodes.AnnotatedNode.annotate; + +import java.util.AbstractList; +import java.util.ArrayList; +import java.util.IdentityHashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import kodkod.ast.BinaryFormula; +import kodkod.ast.ComparisonFormula; +import kodkod.ast.Comprehension; +import kodkod.ast.Decl; +import kodkod.ast.Decls; +import kodkod.ast.Expression; +import kodkod.ast.Formula; +import kodkod.ast.IntComparisonFormula; +import kodkod.ast.IntExpression; +import kodkod.ast.MultiplicityFormula; +import kodkod.ast.NaryFormula; +import kodkod.ast.Node; +import kodkod.ast.NotFormula; +import kodkod.ast.QuantifiedFormula; +import kodkod.ast.Relation; +import kodkod.ast.RelationPredicate; +import kodkod.ast.SumExpression; +import kodkod.ast.Variable; +import kodkod.ast.operator.FormulaOperator; +import kodkod.ast.operator.Multiplicity; +import kodkod.ast.operator.Quantifier; +import kodkod.ast.visitor.AbstractDetector; +import kodkod.ast.visitor.AbstractReplacer; +import kodkod.engine.bool.BooleanMatrix; +import kodkod.engine.config.Options; +import kodkod.engine.config.Reporter; +import kodkod.instance.Bounds; +import kodkod.instance.TupleSet; +import kodkod.util.nodes.AnnotatedNode; + +/** + * Skolemizes existential quantifiers, up to a given + * number of nestings (within universal quantifiers). + * @author Emina Torlak + */ +abstract class Skolemizer extends AbstractReplacer { + + /** + * Skolemizes the given annotated formula using the given bounds and options. If + * Options.trackFormulas is set and the formula is skolemizable, the resulting annotated + * formula will contain transitive source information for each of its subformulas. + * Specifically, let f be the returned annotated formula, t be a descendeant of f.node, and + * s a descendant of annotated.node from which t was derived. Then, + * f.source[t] = annotated.source[s]. If options.trackFormulas is false, no source + * information will be recorded (i.e. f.source[t] = t for all descendants t of f). + * @ensures upper bound mappings for skolem constants, if any, are added to the bounds + * @return the skolemized version of the given formula + * @throws NullPointerException - any of the arguments are null + * @throws IllegalArgumentException - some Relation & annotated.node.^children - bounds.relations + * @throws UnsupportedOperationException - bounds is unmodifiable + */ + static AnnotatedNode skolemize(final AnnotatedNode annotated, Bounds bounds, Options options) { + if (options.logTranslation()>0) { + final Map source = new IdentityHashMap(); + final Skolemizer r = new Skolemizer(annotated, bounds, options) { + protected Formula source(Formula f, Node n) { + //System.out.println("logging " + f + " <-- " + n); + final Node nsource = annotated.sourceOf(n); + if (f!=nsource) source.put(f, nsource); + return f; + } + }; + final Formula f = annotated.node().accept(r); + return f==annotated.node() ? annotated : annotate(f, source); + } else { + final Skolemizer r = new Skolemizer(annotated, bounds, options) {}; + final Formula f = annotated.node().accept(r); + return f==annotated.node() ? annotated : annotate(f); + } + } + + /** + * Contains info about an approximate bound for a + * non-skolemizable decl. + * @specfield decl: Decl + * @specfield upperBound: lone BooleanMatrix + * @invariant decl.expression in upperBound + * @author Emina Torlak + */ + private static final class DeclInfo { + final Decl decl; + BooleanMatrix upperBound; + /** + * Constructs a DeclInfo for the given decl. + * @ensures this.decl' = decl && this.upperBound' = null + */ + DeclInfo(Decl decl) { + this.decl = decl; + this.upperBound = null; + } + } + + /* replacement environment; maps skolemized variables to their skolem expressions, + * and non-skolemized variables to themselves */ + private Environment repEnv; + /* the interpreter used to determine the upper bounds for skolem constants; + * the upper bounds for skolem constants will be added to interpreter.bounds */ + private final LeafInterpreter interpreter; + /* bounds on which the interpreter is based */ + private final Bounds bounds; + /* reporter */ + private final Reporter reporter; + /* non-skolemizable quantified declarations in the current scope, in the order of declaration + * (most recent decl is last in the list) */ + private final List nonSkolems; + /* a Decl-only view of the nonSkolems list */ + private final List nonSkolemsView; + private final List topSkolemConstraints; + /* true if the polarity of the currently visited node is negative, otherwise false */ + private boolean negated; + /* depth to which to skolemize; negative depth indicates that no skolemization can be done at that point */ + private int skolemDepth; + + /** + * Constructs a skolem replacer from the given arguments. + */ + private Skolemizer(AnnotatedNode annotated, Bounds bounds, Options options) { + super(annotated.sharedNodes()); + + // only cache intermediate computations for expressions with no free variables + // and formulas with no free variables and no quantified descendents + final AbstractDetector fvdetect = annotated.freeVariableDetector(); + final AbstractDetector qdetect = annotated.quantifiedFormulaDetector(); + for(Node n: annotated.sharedNodes()) { + if (!(Boolean)n.accept(fvdetect)) { + if (!(n instanceof Formula) || !((Boolean)n.accept(qdetect))) + this.cache.put(n, null); + } + } + this.reporter = options.reporter(); + this.bounds = bounds; + this.interpreter = LeafInterpreter.overapproximating(bounds, options); + this.repEnv = Environment.empty(); + this.nonSkolems = new ArrayList(); + this.nonSkolemsView = new AbstractList() { + public Decl get(int index) { return nonSkolems.get(index).decl; } + public int size() { return nonSkolems.size(); } + }; + this.topSkolemConstraints = new ArrayList(); + this.negated = false; + this.skolemDepth = options.skolemDepth(); + } + + /** + * Caches the given replacement for the specified node, if + * the node is a syntactically shared expression, int expression or declaration with + * no free variables. Otherwise does nothing. The method returns + * the replacement node. + * @return replacement + */ + @Override + protected final N cache(N node, N replacement) { + if (cache.containsKey(node)) { + cache.put(node, replacement); + } + return replacement; + } + + /** + * Records that the given node is the source of the + * specified formula, if this is a tracking skolemizer. Otherwise does nothing. + * This method is always called when the result of visiting a node n will result + * in the creation of a formula f such that f != n. + * @return f + * @ensures Records that the given node is the source of the + * specified formula, if this is a tracking skolemizer. Otherwise does nothing. + */ + protected Formula source(Formula f, Node n) { + return f; + } + + /*-------declarations---------*/ + /** + * Visits the given decl's expression. Note that we must not visit variables + * in case they are re-used. For example, consider the formula + * some x: X | all x: Y | F(x). Since x bound by the existential quantifier + * is going to be skolemized, if we visited the variable in the enclosed + * declaration, we would get the skolem constant as a return value and + * a ClassCastException would be thrown. + * + * @return { d: Declaration | d.variable = decl.variable && d.multiplicity = decl.multiplicity && + * d.expression = decl.expression.accept(this) } + */ + @Override + public final Decl visit(Decl decl) { + Decl ret = lookup(decl); + if (ret!=null) return ret; + final int oldDepth = skolemDepth; + skolemDepth = -1; // can't skolemize inside a decl + final Expression expression = decl.expression().accept(this); + skolemDepth = oldDepth; + ret = (expression==decl.expression()) ? decl : decl.variable().declare(decl.multiplicity(), expression); + return cache(decl,ret); + } + + /** + * This method should be accessed only from the context of a non-skolemizable + * node, because it extends the replacement environment + * with identity mappings for the variables declared in the given decls. To ensure + * that the environment is always extended, the method should be called using the + * visit((Decls) node.declarations()) syntax, since the accept syntax may dynamically + * dispatch the call to the {@link #visit(Decl)} method, producing UnboundLeafExceptions. + * @ensures this.repEnv in this.repEnv'.^parent && + * #(this.repEnv'.*parent - this.repEnv.*parent) = decls.size() && + * all v: decls.variable | this.repEnv'.lookup(v) = v + * @requires this.skolemDepth < 0 + * @return { d: Decls | d.size = decls.size && + * all i: [0..d.size) | d.declarations[i] = decls.declarations[i].accept(this) } + */ + public final Decls visit(Decls decls) { + Decls ret = lookup(decls); + if (ret==null) { + Decls visitedDecls = null; + boolean allSame = true; + for(Decl decl : decls) { + Decls newDecl = visit(decl); + if (newDecl != decl) + allSame = false; + visitedDecls = (visitedDecls==null) ? newDecl : visitedDecls.and(newDecl); + repEnv = repEnv.extend(decl.variable(), decl.expression(), decl.variable()); + } + ret = allSame ? decls : visitedDecls; + return cache(decls, ret); + } else { // just extend the replacement environment + for(Decl decl: decls) { + repEnv = repEnv.extend(decl.variable(), decl.expression(), decl.variable()); + } + return ret; + } + } + + /*-------expressions and intexpressions---------*/ + /* INVARIANT: whenever an expression or intexpression is visited, skolemDepth < 0 */ + /** + * Returns the binding for the given variable in the current replacement environment. + * @return the binding for the given variable in the current replacement environment. + * @throws UnboundLeafException - variable not bound in teh replacement environment. + */ + @Override + public final Expression visit(Variable variable) { + final Expression ret = repEnv.lookup(variable); + if (ret==null) + throw new UnboundLeafException("Unbound variable", variable); + return ret; + } + + /** + * @see kodkod.ast.visitor.AbstractReplacer#visit(kodkod.ast.Comprehension) + */ + @Override + public final Expression visit(Comprehension expr) { + Expression ret = lookup(expr); + if (ret!=null) return ret; + final Environment oldRepEnv = repEnv; // skolemDepth < 0 at this point + final Decls decls = visit((Decls)expr.decls()); + final Formula formula = expr.formula().accept(this); + ret = (decls==expr.decls() && formula==expr.formula()) ? expr : formula.comprehension(decls); + repEnv = oldRepEnv; + return cache(expr,ret); + } + /** + * @see kodkod.ast.visitor.AbstractReplacer#visit(kodkod.ast.SumExpression) + */ + @Override + public final IntExpression visit(SumExpression intExpr) { + IntExpression ret = lookup(intExpr); + if (ret!=null) return ret; + final Environment oldRepEnv = repEnv; // skolemDepth < 0 at this point + final Decls decls = visit((Decls)intExpr.decls()); + final IntExpression expr = intExpr.intExpr().accept(this); + ret = (decls==intExpr.decls() && expr==intExpr.intExpr()) ? intExpr : expr.sum(decls); + repEnv = oldRepEnv; + return cache(intExpr,ret); + } + + /*-------formulas---------*/ + /** + * Returns the least sound upper bound on the value of expr + * @return the least sound upper bound on the value of expr + */ + private final BooleanMatrix upperBound(Expression expr, Environment env) { + return FOL2BoolTranslator.approximate(annotate(expr), interpreter, env); + } + + /** + * Adds a bound for the given skolem relation to + * this.bounds, and returns the expression that should replace skolemDecl.variable in the final formula. + * @requires skolem !in this.bounds.relations + * @requires skolem.arity = nonSkolems.size() + skolemDecl.variable().arity() + * @ensures adds a sound upper bound for the given skolem relation to this.bounds + * @return the expression that should replace skolemDecl.variable in the final formula + */ + private Expression skolemExpr(Decl skolemDecl, Relation skolem) { + final int depth = nonSkolems.size(); + final int arity = depth + skolemDecl.variable().arity(); + + Expression skolemExpr = skolem; + Environment skolemEnv = Environment.empty(); + + for(DeclInfo info : nonSkolems) { + if (info.upperBound==null) { + info.upperBound = upperBound(info.decl.expression(), skolemEnv); + } + skolemEnv = skolemEnv.extend(info.decl.variable(), info.decl.expression(), info.upperBound); + skolemExpr = info.decl.variable().join(skolemExpr); + } + + BooleanMatrix matrixBound = upperBound(skolemDecl.expression(), skolemEnv); + for(int i = depth-1; i >= 0; i--) { + matrixBound = nonSkolems.get(i).upperBound.cross(matrixBound); + } + + final TupleSet skolemBound = bounds.universe().factory().setOf(arity, matrixBound.denseIndices()); + bounds.bound(skolem, skolemBound); + + return skolemExpr; + } + + /** + * Returns a formula that properly constrains the given skolem's domain. + * @requires !nonSkolems.isEmpty() + * @return a formula that properly constrains the given skolem's domain. + */ + private Formula domainConstraint(Decl skolemDecl, Relation skolem) { + final Iterator itr = nonSkolems.iterator(); + Decls rangeDecls = itr.next().decl; + while(itr.hasNext()) { + rangeDecls = rangeDecls.and(itr.next().decl); + } +// System.out.println(skolemDecl.expression()); + Expression skolemDomain = skolem; + for(int i = 0, max = skolemDecl.variable().arity(); i < max; i++) { + skolemDomain = skolemDomain.join(Expression.UNIV); + } + return skolemDomain.in(Formula.TRUE.comprehension(rangeDecls)); + } + + /** + * Skolemizes the given formula, if possible, otherwise returns the result + * of replacing its free variables according to the current replacement environment. + * @see kodkod.ast.visitor.AbstractReplacer#visit(kodkod.ast.QuantifiedFormula) + */ + public final Formula visit(QuantifiedFormula qf) { + Formula ret = lookup(qf); + if (ret!=null) return ret; + + final Environment oldRepEnv = repEnv; + final Quantifier quant = qf.quantifier(); + final Decls decls = qf.decls(); + + if (skolemDepth>=0 && (negated && quant==ALL || !negated && quant==SOME)) { // skolemizable formula + final List rangeConstraints = new LinkedList(); + final List domConstraints = new LinkedList(); + + for(Decl decl : decls) { + final Decl skolemDecl = visit(decl); + + final Relation skolem = Relation.nary("$"+ skolemDecl.variable().name(), nonSkolems.size() + skolemDecl.variable().arity()); + reporter.skolemizing(decl, skolem, nonSkolemsView); + + final Expression skolemExpr = skolemExpr(skolemDecl, skolem); + + final Multiplicity mult = decl.multiplicity(); + rangeConstraints.add(source(skolemExpr.in(skolemDecl.expression()), decl)); + if (mult!=Multiplicity.SET) { + rangeConstraints.add(source(skolemExpr.apply(mult), decl)); + } + + if (!nonSkolems.isEmpty()) + domConstraints.add(source(domainConstraint(skolemDecl, skolem), decl)); + + repEnv = repEnv.extend(decl.variable(), decl.expression(), skolemExpr); + } + + ret = source(Formula.and(rangeConstraints), decls).compose(negated ? IMPLIES : AND, qf.formula().accept(this)); + + if (!domConstraints.isEmpty()) + topSkolemConstraints.add(source(Formula.and(domConstraints), decls)); + + } else { // non-skolemizable formula + + final Decls newDecls = visit((Decls)qf.decls()); + if (skolemDepth>=nonSkolems.size()+newDecls.size()) { // could skolemize below + for(Decl d: newDecls) { nonSkolems.add(new DeclInfo(d)); } + final Formula formula = qf.formula().accept(this); + ret = ((newDecls==decls && formula==qf.formula()) ? qf : formula.quantify(quant, newDecls)); + for(int i = newDecls.size(); i > 0; i--) { nonSkolems.remove(nonSkolems.size()-1); } + } else { // can't skolemize below + final int oldDepth = skolemDepth; + skolemDepth = -1; + final Formula formula = qf.formula().accept(this); + ret = ((newDecls==decls && formula==qf.formula()) ? qf : formula.quantify(quant, newDecls)); + skolemDepth = oldDepth; + } + } + + repEnv = oldRepEnv; + if (repEnv.isEmpty() && !topSkolemConstraints.isEmpty()) { + ret = source(Formula.and(topSkolemConstraints), qf).compose(negated ? IMPLIES : AND, ret); + } + return source(cache(qf,ret), qf); + } + + /** + * Calls not.formula.accept(this) after flipping the negation flag and returns the result. + * @see kodkod.ast.visitor.AbstractReplacer#visit(kodkod.ast.NotFormula) + **/ + public final Formula visit(NotFormula not) { + Formula ret = lookup(not); + if (ret!=null) return ret; + negated = !negated; // flip the negation flag + final Formula retChild = not.formula().accept(this); + negated = !negated; + return retChild==not.formula() ? cache(not,not) : source(cache(not, retChild.not()), not); + } + + /** + * If not cached, visits the formula's children with appropriate settings + * for the negated flag and the skolemDepth parameter. + * @see kodkod.ast.visitor.AbstractReplacer#visit(kodkod.ast.BinaryFormula) + */ + public final Formula visit(BinaryFormula bf) { + Formula ret = lookup(bf); + if (ret!=null) return ret; + final FormulaOperator op = bf.op(); + final int oldDepth = skolemDepth; + if (op==IFF || (negated && op==AND) || (!negated && (op==OR || op==IMPLIES))) { // cannot skolemize in these cases + skolemDepth = -1; + } + final Formula left, right; + if (negated && op==IMPLIES) { // !(a => b) = !(!a || b) = a && !b + negated = !negated; + left = bf.left().accept(this); + negated = !negated; + right = bf.right().accept(this); + } else { + left = bf.left().accept(this); + right = bf.right().accept(this); + } + skolemDepth = oldDepth; + ret = (left==bf.left()&&right==bf.right()) ? bf : left.compose(op, right); + return source(cache(bf,ret),bf); + } + + /** + * If not cached, visits the formula's children with appropriate settings + * for the negated flag and the skolemDepth parameter. + * @see kodkod.ast.visitor.AbstractReplacer#visit(kodkod.ast.NaryFormula) + */ + public final Formula visit(NaryFormula bf) { + Formula ret = lookup(bf); + if (ret!=null) return ret; + + final int oldDepth = skolemDepth; + final FormulaOperator op = bf.op(); + + switch(op) { + case AND : if (negated) skolemDepth = -1; break; + case OR : if (!negated) skolemDepth = -1; break; + default : throw new IllegalArgumentException("Unknown nary operator: " + op); + } + + final Formula[] visited = new Formula[bf.size()]; + boolean allSame = true; + for(int i = 0; i < visited.length; i++) { + final Formula child = bf.child(i); + visited[i] = child.accept(this); + allSame = allSame && (child==visited[i]); + } + ret = allSame ? bf : Formula.compose(op, visited); + + skolemDepth = oldDepth; + + return source(cache(bf,ret),bf); + } + + /** + * Calls super.visit(icf) after disabling skolemization and returns the result. + * @return super.visit(icf) + **/ + public final Formula visit(IntComparisonFormula icf) { + final int oldDepth = skolemDepth; + skolemDepth = -1; // cannot skolemize inside an int comparison formula + final Formula ret = super.visit(icf); + skolemDepth = oldDepth; + return source(ret,icf); + } + + /** + * Calls super.visit(cf) after disabling skolemization and returns the result. + * @return super.visit(cf) + **/ + public final Formula visit(ComparisonFormula cf) { + final int oldDepth = skolemDepth; + skolemDepth = -1; // cannot skolemize inside a comparison formula + final Formula ret = super.visit(cf); + skolemDepth = oldDepth; + return source(ret,cf); + } + + /** + * Calls super.visit(mf) after disabling skolemization and returns the result. + * @return super.visit(mf) + **/ + public final Formula visit(MultiplicityFormula mf) { + final int oldDepth = skolemDepth; + skolemDepth = -1; // cannot skolemize inside a multiplicity formula + final Formula ret = super.visit(mf); + skolemDepth = oldDepth; + return source(ret,mf); + } + + /** + * Calls super.visit(pred) after disabling skolemization and returns the result. + * @return super.visit(pred) + **/ + public final Formula visit(RelationPredicate pred) { + final int oldDepth = skolemDepth; + skolemDepth = -1; // cannot skolemize inside a relation predicate + final Formula ret = super.visit(pred); + skolemDepth = oldDepth; + return source(ret,pred); + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/fol2sat/SymmetryBreaker.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/fol2sat/SymmetryBreaker.java new file mode 100644 index 00000000..c5ac60dd --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/fol2sat/SymmetryBreaker.java @@ -0,0 +1,489 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.engine.fol2sat; + +import static kodkod.ast.RelationPredicate.Name.ACYCLIC; +import static kodkod.ast.RelationPredicate.Name.TOTAL_ORDERING; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.IdentityHashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import kodkod.ast.Formula; +import kodkod.ast.Relation; +import kodkod.ast.RelationPredicate; +import kodkod.ast.RelationPredicate.Name; +import kodkod.engine.bool.BooleanAccumulator; +import kodkod.engine.bool.BooleanConstant; +import kodkod.engine.bool.BooleanFactory; +import kodkod.engine.bool.BooleanMatrix; +import kodkod.engine.bool.BooleanValue; +import kodkod.engine.bool.Operator; +import kodkod.engine.config.Reporter; +import kodkod.instance.Bounds; +import kodkod.instance.TupleFactory; +import kodkod.util.ints.IndexedEntry; +import kodkod.util.ints.IntIterator; +import kodkod.util.ints.IntSet; +import kodkod.util.ints.Ints; + +/** + * Breaks symmetries for a given problem. Symmetries + * are broken for total orders, acyclic relations, and + * via a generic lex-leader predicate. + * + * @specfield bounds: Bounds // problem bounds + * @specfield symmetries: set IntSet + * @specfield broken: set RelationPredicate + * @author Emina Torlak + */ +final class SymmetryBreaker { + private final Bounds bounds; + private final Set symmetries; + private final int usize; + + /** + * Constructs a new symmetry breaker for the given Bounds. + * Note that the constructor does not make a local copy of the given + * bounds, so the caller must ensure that all modifications of the + * given bounds are symmetry preserving. + * @ensures this.bounds' = bounds && this.symmetries' = SymmetryDetector.partition(bounds) && + * no this.broken' + **/ + SymmetryBreaker(Bounds bounds, Reporter reporter) { + this.bounds = bounds; + this.usize = bounds.universe().size(); + reporter.detectingSymmetries(bounds); + this.symmetries = SymmetryDetector.partition(bounds); + reporter.detectedSymmetries(symmetries); +// System.out.println(symmetries); + } + + /** + * Breaks matrix symmetries on the relations in this.bounds that are constrained by + * the total ordering and acyclic predicates, drawn from preds.values(), that make up the + * keyset of the returned map. After this method returns, the following constraint holds. + * Let m be the map returned by the method, and m.keySet() be the subset of preds.values() + * used for symmetry breaking. Then, if we let [[b]] denote the set of constraints + * specified by a Bounds object b, the formulas "p and [[this.bounds]]" and "m.get(p) and [[this.bounds']]" + * are equisatisfiable for all p in m.keySet(). + * + *

The value of the "aggressive" flag determines how the symmetries are broken. In particular, if + * the aggressive flag is true, then the symmetries are broken efficiently, at the cost of + * losing the information needed to determine whether a predicate in m.keySet() belongs to an unsatisfiable + * core or not. If the aggressive flag is false, then a less efficient algorithm is applied, which preserves + * the information necessary for unsatisfiable core extraction.

+ * + *

The aggressive symmetry breaking algorithm works as follows. Let t1...tn and c1...ck + * be the total ordering and acyclic predicates in m.keySet(). For each t in {t1...tn}, this.bounds + * is modified so that the bounds for t.first, t.last, t.ordered and t.relation are the following constants: + * t.first is the first atom in the upper bound of t.ordered, t.last is the last atom in the upper bound of t.ordered, t.ordered's + * lower bound is changed to be equal to its upper bound, and t.relation imposes a total ordering on t.ordered + * that corresponds to the ordering of the atoms in this.bounds.universe. Then, m is updated with a binding + * from t to the constant formula TRUE. For each c in {c1...ck}, + * this.bounds is modified so that the upper bound for c.relation is a tupleset whose equivalent matrix + * has FALSE in the entries on or below the main diagonal. Then, m is updated with a binding from c to the + * constant formula TRUE.

+ * + *

The lossless symmetry breaking algorithm works as follows. Let t1...tn and c1...ck be the total ordering + * and acyclic predicates in m.keySet(). For each t in {t1...tn}, three fresh relations are added to this.bounds-- + * t_first, t_last, t_ordered, and t_order--and constrained as follows: t_first is the first atom + * in the upper bound of t.ordered, t_last is the last atom in the upper bound of t.ordered, t_ordered is + * the upper bound of t.ordered, and t_order imposes a total ordering on t.ordered that corresponds to the + * ordering of the atoms in this.bounds.universe. Then, m is updated with a binding from t to the formula + * "t.first = t_first and t.last = t_last and t.ordered = t_ordered and t.relation = t.order." + * For each c in {c1...ck}, a fresh relation c_acyclic is added to this.bounds and constrained to be a constant + * whose equivalent matrix has no entries on or below the main diagonal. The map m is then + * updated with a binding from c to the constraint "c in c_acyclic".

+ * + * @ensures this.bounds' is modified as described above + * @ensures this.symmetries' is modified to no longer contain the partitions that made up the bounds of + * the relations on which symmetries have been broken + * @return a map m such that m.keySet() in preds.values(), and for all predicates p in m.keySet(), the formulas + * "p and [[this.bounds]]" and "m.get(p) and [[this.bounds']]" are equisatisfiable + */ + Map breakMatrixSymmetries(Map> preds, boolean aggressive) { + final Set totals = preds.get(TOTAL_ORDERING); + final Set acyclics = preds.get(ACYCLIC); + final Map broken = new IdentityHashMap(); + + for(RelationPredicate.TotalOrdering pred : sort(totals.toArray(new RelationPredicate.TotalOrdering[totals.size()]))) { + Formula replacement = breakTotalOrder(pred,aggressive); + if (replacement!=null) + broken.put(pred, replacement); + } + + for(RelationPredicate.Acyclic pred : sort(acyclics.toArray(new RelationPredicate.Acyclic[acyclics.size()]))) { + Formula replacement = breakAcyclic(pred,aggressive); + if (replacement!=null) + broken.put(pred, replacement); + } + + return broken; + } + + /** + * Generates a lex leader symmetry breaking predicate for this.symmetries + * (if any), using the specified leaf interpreter and the specified predicate length. + * @requires interpreter.relations in this.bounds.relations + * @return a symmetry breaking predicate for this.symmetries + */ + final BooleanValue generateSBP(LeafInterpreter interpreter, int predLength) { + if (symmetries.isEmpty() || predLength==0) return BooleanConstant.TRUE; + + final List relParts = relParts(); + final BooleanFactory factory = interpreter.factory(); + final BooleanAccumulator sbp = BooleanAccumulator.treeGate(Operator.AND); + final List original = new ArrayList(predLength); + final List permuted = new ArrayList(predLength); + + for(IntSet sym : symmetries) { + + IntIterator indeces = sym.iterator(); + for(int prevIndex = indeces.next(); indeces.hasNext(); ) { + int curIndex = indeces.next(); + for(Iterator rIter = relParts.iterator(); rIter.hasNext() && original.size() < predLength;) { + + RelationParts rparts = rIter.next(); + Relation r = rparts.relation; + + if (!rparts.representatives.contains(sym.min())) continue; // r does not range over sym + + BooleanMatrix m = interpreter.interpret(r); + for(IndexedEntry entry : m) { + int permIndex = permutation(r.arity(), entry.index(), prevIndex, curIndex); + BooleanValue permValue = m.get(permIndex); + if (permIndex==entry.index() || atSameIndex(original, permValue, permuted, entry.value())) + continue; + + original.add(entry.value()); + permuted.add(permValue); + } + } + + sbp.add(leq(factory, original, permuted)); + original.clear(); + permuted.clear(); + prevIndex = curIndex; + } + } + + return factory.accumulate(sbp); + } + + /** + * Returns a list of RelationParts that map each non-constant r in this.bounds.relations to + * the representatives of the sets from this.symmetries contained in the upper bound of r. + * The entries are sorted by relations' arities and names. + * @return a list of RelationParts that contains an entry for each non-constant r in this.bounds.relations and + * the representatives of sets from this.symmetries contained in the upper bound of r. + */ + private List relParts() { + final List relParts = new ArrayList(bounds.relations().size()); + for(Relation r: bounds.relations()) { + IntSet upper = bounds.upperBound(r).indexView(); + if (upper.size()==bounds.lowerBound(r).size()) continue; // skip constant relation + IntSet reps = Ints.bestSet(usize); + for(IntIterator tuples = upper.iterator(); tuples.hasNext(); ) { + for(int tIndex = tuples.next(), i = r.arity(); i > 0; i--, tIndex /= usize) { + for(IntSet symm : symmetries) { + if (symm.contains(tIndex%usize)) { + reps.add(symm.min()); + break; + } + } + } + } + relParts.add(new RelationParts(r, reps)); + } + final Comparator cmp = new Comparator() { + public int compare(RelationParts o1, RelationParts o2) { + final int acmp = o1.relation.arity() - o2.relation.arity(); + return acmp!=0 ? acmp : String.valueOf(o1.relation.name()).compareTo(String.valueOf(o2.relation.name())); + } + }; + Collections.sort(relParts, cmp); + return relParts; + } + + /** + * Returns a BooleanValue that is true iff the string of bits + * represented by l0 is lexicographically less than or equal + * to the string of bits reprented by l1. + * @requires l0.size()==l1.size() + * @return a circuit that compares l0 and l1 + */ + private static final BooleanValue leq(BooleanFactory f, List l0, List l1) { + final BooleanAccumulator cmp = BooleanAccumulator.treeGate(Operator.AND); + BooleanValue prevEquals = BooleanConstant.TRUE; + for(int i = 0; i < l0.size(); i++) { + cmp.add(f.implies(prevEquals, f.implies(l0.get(i), l1.get(i)))); + prevEquals = f.and(prevEquals, f.iff(l0.get(i), l1.get(i))); + } + return f.accumulate(cmp); + } + + /** + * Let t be the tuple represent by the given arity and tupleIndex. + * This method returns the tuple index of the tuple t' such t' + * is equal to t with each occurence of atomIndex0 + * replaced by atomIndex1 and vice versa. + * @return the index of the tuple to which the given symmetry + * maps the tuple specified by arith and tupleIndex + */ + private final int permutation(int arity, int tupleIndex, int atomIndex0, int atomIndex1) { + int permIndex = 0; + for(int u = 1; arity > 0; arity--, tupleIndex /= usize, u *= usize ) { + int atomIndex = tupleIndex%usize; + if (atomIndex==atomIndex0) + permIndex += atomIndex1 * u; + else if (atomIndex==atomIndex1) { + permIndex += atomIndex0 * u; + } else { + permIndex += atomIndex * u; + } + } + return permIndex; + } + + /** + * Returns true if there is some index i such that l0[i] = v0 and l1[i] = v1. + * @requires l0.size()=l1.size() + * @return some i: int | l0[i] = v0 && l1[i] = v1 + */ + private static boolean atSameIndex(List l0, BooleanValue v0, List l1, BooleanValue v1) { + for(int i = 0; i < l0.size(); i++) { + if (l0.get(i).equals(v0) && l1.get(i).equals(v1)) + return true; + } + return false; + } + + /** + * Sorts the predicates in the given array in the ascending order of + * the names of the predicates' relations, and returns it. + * @return broken' + * @ensures all i: [0..preds.size()) | all j: [0..i) | + * broken[j].relation.name <= broken[i].relation.name + */ + private static final

P[] sort(final P[] preds) { + final Comparator cmp = new Comparator() { + public int compare(RelationPredicate o1, RelationPredicate o2) { + return String.valueOf(o1.relation().name()).compareTo(String.valueOf(o2.relation().name())); + } + }; + Arrays.sort(preds, cmp); + return preds; + } + + /** + * If possible, breaks symmetry on the given acyclic predicate and returns a formula + * f such that the meaning of acyclic with respect to this.bounds is equivalent to the + * meaning of f with respect to this.bounds'. If symmetry cannot be broken on the given predicate, returns null. + * + *

We break symmetry on the relation constrained by the given predicate iff + * this.bounds.upperBound[acyclic.relation] is the cross product of some partition in this.symmetries with + * itself. Assuming that this is the case, we then break symmetry on acyclic.relation using one of the methods + * described in {@linkplain #breakMatrixSymmetries(Map, boolean)}; the method used depends + * on the value of the "agressive" flag. + * The partition that formed the upper bound of acylic.relation is removed from this.symmetries.

+ * + * @return null if symmetry cannot be broken on acyclic; otherwise returns a formula + * f such that the meaning of acyclic with respect to this.bounds is equivalent to the + * meaning of f with respect to this.bounds' + * @ensures this.symmetries and this.bounds are modified as described in {@linkplain #breakMatrixSymmetries(Map, boolean)} iff this.bounds.upperBound[acyclic.relation] is the + * cross product of some partition in this.symmetries with itself + * + * @see #breakMatrixSymmetries(Map,boolean) + */ + private final Formula breakAcyclic(RelationPredicate.Acyclic acyclic, boolean aggressive) { + final IntSet[] colParts = symmetricColumnPartitions(acyclic.relation()); + if (colParts!=null) { + final Relation relation = acyclic.relation(); + final IntSet upper = bounds.upperBound(relation).indexView(); + final IntSet reduced = Ints.bestSet(usize*usize); + for(IntIterator tuples = upper.iterator(); tuples.hasNext(); ) { + int tuple = tuples.next(); + int mirror = (tuple / usize) + (tuple % usize)*usize; + if (tuple != mirror) { + if (!upper.contains(mirror)) return null; + if (!reduced.contains(mirror)) + reduced.add(tuple); + } + } + + // remove the partition from the set of symmetric partitions + removePartition(colParts[0].min()); + + if (aggressive) { + bounds.bound(relation, bounds.universe().factory().setOf(2, reduced)); + return Formula.TRUE; + } else { + final Relation acyclicConst = Relation.binary("SYM_BREAK_CONST_"+acyclic.relation().name()); + bounds.boundExactly(acyclicConst, bounds.universe().factory().setOf(2, reduced)); + return relation.in(acyclicConst); + } + } + return null; + } + + /** + * If possible, breaks symmetry on the given total ordering predicate and returns a formula + * f such that the meaning of total with respect to this.bounds is equivalent to the + * meaning of f with respect to this.bounds'. If symmetry cannot be broken on the given predicate, returns null. + * + *

We break symmetry on the relation constrained by the given predicate iff + * total.first, total.last, and total.ordered have the same upper bound, which, when + * cross-multiplied with itself gives the upper bound of total.relation. Assuming that this is the case, + * we then break symmetry on total.relation, total.first, total.last, and total.ordered using one of the methods + * described in {@linkplain #breakMatrixSymmetries(Map, boolean)}; the method used depends + * on the value of the "agressive" flag. + * The partition that formed the upper bound of total.ordered is removed from this.symmetries.

+ * + * @return null if symmetry cannot be broken on total; otherwise returns a formula + * f such that the meaning of total with respect to this.bounds is equivalent to the + * meaning of f with respect to this.bounds' + * @ensures this.symmetries and this.bounds are modified as desribed in {@linkplain #breakMatrixSymmetries(Map, boolean)} + * iff total.first, total.last, and total.ordered have the same upper bound, which, when + * cross-multiplied with itself gives the upper bound of total.relation + * + * @see #breakMatrixSymmetries(Map,boolean) + */ + private final Formula breakTotalOrder(RelationPredicate.TotalOrdering total, boolean aggressive) { + final Relation first = total.first(), last = total.last(), ordered = total.ordered(), relation = total.relation(); + final IntSet domain = bounds.upperBound(ordered).indexView(); + + if (symmetricColumnPartitions(ordered)!=null && + bounds.upperBound(first).indexView().contains(domain.min()) && + bounds.upperBound(last).indexView().contains(domain.max())) { + + // construct the natural ordering that corresponds to the ordering of the atoms in the universe + final IntSet ordering = Ints.bestSet(usize*usize); + int prev = domain.min(); + for(IntIterator atoms = domain.iterator(prev+1, usize); atoms.hasNext(); ) { + int next = atoms.next(); + ordering.add(prev*usize + next); + prev = next; + } + + if (ordering.containsAll(bounds.lowerBound(relation).indexView()) && + bounds.upperBound(relation).indexView().containsAll(ordering)) { + + // remove the ordered partition from the set of symmetric partitions + removePartition(domain.min()); + + final TupleFactory f = bounds.universe().factory(); + + if (aggressive) { + bounds.boundExactly(first, f.setOf(f.tuple(1, domain.min()))); + bounds.boundExactly(last, f.setOf(f.tuple(1, domain.max()))); + bounds.boundExactly(ordered, bounds.upperBound(total.ordered())); + bounds.boundExactly(relation, f.setOf(2, ordering)); + + return Formula.TRUE; + + } else { + final Relation firstConst = Relation.unary("SYM_BREAK_CONST_"+first.name()); + final Relation lastConst = Relation.unary("SYM_BREAK_CONST_"+last.name()); + final Relation ordConst = Relation.unary("SYM_BREAK_CONST_"+ordered.name()); + final Relation relConst = Relation.binary("SYM_BREAK_CONST_"+relation.name()); + bounds.boundExactly(firstConst, f.setOf(f.tuple(1, domain.min()))); + bounds.boundExactly(lastConst, f.setOf(f.tuple(1, domain.max()))); + bounds.boundExactly(ordConst, bounds.upperBound(total.ordered())); + bounds.boundExactly(relConst, f.setOf(2, ordering)); + + return Formula.and(first.eq(firstConst), last.eq(lastConst), ordered.eq(ordConst), relation.eq(relConst)); +// return first.eq(firstConst).and(last.eq(lastConst)).and( ordered.eq(ordConst)).and( relation.eq(relConst)); + } + + } + } + + return null; + } + + /** + * Removes from this.symmetries the partition that contains the specified atom. + * @ensures this.symmetries' = { s: this.symmetries | !s.contains(atom) } + */ + private final void removePartition(int atom) { + for(Iterator symIter = symmetries.iterator(); symIter.hasNext(); ) { + if (symIter.next().contains(atom)) { + symIter.remove(); + break; + } + } + } + + /** + * If all columns of the upper bound of r are symmetric partitions, + * those partitions are returned. Otherwise null is returned. + * @return (all i: [0..r.arity) | some s: symmetries[int] | + * bounds.upperBound[r].project(i).indexView() = s) => + * {colParts: [0..r.arity)->IntSet | + * all i: [0..r.arity()) | colParts[i] = bounds.upperBound[r].project(i).indexView() }, + * null + */ + private final IntSet[] symmetricColumnPartitions(Relation r) { + final IntSet upper = bounds.upperBound(r).indexView(); + if (upper.isEmpty()) return null; + + final IntSet[] colParts = new IntSet[r.arity()]; + for(int i = r.arity()-1, min = upper.min(); i >= 0; i--, min /= usize) { + for(IntSet part : symmetries) { + if (part.contains(min%usize)) { + colParts[i] = part; + break; + } + } + if (colParts[i]==null) + return null; + } + for(IntIterator tuples = upper.iterator(); tuples.hasNext(); ) { + for(int i = r.arity()-1, tuple = tuples.next(); i >= 0; i--, tuple /= usize) { + if (!colParts[i].contains(tuple%usize)) + return null; + } + } + return colParts; + } + + /** + * An entry for a relation and the representative (least atom) for each + * symmetry class in the relation's upper bound. + */ + private static final class RelationParts { + final Relation relation; + final IntSet representatives; + + RelationParts(Relation relation, IntSet representatives) { + this.relation = relation; + this.representatives = representatives; + } + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/fol2sat/SymmetryDetector.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/fol2sat/SymmetryDetector.java new file mode 100644 index 00000000..5658d650 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/fol2sat/SymmetryDetector.java @@ -0,0 +1,244 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.engine.fol2sat; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; +import java.util.Set; + +import kodkod.ast.Relation; +import kodkod.instance.Bounds; +import kodkod.instance.TupleSet; +import kodkod.util.ints.IntIterator; +import kodkod.util.ints.IntSet; +import kodkod.util.ints.Ints; + +/** + * Partitions a universe into equivalence classes based + * on the bounding constraints given by a Bounds object. + * @specfield bounds: Bounds // bounds on which the partitioning is based + * @author Emina Torlak + */ +final class SymmetryDetector { + private final Bounds bounds; + /* invariant: representatives always holds a sequence of IntSets that partition bounds.universe */ + private final List parts; + private final int usize; + + /** + * Constructs a new SymmetryDetector for the given bounds. + * @ensures this.bounds' = bounds + */ + private SymmetryDetector(Bounds bounds) { + this.bounds = bounds; + this.usize = bounds.universe().size(); + + // start with the maximum partition -- the whole universe. + this.parts = new LinkedList(); + final IntSet set = Ints.bestSet(usize); + for(int i = 0; i < usize; i++) { set.add(i); } + this.parts.add(set); + } + + /** + * Returns a sound partitioning of bounds.universe + * into symmetry classes. Each intset in the returned + * collection represents the indices of the atoms in + * this.bounds.universe that belong to the same equivalence class. + * @return a sound partitioning of bounds.universe + * into symmetry classes + */ + static Set partition(Bounds bounds) { + final SymmetryDetector detector = new SymmetryDetector(bounds); + detector.computePartitions(); + final Set parts = new LinkedHashSet(detector.parts); + assert parts.size()==detector.parts.size(); // sanity check + return parts; + } + + + /** + * Partitions this.bounds.universe into sets of equivalent atoms. + * @ensures all disj s, q: this.parts'[int] | + * some s.ints && some q.ints && (no s.ints & q.ints) && + * this.parts'[int].ints = [0..this.bounds.universe.size()) && + * (all ts: this.bounds.lowerBound[Relation] + this.bounds.upperBound[Relation] | + * all s: this.parts'[int] | all a1, a2: this.bounds.universe.atoms[s.ints] | + * all t1, t2: ts.tuples | t1.atoms[0] = a1 && t2.atoms[0] = a2 => + * t1.atoms[1..ts.arity) = t1.atoms[1..ts.arity) || + * t1.atoms[1..ts.arity) = a1 && t1.atoms[1..ts.arity) = a2) + */ + private final void computePartitions() { + if (usize==1) return; // nothing more to do + + final Map range2domain = new HashMap((usize*2) / 3); + + // refine the partitions based on the bounds for each integer + for(IntIterator iter = bounds.ints().iterator(); iter.hasNext();) { + TupleSet exact = bounds.exactBound(iter.next()); + refinePartitions(exact.indexView(), 1, range2domain); + } + + // refine the partitions based on the upper/lower bounds for each relation + for(TupleSet s : sort(bounds)) { + if (parts.size()==usize) return; + refinePartitions(s.indexView(), s.arity(), range2domain); + } + + } + + /** + * Returns an array that contains unique non-empty tuplesets in the given bounds, + * sorted in the order of increasing size. + * @return unique non-empty tuplesets in the given bounds, + * sorted in the order of increasing size. + */ + private static TupleSet[] sort(Bounds bounds) { + final List sets = new ArrayList(bounds.relations().size()); + for(Relation r : bounds.relations()) { + final TupleSet lower = bounds.lowerBound(r); + final TupleSet upper = bounds.upperBound(r); + if (!lower.isEmpty() && lower.size()(){ + public int compare(TupleSet o1, TupleSet o2) { + return o1.size() - o2.size(); + } + }); + return sorted; + } + + /** + * Refines the atomic partitions in this.parts based on the contents of the given tupleset, + * decomposed into its constituent IntSet and arity. The range2domain map is used for + * intermediate computations for efficiency (to avoid allocating it in each recursive call). + * @requires all disj s, q: this.parts[int] | + * some s.ints && some q.ints && (no s.ints & q.ints) && + * this.parts[int].ints = [0..this.bounds.universe.size()) + * @ensures let usize = this.bounds.universe.size(), firstColFactor = usize^(arit-1) | + * all disj s, q: this.parts'[int] | + * some s.ints && some q.ints && (no s.ints & q.ints) && + * this.parts'[int].ints = [0..usize) && + * all s: this.parts'[int] | all a1, a2: this.bounds.universe.atoms[s.ints] | + * all t1, t2: set.ints | t1 / firstColFactor = a1 && t2 / firstColFactor = a2 => + * t1 % firstColFactor = t2 % firstColFactor || + * t1 = a1*((1 - firstColFactor) / (1 - usize)) && + * t2 = a2*((1 - firstColFactor) / (1 - usize))) + */ + private void refinePartitions(IntSet set, int arity, Map range2domain) { + if (arity==1) { + refinePartitions(set); + return; + } + + final List otherColumns = new LinkedList(); + int firstColFactor = (int) StrictMath.pow(usize, arity-1); + IntSet firstCol = Ints.bestSet(usize); + for(IntIterator rbIter = set.iterator(); rbIter.hasNext(); ) { + firstCol.add(rbIter.next() / firstColFactor); + } + refinePartitions(firstCol); + + int idenFactor = (1 - firstColFactor) / (1 - usize); + for(ListIterator partsIter = parts.listIterator(); partsIter.hasNext(); ) { + IntSet part = partsIter.next(); + if (firstCol.contains(part.min())) { // contains one, contains them all + range2domain.clear(); + for(IntIterator atoms = part.iterator(); atoms.hasNext(); ) { + int atom = atoms.next(); + IntSet atomRange = Ints.bestSet(firstColFactor); + for(IntIterator rbIter = set.iterator(atom*firstColFactor, (atom+1)*firstColFactor - 1); + rbIter.hasNext(); ) { + atomRange.add(rbIter.next() % firstColFactor); + } + IntSet atomDomain = range2domain.get(atomRange); + if (atomDomain != null) atomDomain.add(atom); + else range2domain.put(atomRange, oneOf(usize, atom)); + } + partsIter.remove(); + IntSet idenPartition = Ints.bestSet(usize); + for(Map.Entry entry : range2domain.entrySet()) { + if (entry.getValue().size()==1 && entry.getKey().size()==1 && + entry.getKey().min() == entry.getValue().min() * idenFactor) { + idenPartition.add(entry.getValue().min()); + } else { + partsIter.add(entry.getValue()); + otherColumns.add(entry.getKey()); + } + } + if (!idenPartition.isEmpty()) + partsIter.add(idenPartition); + } + } + + // refine based on the remaining columns + for(IntSet otherCol : otherColumns) { + refinePartitions(otherCol, arity-1, range2domain); + } + } + + /** + * Refines the atomic partitions this.parts based on the contents of the given set. + * @requires all disj s, q: this.parts[int] | + * some s.ints && some q.ints && (no s.ints & q.ints) && + * this.parts[int].ints = [0..this.bounds.universe.size()) + * @ensures all disj s, q: this.parts'[int] | + * some s.ints && some q.ints && (no s.ints & q.ints) && + * this.parts'[int].ints = [0..this.bounds.universe.size()) && + * (all i: [0..this.parts'.size()) | + * this.parts'[i].ints in set.ints || no this.parts'[i].ints & set.ints) + */ + private void refinePartitions(IntSet set) { + for(ListIterator partsIter = parts.listIterator(); partsIter.hasNext(); ) { + IntSet part = partsIter.next(); + IntSet intersection = Ints.bestSet(part.min(), part.max()); + intersection.addAll(part); + intersection.retainAll(set); + if (!intersection.isEmpty() && intersection.size() < part.size()) { + part.removeAll(intersection); + partsIter.add(intersection); + } + } + } + + /** + * Returns an IntSet that can store elements + * in the range [0..size), and that holds + * the given number. + * @requries 0 <= num < size + * @return {s: IntSet | s.ints = num } + */ + private static final IntSet oneOf(int size, int num) { + final IntSet set = Ints.bestSet(size); + set.add(num); + return set; + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/fol2sat/Translation.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/fol2sat/Translation.java new file mode 100644 index 00000000..e59cb1e3 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/fol2sat/Translation.java @@ -0,0 +1,152 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.engine.fol2sat; + +import java.util.Map; + +import kodkod.ast.Relation; +import kodkod.engine.satlab.SATSolver; +import kodkod.instance.Bounds; +import kodkod.instance.Instance; +import kodkod.instance.TupleFactory; +import kodkod.instance.TupleSet; +import kodkod.util.ints.IntIterator; +import kodkod.util.ints.IntSet; +import kodkod.util.ints.Ints; + +/** + * Stores the translation of a {@link kodkod.ast.Formula kodkod formula} + * to CNF. + * + * @specfield formula: Formula // the formula that was translated + * @specfield bounds: Bounds // the bounds used to obtain the CNF from the formula + * @specfield solver: SATSolver // a SATSolver containing the CNF representation of the formula + * @specfield options: Options // the options object used to control translation parameters + * @author Emina Torlak + */ +public final class Translation { + private final Bounds bounds; + private final SATSolver solver; + /* maps relations to the literals that comprise their translations */ + private final Map primaryVarUsage; + private final TranslationLog log; + private final int maxPrimaryLit; + + /** + * Constructs a new Translation object for the given solver, bounds, mapping + * from Relations to literals, and TranslationLog. + * @requires maxPrimaryLit = max(varUsage[Relation].max) + * @requires bounds.relations = varUsage.IntSet + * @ensures this.solver' = solver && this.bounds' = bounds + */ + Translation(SATSolver solver, Bounds bounds, Map varUsage, int maxPrimaryLit, TranslationLog log) { + this.solver = solver; + this.bounds = bounds; + this.primaryVarUsage = varUsage; + this.maxPrimaryLit = maxPrimaryLit; + this.log = log; + } + + /** + * Returns a SATSolver object initialized with the CNF encoding of this.formula + * and the timeout and random seed values specified by this.options. Satisfiability + * of the formula can be checked by calling {@link kodkod.engine.satlab.SATSolver#solve()}. + * @return {s: SATSolver | [[s.clauses]] = [[this.formula]] && s.timeout() = this.options.timeout() && + * s.seed() = this.options.seed() } + */ + public SATSolver cnf() { + return solver; + } + + /** + * If this.solver.solve() is true, returns + * an interpretation of the cnf solution as a + * mapping from Relations to sets of Tuples. The Relations + * mapped by the returned instance are either leaves + * of this.formula with different lower and upper + * bounds (i.e. {r: this.formula.*children & Relation | + * this.bounds.upperBound[r] != this.bounds.lowerBound[r]}), + * or skolem constants. + * @return an interpretation of the cnf solution as + * a mapping from (this.variableUsage().keySet() & Relation) to sets of Tuples. + * @throws IllegalStateException - this.solver.solve() has not been called or the + * outcome of the last call was not true. + */ + public Instance interpret() { + final TupleFactory f = bounds.universe().factory(); + final Instance instance = new Instance(bounds.universe()); +// System.out.println(varUsage); + for(Relation r : bounds.relations()) { + TupleSet lower = bounds.lowerBound(r); + IntSet indeces = Ints.bestSet(lower.capacity()); + indeces.addAll(lower.indexView()); + IntSet vars = primaryVarUsage.get(r); + if (vars!=null) { + int lit = vars.min(); + for(IntIterator iter = bounds.upperBound(r).indexView().iterator(); iter.hasNext();) { + final int index = iter.next(); + if (!indeces.contains(index) && solver.valueOf(lit++)) + indeces.add(index); + } + } + instance.add(r, f.setOf(r.arity(), indeces)); + } + return instance; + } + + /** + * Returns the set of primary variable literals that represent + * the tuples in the given relation. If no literals were allocated + * to the given relation, null is returned. + * @return the set of primary variable literals that represent + * the tuples in the given relation. + */ + public IntSet primaryVariables(Relation relation) { + return primaryVarUsage.get(relation); + } + + /** + * Returns the number of primary variables allocated + * during translation. Primary variables represent + * the tuples of Relations that are either leaves + * of this.formula with different lower and upper + * bounds (i.e. {r: this.formula.*children & Relation | + * this.bounds.upperBound[r] != this.bounds.lowerBound[r]}), + * or skolem constants. + * @return the number of primary variables allocated + * during translation. + */ + public int numPrimaryVariables() { + return maxPrimaryLit; + } + + /** + * If this.options.logTranslation was set to true, returns the log of the + * translation that produced this Translation object. Otherwise returns null. + * @return the log of the translation that produced this Translation + * object, if this.options.logTranslation was enabled during translation, or null if not. + */ + public TranslationLog log() { + return log; + } + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/fol2sat/TranslationLog.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/fol2sat/TranslationLog.java new file mode 100644 index 00000000..69f1cd49 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/fol2sat/TranslationLog.java @@ -0,0 +1,114 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.engine.fol2sat; + +import java.util.Iterator; +import java.util.Set; + +import kodkod.ast.Formula; +import kodkod.engine.Solver; +import kodkod.engine.config.Options; +import kodkod.instance.Bounds; + +/** + * A log of the translations of the descendants of a given formula that + * are either formulas or that desugar to formulas. + * @specfield originalFormula: Formula // the {@linkplain Solver#solve(Formula, kodkod.instance.Bounds) original} formula, provided by the user + * @specfield originalBounds: Bounds // the {@linkplain Solver#solve(Formula, kodkod.instance.Bounds) original} bounds, provided by the user + * @specfield formula: Formula // desugaring of this.formula that was translated + * @specfield bounds: Bounds // translation bounds + * @specfield records: set TranslationRecord + * @specfield replay: [0..#records) one->one records // replay order -- i.e. the order in the which records were added to the log + * @invariant all r: records | r.node in formula.*children + * @invariant Solver.solve(formula, bounds).instance() == null iff Solver.solve(originalFormula, originalBounds).instance() == null + * @author Emina Torlak + */ +public abstract class TranslationLog { + TranslationLog() {} + + /** + * Returns the roots of this.formula. In other words, returns the subformulas, {f0, ..., fk}, + * of this.formula such that, for all 0<=i<=k, fi [[f0 && ... && fk]] <=> [[formula]]. + * The granularity of the subdivision of this.formula into roots depends on the core granularity + * specified in the {@linkplain Options} that were used when translating this.formula. + * + *

Unless a given root translates to a constant, the highest magnitude literal corresponding to + * each root (as given by this.records) is guaranteed to be present in the translation of this.formula + * as a unit clause. All the remaining clauses (except those comprising the symmetry + * breaking predicate, encoded with its own unit clause containing the maximum literal) + * that are reachable from such a unit clause represent the translations + * of the given root's descendants. We define reachability over the clauses in a translation + * as follows: let l1 be the highest magnitude literal in the clause c1, and let l2 be the + * highest magnitude literal in c2. If l2 occurs in c1 (in any polarity), then there is + * an edge from c1 and c2. The unit clauses are always the last clauses to be added to a SAT solver + * during translation.

+ * + * @return roots of this.formula + */ + public abstract Set roots(); + + /** + * Returns this.bounds. + * @return this.bounds. + */ + public abstract Bounds bounds(); + + /** + * Returns an iterator over the translation records in this log that are accepted + * by the given filter. The iterator returns the records in the order in which + * they were generated. This guarantees that records for the descendants of a + * node are always returned before the record for the node itself. + * + *

Note:The record objects returned by the iterator are not + * required to be immutable. In particular, the state of a record object + * returned by next() is guaranteed to remain the same only until the + * subsequent call to next().

+ * @return an iterator, in the proper replay sequence, over the translation records + * in this log that are accepted by the given filter. + */ + public abstract Iterator replay(RecordFilter filter); + + /** + * Returns an iterator over all translation records in this log. The iterator returns + * the records in the order in which they were generated. This guarantees that records for + * the descendants of a node are always returned before the record for the node itself. + * The effect of this method is the same as calling {@linkplain #replay(RecordFilter) replay(RecordFilter.ALL)}. + * + *

Note:The record objects returned by the iterator are not + * required to be immutable. In particular, the state of a record object + * returned by next() is guaranteed to remain the same only until the + * subsequent call to next().

+ * @return an iterator over all translation records in this.log, in the proper replay sequence. + * @see #replay(RecordFilter) + */ + public final Iterator replay() { + return replay(RecordFilter.ALL); + } + +// /** +// * Compresses this translation log (optional operation) by eliminating +// * redundant records. +// * @ensures all r: this.records | one r': this.records' | r.node = r'.node && r.literal = r'.literal && r.env.equals(r'.env) +// * @throws UnsupportedOperationException - this log does not support compression +// */ +// public abstract void compress(); +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/fol2sat/TranslationLogger.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/fol2sat/TranslationLogger.java new file mode 100644 index 00000000..2b239637 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/fol2sat/TranslationLogger.java @@ -0,0 +1,66 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.engine.fol2sat; + +import kodkod.ast.Expression; +import kodkod.ast.Formula; +import kodkod.engine.Solver; +import kodkod.engine.bool.BooleanMatrix; +import kodkod.engine.bool.BooleanValue; + +/** + * Logs the translations of all descendants of a user-provided formula that + * are either formulas or that desugar to formulas. + * @specfield originalFormula: Formula // the {@linkplain Solver#solve(Formula, kodkod.instance.Bounds) original} formula, provided by the user + * @specfield originalBounds: Bounds // the {@linkplain Solver#solve(Formula, kodkod.instance.Bounds) original} bounds, provided by the user + * @specfield formula: Formula // desugaring of this.formula that was translated + * @specfield bounds: Bounds // translation bounds + * @specfield records: (formula.*children & Formula) -> BooleanValue -> Environment + * @invariant Solver.solve(formula, bounds).instance() == null iff Solver.solve(originalFormula, originalBounds).instance() == null + * @author Emina Torlak + */ +abstract class TranslationLogger { + + /** + * Optionally records the translation of the source of the + * given transformed formula to the given boolean value + * in the specified environment. + * @requires f in this.formula.*children + * @ensures this.records' = this.records or this.records' = this.records + f -> translation -> freeVariables(f)<:env + * @throws IllegalArgumentException - some aspect of the given translation event prevents it from being logged + * @throws IllegalStateException - this log has been closed + */ + abstract void log(Formula f, BooleanValue translation, Environment env); + + /** + * Closes this logger and releases associated resources. Attempts to call {@link #log(Formula, BooleanValue, Environment)} + * after the log has been closed may result in an IllegalStateException. + * @ensures closes this logger and releases associated resources. + */ + abstract void close(); + + /** + * Returns a TranslationLog view of this.records. + * @return a TranslationLog view of this.records. + */ + abstract TranslationLog log(); +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/fol2sat/TranslationRecord.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/fol2sat/TranslationRecord.java new file mode 100644 index 00000000..ef8c184b --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/fol2sat/TranslationRecord.java @@ -0,0 +1,87 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.engine.fol2sat; + +import java.util.Map; + +import kodkod.ast.Formula; +import kodkod.ast.Node; +import kodkod.ast.Variable; +import kodkod.instance.TupleSet; + +/** + * Record of a translation event. Each translation event is described by four pieces of information: + *
    + *
  1. the {@linkplain Formula formula} that was translated;
  2. + *
  3. the {@linkplain Node node} from which the translated formula was derived by skolemization or through some other optimization;
  4. + *
  5. the environment in which the given formula is translated, given as a binding of free variables to scalars (singleton, unary tuplesets);
  6. + *
  7. the CNF literal, expressed as an integer, that represents the meaning of the given formula in the given environment.
  8. + *
+ * + * @specfield node: Node // node that was transformed to this.translated + * @specfield translated: Formula // the translated formula obtain from this.node + * @specfield literal: int // cnf literal representing the meaning of this.node in this.env + * @specfield env: Variable ->one TupleSet // bindings for free, non-skolemized variables + * // for which this.node (or its desugared form) evaluates to this.literal + * @author Emina Torlak + */ +public abstract class TranslationRecord { + + /** + * Returns this.node. + * @return this.node. + */ + public abstract Node node(); + + /** + * Returns this.translated. + * @return this.translated + */ + public abstract Formula translated(); + + /** + * Returns this.literal. + * @return this.literal + */ + public abstract int literal(); + + /** + * Returns a map view of this.env. + * @return this.env + */ + public abstract Map env(); + + /** + * @see java.lang.Object#toString() + */ + public String toString() { + final StringBuilder ret = new StringBuilder(); + ret.append("< node: "); + ret.append(node()); + ret.append(", literal: "); + ret.append(literal()); + ret.append(", env: "); + ret.append(env()); + ret.append(">"); + return ret.toString(); + } +} \ No newline at end of file diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/fol2sat/Translator.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/fol2sat/Translator.java new file mode 100644 index 00000000..ecdd391d --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/fol2sat/Translator.java @@ -0,0 +1,430 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.engine.fol2sat; + +import static kodkod.util.nodes.AnnotatedNode.annotate; +import static kodkod.util.nodes.AnnotatedNode.annotateRoots; + +import java.util.IdentityHashMap; +import java.util.Map; +import java.util.Set; + +import kodkod.ast.Expression; +import kodkod.ast.Formula; +import kodkod.ast.IntExpression; +import kodkod.ast.Node; +import kodkod.ast.Relation; +import kodkod.ast.RelationPredicate; +import kodkod.ast.visitor.AbstractReplacer; +import kodkod.engine.bool.BooleanAccumulator; +import kodkod.engine.bool.BooleanConstant; +import kodkod.engine.bool.BooleanFactory; +import kodkod.engine.bool.BooleanFormula; +import kodkod.engine.bool.BooleanMatrix; +import kodkod.engine.bool.BooleanValue; +import kodkod.engine.bool.Int; +import kodkod.engine.bool.Operator; +import kodkod.engine.config.Options; +import kodkod.engine.satlab.SATSolver; +import kodkod.instance.Bounds; +import kodkod.instance.Instance; +import kodkod.util.ints.IntSet; +import kodkod.util.nodes.AnnotatedNode; + +/** + * Translates, evaluates, and approximates {@link Node nodes} with + * respect to given {@link Bounds bounds} (or {@link Instance instances}) and {@link Options}. + * + * @author Emina Torlak + */ +public final class Translator { + + /*---------------------- public methods ----------------------*/ + /** + * Overapproximates the value of the given expression using the provided bounds and options. + * @return a BooleanMatrix whose TRUE entries represent the tuples contained in a sound overapproximation + * of the expression. + * @throws expression = null || instance = null || options = null + * @throws UnboundLeafException - the expression refers to an undeclared variable or a relation not mapped by the instance + * @throws HigherOrderDeclException - the expression contains a higher order declaration + */ + public static BooleanMatrix approximate(Expression expression, Bounds bounds, Options options) { + Environment emptyEnv = Environment.empty(); + return FOL2BoolTranslator.approximate(annotate(expression), LeafInterpreter.overapproximating(bounds, options), emptyEnv); + } + + /** + * Evaluates the given formula to a BooleanConstant using the provided instance and options. + * + * @return a BooleanConstant that represents the value of the formula. + * @throws NullPointerException - formula = null || instance = null || options = null + * @throws UnboundLeafException - the formula refers to an undeclared variable or a relation not mapped by the instance + * @throws HigherOrderDeclException - the formula contains a higher order declaration + */ + public static BooleanConstant evaluate(Formula formula, Instance instance, Options options) { + final LeafInterpreter interpreter = LeafInterpreter.exact(instance, options); + final BooleanConstant eval = (BooleanConstant) FOL2BoolTranslator.translate(annotate(formula), interpreter); + //TODO: check OF +// final BooleanFactory factory = interpreter.factory(); +// BooleanConstant overflow = (BooleanConstant) factory.of(); +// if (options.noOverflow() && overflow.booleanValue()) { //[AM] +// eval = BooleanConstant.FALSE; +// } + return eval; + } + + /** + * Evaluates the given expression to a BooleanMatrix using the provided instance and options. + * + * @return a BooleanMatrix whose TRUE entries represent the tuples contained by the expression. + * @throws NullPointerException - expression = null || instance = null || options = null + * @throws UnboundLeafException - the expression refers to an undeclared variable or a relation not mapped by the instance + * @throws HigherOrderDeclException - the expression contains a higher order declaration + */ + public static BooleanMatrix evaluate(Expression expression,Instance instance, Options options) { + return (BooleanMatrix) FOL2BoolTranslator.translate(annotate(expression), LeafInterpreter.exact(instance, options)); + } + + /** + * Evalutes the given intexpression to an {@link kodkod.engine.bool.Int} using the provided instance and options. + * @return an {@link kodkod.engine.bool.Int} representing the value of the intExpr with respect + * to the specified instance and options. + * @throws NullPointerException - formula = null || instance = null || options = null + * @throws UnboundLeafException - the expression refers to an undeclared variable or a relation not mapped by the instance + * @throws HigherOrderDeclException - the expression contains a higher order declaration + */ + public static Int evaluate(IntExpression intExpr, Instance instance, Options options) { + LeafInterpreter interpreter = LeafInterpreter.exact(instance, options); + Int ret = (Int) FOL2BoolTranslator.translate(annotate(intExpr), interpreter); + //TODO: check OF +// BooleanFactory factory = interpreter.factory(); +// BooleanConstant bc = (BooleanConstant) factory.of(); +// boolean overflow = false; +// if (options.noOverflow() && bc.booleanValue()) //[AM] +// overflow = true; +// ret.setOverflowFlag(overflow); + return ret; + } + + /** + * Translates the given formula using the specified bounds and options. + * @return a Translation whose solver is a SATSolver instance initialized with the + * CNF representation of the given formula, with respect to the given bounds. The CNF + * is generated in such a way that the magnitude of the literal representing the truth + * value of a given formula is strictly larger than the magnitudes of the literals representing + * the truth values of the formula's descendants. + * @throws TrivialFormulaException - the given formula is reduced to a constant during translation + * (i.e. the formula is trivially (un)satisfiable). + * @throws NullPointerException - any of the arguments are null + * @throws UnboundLeafException - the formula refers to an undeclared variable or a relation not mapped by the given bounds. + * @throws HigherOrderDeclException - the formula contains a higher order declaration that cannot + * be skolemized, or it can be skolemized but options.skolemize is false. + */ + public static Translation translate(Formula formula, Bounds bounds, Options options) throws TrivialFormulaException { + return (new Translator(formula,bounds,options)).translate(); + } + + /*---------------------- private translation state and methods ----------------------*/ + /** + * @specfield formula: Formula + * @specfield bounds: Bounds + * @specfield options: Options + * @specfield log: TranslationLog + */ + private final Formula formula; + private final Bounds bounds; + private final Options options; + + private TranslationLog log; + + /** + * Constructs a Translator for the given formula, bounds and options. + * @ensures this.formula' = formula and + * this.options' = options and + * this.bounds' = bounds.clone() and + * no this.log' + */ + private Translator(Formula formula, Bounds bounds, Options options) { + this.formula = formula; + this.bounds = bounds.clone(); + this.options = options; + this.log = null; + } + + /** + * Translates this.formula with respect to this.bounds and this.options. + * @return a Translation whose solver is a SATSolver instance initialized with the + * CNF representation of the given formula, with respect to the given bounds. The CNF + * is generated in such a way that the magnitude of a literal representing the truth + * value of a given formula is strictly larger than the magnitudes of the literals representing + * the truth values of the formula's descendants. + * @throws TrivialFormulaException - this.formula is reduced to a constant during translation + * (i.e. the formula is trivially (un)satisfiable). + * @throws UnboundLeafException - this.formula refers to an undeclared variable or a relation not mapped by this.bounds. + * @throws HigherOrderDeclException - this.formula contains a higher order declaration that cannot + * be skolemized, or it can be skolemized but this.options.skolemDepth < 0 + */ + private Translation translate() throws TrivialFormulaException { + final AnnotatedNode annotated = options.logTranslation()>0 ? annotateRoots(formula) : annotate(formula); + final SymmetryBreaker breaker = optimizeBounds(annotated); + return toBoolean(optimizeFormula(annotated, breaker), breaker); + } + + /** + * Removes bindings for unused relations/ints from this.bounds and + * returns a SymmetryBreaker for the reduced bounds. + * @requires annotated.node = this.formula + * @ensures this.bounds'.relations = this.formula.*children & Relations + * @ensures !annotated.usesInts() => no this.bounds'.int + * @return { b: SymmetryBreaker | b.bounds = this.bounds' } + */ + private SymmetryBreaker optimizeBounds(AnnotatedNode annotated) { + // remove bindings for unused relations/ints + bounds.relations().retainAll(annotated.relations()); + if (!annotated.usesInts()) bounds.ints().clear(); + + // detect symmetries + return new SymmetryBreaker(bounds, options.reporter()); + } + + /** + * Optimizes annotated.node by first breaking symmetries on its top-level predicates, + * replacing them with the simpler formulas generated by {@linkplain SymmetryBreaker#breakMatrixSymmetries(Map, boolean) breaker.breakMatrixSymmetries(...)}, + * and skolemizing the result. + * @requires annotated.node = this.formula + * @requires breaker.bounds = this.bounds + * @return the skolemization, up to depth this.options.skolemDepth, of annotated.node with + * the broken predicates replaced with simpler constraints and the remaining predicates inlined. + */ + private AnnotatedNode optimizeFormula(AnnotatedNode annotated, SymmetryBreaker breaker) { + options.reporter().optimizingBoundsAndFormula(); + if (options.logTranslation()==0) { // no logging + annotated = inlinePredicates(annotated, breaker.breakMatrixSymmetries(annotated.predicates(), true).keySet()); + annotated = options.skolemDepth()>=0 ? Skolemizer.skolemize(annotated, bounds, options) : annotated; +// if (options.noOverflow()) { +// annotated = NNFConverter.flatten(annotated); // FullNegationPropagator.flatten(annotated); +// } + return annotated; + } else { + // logging; inlining of predicates *must* happen last when logging is enabled + if (options.coreGranularity()==1) { + annotated = FormulaFlattener.flatten(annotated, false); + } + if (options.skolemDepth()>=0) { + annotated = Skolemizer.skolemize(annotated, bounds, options); + } + if (options.coreGranularity()>1) { + annotated = FormulaFlattener.flatten(annotated, options.coreGranularity()==3); + } +// if (options.noOverflow()) { +// annotated = NNFConverter.flatten(annotated); //FullNegationPropagator.flatten(annotated); +// } + return inlinePredicates(annotated, breaker.breakMatrixSymmetries(annotated.predicates(), false)); + } + } + + /** + * Returns an annotated formula f such that f.node is equivalent to annotated.node + * with its truePreds replaced with the constant formula TRUE and the remaining + * predicates replaced with equivalent constraints. + * @requires this.options.logTranslation = false + * @requires truePreds in annotated.predicates()[RelationnPredicate.NAME] + * @requires truePreds are trivially true with respect to this.bounds + * @return an annotated formula f such that f.node is equivalent to annotated.node + * with its truePreds replaced with the constant formula TRUE and the remaining + * predicates replaced with equivalent constraints. + */ + private AnnotatedNode inlinePredicates(final AnnotatedNode annotated, final Set truePreds) { + final AbstractReplacer inliner = new AbstractReplacer(annotated.sharedNodes()) { + public Formula visit(RelationPredicate pred) { + Formula ret = lookup(pred); + if (ret!=null) return ret; + return truePreds.contains(pred) ? cache(pred, Formula.TRUE) : cache(pred, pred.toConstraints()); + } + }; + return annotate(annotated.node().accept(inliner)); + } + + /** + * Returns an annotated formula f such that f.node is equivalent to annotated.node + * with its simplified predicates replaced with their corresponding Formulas and the remaining + * predicates replaced with equivalent constraints. The annotated formula f will contain transitive source + * information for each of the subformulas of f.node. Specifically, let t be a subformula of f.node, and + * s be a descdendent of annotated.node from which t was derived. Then, f.source[t] = annotated.source[s].

+ * @requires this.options.logTranslation = true + * @requires simplified.keySet() in annotated.predicates()[RelationPredicate.NAME] + * @requires no disj p, p': simplified.keySet() | simplified.get(p) = simplifed.get(p') // this must hold in order + * to maintain the invariant that each subformula of the returned formula has exactly one source + * @requires for each p in simplified.keySet(), the formulas "p and [[this.bounds]]" and + * "simplified.get(p) and [[this.bounds]]" are equisatisfiable + * @return an annotated formula f such that f.node is equivalent to annotated.node + * with its simplified predicates replaced with their corresponding Formulas and the remaining + * predicates replaced with equivalent constraints. + */ + private AnnotatedNode inlinePredicates(final AnnotatedNode annotated, final Map simplified) { + final Map sources = new IdentityHashMap(); + final AbstractReplacer inliner = new AbstractReplacer(annotated.sharedNodes()) { + private RelationPredicate source = null; + protected N cache(N node, N replacement) { + if (replacement instanceof Formula) { + if (source==null) { + final Node nsource = annotated.sourceOf(node); + if (replacement!=nsource) + sources.put(replacement, nsource); + } else { + sources.put(replacement, source); + } + } + return super.cache(node, replacement); + } + public Formula visit(RelationPredicate pred) { + Formula ret = lookup(pred); + if (ret!=null) return ret; + source = pred; + if (simplified.containsKey(pred)) { + ret = simplified.get(pred).accept(this); + } else { + ret = pred.toConstraints().accept(this); + } + source = null; + return cache(pred, ret); + } + }; + + return annotate(annotated.node().accept(inliner), sources); + } + + /** + * Translates the given annotated formula to a circuit, conjoins the circuit with an + * SBP generated by the given symmetry breaker, flattens the result if so specified by this.options, + * and returns its Translation to CNF. + * @requires [[annotated.node]] <=> ([[this.formula]] and [[breaker.broken]]) + * @ensures this.options.logTranslation => some this.log' + * @return the result of calling {@link #generateSBP(BooleanFormula, LeafInterpreter, SymmetryBreaker)} + * on the translation of annotated.node with respect to this.bounds + * @throws TrivialFormulaException - the translation of annotated is a constant or can be made into + * a constant by flattening + */ + private Translation toBoolean(AnnotatedNode annotated, SymmetryBreaker breaker) throws TrivialFormulaException { + + options.reporter().translatingToBoolean(annotated.node(), bounds); + + final LeafInterpreter interpreter = LeafInterpreter.exact(bounds, options); + //final BooleanFactory factory = interpreter.factory(); + + if (options.logTranslation()>0) { + final TranslationLogger logger = options.logTranslation()==1 ? new MemoryLogger(annotated, bounds) : new FileLogger(annotated, bounds); + BooleanAccumulator circuit = FOL2BoolTranslator.translate(annotated, interpreter, logger); + log = logger.log(); + if (circuit.isShortCircuited()) { + throw new TrivialFormulaException(annotated.node(), bounds, circuit.op().shortCircuit(), log); + } else if (circuit.size()==0) { + throw new TrivialFormulaException(annotated.node(), bounds, circuit.op().identity(), log); + } + return generateSBP(circuit, interpreter, breaker); + } else { + BooleanValue circuit = (BooleanValue)FOL2BoolTranslator.translate(annotated, interpreter); + if (circuit.op()==Operator.CONST) { + throw new TrivialFormulaException(annotated.node(), bounds, (BooleanConstant)circuit, null); + } + return generateSBP(annotated, (BooleanFormula)circuit, interpreter, breaker); + } + } + + /** + * Adds to given accumulator an SBP generated using the given symmetry breaker and interpreter, + * and returns the resulting circuit's translation to CNF. + * @requires circuit is a translation of this.formula with respect to this.bounds + * @requires interpreter is the leaf interpreter used in generating the given circuit + * @requires breaker.bounds = this.bounds + * @return toCNF(circuit && breaker.generateSBP(interpreter)) + */ + private Translation generateSBP(BooleanAccumulator circuit, LeafInterpreter interpreter, SymmetryBreaker breaker) { + options.reporter().generatingSBP(); + final BooleanFactory factory = interpreter.factory(); + circuit.add(breaker.generateSBP(interpreter, options.symmetryBreaking())); + return toCNF((BooleanFormula)factory.accumulate(circuit), factory.numberOfVariables(), interpreter.vars()); + } + + /** + * Conjoins the given circuit with an SBP generated using the given symmetry breaker and interpreter, + * and returns the resulting circuit's translation to CNF. + * @requires [[annotated.node]] <=> ([[this.formula]] and [[breaker.broken]]) + * @requires circuit is a translation of annotated.node with respect to this.bounds + * @requires interpreter is the leaf interpreter used in generating the given circuit + * @requires breaker.bounds = this.bounds + * @return flatten(circuit && breaker.generateSBP(interpreter), interpreter) + * @throws TrivialFormulaException - flattening the circuit and the predicate yields a constant + */ + private Translation generateSBP(AnnotatedNode annotated, BooleanFormula circuit, LeafInterpreter interpreter, SymmetryBreaker breaker) + throws TrivialFormulaException { + options.reporter().generatingSBP(); + final BooleanFactory factory = interpreter.factory(); + final BooleanValue sbp = breaker.generateSBP(interpreter, options.symmetryBreaking()); + return flatten(annotated, (BooleanFormula)factory.and(circuit, sbp), interpreter); + } + + /** + * If this.options.flatten is true, flattens the given circuit and returns its translation to CNF. + * Otherwise, simply returns the given circuit's translation to CNF. + * @requires [[annotated.node]] <=> ([[this.formula]] and [[breaker.broken]]) + * @requires circuit is a translation of annotated.node with respect to this.bounds + * @requires interpreter is the leaf interpreter used in generating the given circuit + * @return if this.options.flatten then + * toCNF(flatten(circuit), interpreter.factory().numberOfVariables(), interpreter.vars()) else + * toCNF(circuit, interpreter.factory().numberOfVariables(), interpreter.vars()) + * @throws TrivialFormulaException - flattening the circuit yields a constant + */ + private Translation flatten(AnnotatedNode annotated, BooleanFormula circuit, LeafInterpreter interpreter) throws TrivialFormulaException { + final BooleanFactory factory = interpreter.factory(); + if (options.flatten()) { + options.reporter().flattening(circuit); + final BooleanValue flatCircuit = BooleanFormulaFlattener.flatten(circuit, factory); + if (flatCircuit.op()==Operator.CONST) { + throw new TrivialFormulaException(annotated.node(), bounds, (BooleanConstant)flatCircuit, null); + } else { + return toCNF((BooleanFormula)flatCircuit, factory.numberOfVariables(), interpreter.vars()); + } + } else { + return toCNF(circuit, factory.numberOfVariables(), interpreter.vars()); + } + } + + /** + * Translates the given circuit to CNF, adds the clauses to a SATSolver returned + * by options.solver(), and returns a Translation object constructed from the solver + * and the provided arguments. + * @requires circuit is a translation of this.formula with respect to this.bounds + * @requires primaryVars is the number of primary variables generated by translating + * this.formula and this.bounds into the given circuit + * @requires varUsage maps each non-constant relation in this.bounds to the labels of + * the primary variables used to represent that relation in the given circuit + * @return Translation constructed from a SAT solver initialized with the CNF translation + * of the given circuit, the provided arguments, this.bounds, and this.log + */ + private Translation toCNF(BooleanFormula circuit, int primaryVars, Map varUsage) { + options.reporter().translatingToCNF(circuit); + final SATSolver cnf = Bool2CNFTranslator.translate((BooleanFormula)circuit, options.solver(), primaryVars); + return new Translation(cnf, bounds, varUsage, primaryVars, log); + } + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/fol2sat/TrivialFormulaException.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/fol2sat/TrivialFormulaException.java new file mode 100644 index 00000000..e99b27d2 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/fol2sat/TrivialFormulaException.java @@ -0,0 +1,93 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.engine.fol2sat; + +import kodkod.ast.Formula; +import kodkod.engine.bool.BooleanConstant; +import kodkod.instance.Bounds; + +/** + * Thrown when a formula is found to be trivially (un)satisfiable + * with respect to given Bounds. + * + * @specfield formula: Formula // possibly de-sugared (skolemized) formula + * @specfield bounds: Bounds // bounds (possibly a subset of the original bounds) with respect to which the formula reduces to a constant + * @specfield log: lone TranslationLog // log is null if translation logging was not enabled + * @specfield value: BooleanConstant // the value to which the reduction simplified + * @author Emina Torlak + */ +public final class TrivialFormulaException extends Exception { + private final BooleanConstant value; + private final TranslationLog log; + private final Formula formula; + private final Bounds bounds; + + private static final long serialVersionUID = 6251577831781586067L; + + /** + * Constructs a new TrivialFormulaException using the given arguments. + * @requires log != null && bounds != null && value != null + * @ensures this.log' = log && this.formula' = log.formula && + * this.bounds' = bounds && this.value' = value + */ + TrivialFormulaException(Formula formula, Bounds bounds, BooleanConstant formulaValue, TranslationLog log) { + super("Trivially " + ((formulaValue==BooleanConstant.FALSE) ? "un" : "" ) + "satisfiable formula."); + assert formulaValue != null && bounds != null; + this.log = log; + this.formula = formula; + this.bounds = bounds; + this.value = formulaValue; + } + + /** + * Returns this.log. + * @return this.log + */ + public TranslationLog log() { + return log; + } + + /** + * Returns this.formula. + * @return this.formula + */ + public Formula formula() { + return formula; + } + + /** + * Return this.bounds. + * @return this.bounds + */ + public Bounds bounds() { + return bounds; + } + + /** + * Returns the value to which this.formula is trivially reducible. + * @return this.value + */ + public BooleanConstant value() { + return value; + } + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/fol2sat/UnboundLeafException.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/fol2sat/UnboundLeafException.java new file mode 100644 index 00000000..fd616bad --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/fol2sat/UnboundLeafException.java @@ -0,0 +1,52 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.engine.fol2sat; + +import kodkod.ast.LeafExpression; + +/** + * Thrown when a node contains an undeclared variable or a relation with no bounds. + * @specfield leaf: LeafExpression // the unbound leaf that caused the exception to be thrown + * @author Emina Torlak + */ +public final class UnboundLeafException extends RuntimeException { + private final LeafExpression leaf; + private static final long serialVersionUID = 2472395272061454465L; + + /** + * Constructs an UnboundLeafException for the given leaf. + * @ensures this.leaf' = leaf + */ + UnboundLeafException(String msg, LeafExpression leaf) { + super(msg + ": " +leaf); + this.leaf = leaf; + } + + /** + * Returns this.leaf. + * @return this.leaf + */ + public LeafExpression leaf() { + return leaf; + } + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/fol2sat/package.html b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/fol2sat/package.html new file mode 100644 index 00000000..1163ddca --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/fol2sat/package.html @@ -0,0 +1,45 @@ + + + + + + + + +Provides a facade for translating, evaluating, and approximating Kodkod +formulas, expressions, and int expressions with respect to a given Bounds +(or Instance) and Options. + +

Package Specification

+ +

Provides a facade for translating, evaluating, and approximating Kodkod +formulas, expressions, and int expressions with respect to given Bounds +(or Instance) and Options. The {@linkplain kodkod.engine.fol2sat.Translator} +class contains methods for translating a Kodkod formula to CNF, evaluating +a Node with respect to an instance, and over-approximating the value of an +expression based on the upper bounds in a given Bounds object.

+ +

Related Documentation

+ +@see kodkod.engine.fol2sat.Translator +@see kodkod.engine.fol2sat.Translation +@see kodkod.engine.fol2sat.TranslationLog +@see kodkod.engine.fol2sat.TranslationRecord + + + diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/package.html b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/package.html new file mode 100644 index 00000000..2810e5d3 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/package.html @@ -0,0 +1,47 @@ + + + + + + + + +Provides classes for analyzing and evaluating Kodkod ASTs with +respect to finite bounds or instances. + +

Package Specification

+ +

Contains classes for analyzing and evaluating Kodkod ASTs with +respect to finite bounds or instances. The class Solver provides +methods for finding finite models of Kodkod formulas with respect +to given {@linkplain kodkod.instance.Bounds Bounds} and {@linkplain kodkod.engine.config.Options Options}. +The class Evalutor enables evaluation of formulas, expressions, and integer expressions with +respect to a particular {@linkplain kodkod.instance.Instance Instance} and {@linkplain kodkod.engine.config.Options Options}.

+ +

Related Documentation

+ +@see kodkod.instance.Bounds +@see kodkod.instance.Instance +@see kodkod.ast.Expression +@see kodkod.ast.IntExpression +@see kodkod.ast.Formula +@see kodkod.engine.Solver +@see kodkod.engine.Evaluator + + + diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/satlab/ArrayTrace.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/satlab/ArrayTrace.java new file mode 100644 index 00000000..a2aec07d --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/satlab/ArrayTrace.java @@ -0,0 +1,566 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.engine.satlab; + +import java.util.Iterator; +import java.util.NoSuchElementException; + +import kodkod.util.collections.Containers; +import kodkod.util.ints.IntBitSet; +import kodkod.util.ints.IntIterator; +import kodkod.util.ints.IntSet; +import kodkod.util.ints.Ints; + +/** + * An array-backed implementation of the {@linkplain ResolutionTrace} interface. + * @author Emina Torlak + */ +final class ArrayTrace implements ResolutionTrace { + /* The trace array encodes the resolution trace as follows. + * The first axioms entries in the trace array contain + * the literals of the clauses added to the prover, in the order + * in which they were added. The remaining entries encode the + * resolvents as follows. Let i >= axioms + * represent the ith resolvent. Then trace[i][0] contains the number of the resolvent's + * antecedents; trace[i][1..trace[i][0]] contains the indices of + * the resolvent's antecedents in the trace; and trace[i][trace[i][0]+1..trace[i].length-1] + * contains the literals of the ith resolvent. All literals are sorted in the + * increasing order of absolute values. All antecedents of a given resolvent precede it in the trace, + * and the conflict clause should be the last trace element. + */ + private final int[][] trace; + private final int axioms; + private final IntSet core; + + /** + * Constructs a resolution trace view for the given raw trace. + * The first axioms entries in the trace array should + * contain the literals of the clauses added to the prover, in + * the order in which they were added. The literals should be + * sorted in the increasing order of absolute values. The remaining entries + * should encode the resolvents as follows. Let i be the index + * of a resolvent in the raw trace. Then, for all 0 <= j < trace[i].length, + * trace[i][j] is the index of the resolvent's jth antecedents in the trace array. + * All antecedents of a given resolvent should precede it in the trace, + * and the conflict clause should be the last trace element. + * + *

Note: the given array's contents must not be modified while + * in use by this resolution trace object.

+ */ + ArrayTrace(int[][] trace, int axioms) { + this.axioms = axioms; + this.trace = computeResolventLiterals(trace, axioms, axioms); + this.core = Ints.unmodifiableIntSet(core(trace,axioms)); + } + + /** + * Constructs a resolution trace from the given subtrace and partial + * trace. This constructor assumes that partial is the result + * of solving the subtrace of the original trace that is given by the + * specified set of indices. The first indices.size() of the partial + * trace are assumed to represent the clauses given by original.trace[indices], + * in the increasing order of indices; the remaining entries should encode + * the resolvents computed from original.trace[indices], as specified by + * {@linkplain #ArrayTrace(int[][], int)}. The given subtrace of the original + * trace must be self-contained, i.e. original.reachable(indices).equals(indices). + * + *

Note: the given array's contents must not be modified while + * in use by this resolution trace object.

+ */ + ArrayTrace(ArrayTrace original, IntSet indices, int[][] partial) { + + int axiomCount = indices.size(); + { // fill the partial[0..indices.size()-1] with the corresponding clauses from original.trace[indices] + final int[][] originalTrace = original.trace; + final int[] position = new int[originalTrace.length]; + + IntIterator itr = indices.iterator(); + for(int i = 0, length = indices.size(); i < length; i++) { + int index = itr.next(); + position[index] = i; + if (original.axiom(index)) { // just set the ith pointer to original literals + partial[i] = originalTrace[index]; + } else { // copy the resolvent and adjust copy's antecedent indices + int resLength = originalTrace[index].length; + int[] resolvent = new int[resLength]; + System.arraycopy(originalTrace[index], 0, resolvent, 0, resLength); + for(int j = 1, lastAnte = resolvent[0]; j <= lastAnte; j++) { + resolvent[j] = position[resolvent[j]]; + } + partial[i] = resolvent; + axiomCount--; + } + } + } + this.axioms = axiomCount; + this.trace = computeResolventLiterals(partial, axioms, indices.size()); + this.core = Ints.unmodifiableIntSet(core(trace,axioms)); + } + + + /** + * Computes literals for the resolvents at indices [computeFrom..trace.length) in the given trace. + * Arrays at indices [0..computeFrom) are assumed to encode clauses as specified by {@linkplain #trace}. + * The resolvents at indices [computeFrom..trace.length) are assumed to encode the remaining resolvents, + * as specified by the {@linkplain #ArrayTrace(int[][], int)} constructor. + * @requires axioms <= computeFrom < trace.length + * @ensures modifies trace to conform to the specification of {@linkplain #trace} + * @return modified trace + */ + private static int[][] computeResolventLiterals(int[][] trace, int axioms, int computeFrom) { + for(int i = computeFrom, length = trace.length; i < length; i++) { + int[] ante = trace[i]; + int[] lits = resolve(trace[ante[0]], ante[0] abs(c1[i]) < abs(c1[j])) and + * (all i: [off2..c2.length), j: [off2..c2.length) | i < j => abs(c2[i]) < abs(c2[j])) and + * (one i: [off1..c1.length), j: [off2..c2.length) | c1[i] = -c2[j]) + * @return an array of integers representing the result of + * resolving the clauses c1 and c2, sorted in the increasing order of absolute values + */ + private static int[] resolve(int[] c1, boolean axiom1, int[] c2, boolean axiom2) { + final int len1 = c1.length, len2 = c2.length; + int i = axiom1 ? 0 : c1[0] + 1; + int j = axiom2 ? 0 : c2[0] + 1; + int k = 0; + + final int[] tmp = new int[(len1-i + len2-j) - 2]; + + while(i < len1 && j < len2) { + int lit1 = c1[i], lit2 = c2[j]; + int var1 = StrictMath.abs(lit1), var2 = StrictMath.abs(lit2); + if (var1==var2) { + if (lit1==lit2) { + tmp[k++] = lit1; + } + i++; + j++; + } else if (var1 < var2) { + tmp[k++] = lit1; + i++; + } else { // var1 > var2 + tmp[k++] = lit2; + j++; + } + } + if (i= axioms; i--) { + if (reachable.contains(i)) { + int[] resolvent = trace[i]; + for(int j = 1, antes = resolvent[0]; j <= antes; j++) { + reachable.add(resolvent[j]); + } + } + } + + for(IntIterator itr = reachable.iterator(0, axioms-1); itr.hasNext(); ) { + core.add(itr.next()); + } + + return core; + } + + /** + * Returns true if the clause at the given index is an axiom. + * @return index < this.axioms + */ + private boolean axiom(int index) { + return index < axioms; + } + + /** + * Returns the offset in this.trace[index] array where literal + * data is stored. + * @return axiom(index) ? 0 : this.trace[index][0] + 1 + */ + private int litOffset(int index) { + return axiom(index) ? 0 : trace[index][0] + 1; + } + + /** + * {@inheritDoc} + * @see kodkod.engine.satlab.ResolutionTrace#size() + */ + public int size() { return trace.length; } + + + /** + * {@inheritDoc} + * @see kodkod.engine.satlab.ResolutionTrace#core() + */ + public IntSet core() { return core; } + + /** + * {@inheritDoc} + * @see kodkod.engine.satlab.ResolutionTrace#axioms() + */ + public IntSet axioms() { return Ints.rangeSet(Ints.range(0, axioms-1)); } + + /** + * {@inheritDoc} + * @see kodkod.engine.satlab.ResolutionTrace#resolvents() + */ + public IntSet resolvents() { return Ints.rangeSet(Ints.range(axioms, trace.length-1)); } + + /** + * {@inheritDoc} + * @see kodkod.engine.satlab.ResolutionTrace#get(int) + */ + public Clause get(final int index) { + if (index>=0 && index antecedents() { return Containers.emptyIterator(); } + public IntIterator literals() { return new IntArrayIterator(literals,0,literals.length); } + public int maxVariable() { return StrictMath.abs(literals[literals.length-1]); } + public int numberOfAntecedents() { return 0; } + public int size() { return literals.length; } + public int[] toArray(int[] array) { + if (array.length iterator() { + return new ClauseIterator(new IntIterator() { + int index = 0; + public boolean hasNext() { return index>=0 && index < trace.length; } + public int next() { + if (!hasNext()) throw new NoSuchElementException(); + return index++; + } + public void remove() { throw new UnsupportedOperationException(); } + }); + } + + /** + * Returns true if indices.min() >= 0 && indices.max() < this.size() + * @requires !indices.isEmpty() + * @return indices.min() >= 0 && indices.max() < this.size() + */ + private boolean valid(IntSet indices) { + return indices.min()>=0 && indices.max() iterator(IntSet indices) { + if (indices.isEmpty() || valid(indices)) { + return new ClauseIterator(indices.iterator()); + } + throw new IndexOutOfBoundsException("invalid indices: " + indices); + } + + /** + * {@inheritDoc} + * @see kodkod.engine.satlab.ResolutionTrace#reverseIterator(kodkod.util.ints.IntSet) + */ + public Iterator reverseIterator(IntSet indices) { + if (indices.isEmpty() || valid(indices)) { + return new ClauseIterator(indices.iterator(Integer.MAX_VALUE, Integer.MIN_VALUE)); + } + throw new IndexOutOfBoundsException("invalid indices: " + indices); + } + + /** + * {@inheritDoc} + * @see kodkod.engine.satlab.ResolutionTrace#implicants(kodkod.util.ints.IntSet) + */ + public IntSet reachable(IntSet indices) { + if (indices.isEmpty()) return Ints.EMPTY_SET; + else if (valid(indices)) { + final IntSet ret = new IntBitSet(trace.length); + ret.addAll(indices); + for(int i = indices.max(); i >= axioms; i--) { + if (ret.contains(i)) { + int[] resolvent = trace[i]; + for(int j = 1, antes = resolvent[0]; j <= antes; j++) { + ret.add(resolvent[j]); + } + } + } + return ret; + } + else throw new IndexOutOfBoundsException("invalid indices: " + indices); + } + + /** + * {@inheritDoc} + * @see kodkod.engine.satlab.ResolutionTrace#backwardReachable(kodkod.util.ints.IntSet) + */ + public IntSet backwardReachable(IntSet indices) { + if (indices.isEmpty()) return Ints.EMPTY_SET; + else if (valid(indices)) { + final IntSet ret = new IntBitSet(trace.length); + ret.addAll(indices); + for(int i = axioms, length = trace.length; i < length; i++) { + int[] resolvent = trace[i]; + for(int j = 1, antes = resolvent[0]; j <= antes; j++) { + if (ret.contains(resolvent[j])) { + ret.add(i); + break; + } + } + } + return ret; + } + else throw new IndexOutOfBoundsException("invalid indices: " + indices); + } + + /** + * {@inheritDoc} + * @see kodkod.engine.satlab.ResolutionTrace#learnable(kodkod.util.ints.IntSet) + */ + public IntSet learnable(IntSet indices) { + if (indices.isEmpty()) return Ints.EMPTY_SET; + else if (valid(indices)) { + final IntSet ret = new IntBitSet(trace.length); + ret.addAll(indices); + TOP: for(int i = axioms, length = trace.length; i < length; i++) { + int[] resolvent = trace[i]; + for(int j = 1, antes = resolvent[0]; j <= antes; j++) { + if (!ret.contains(resolvent[j])) { + continue TOP; + } + } + ret.add(i); + } + return ret; + } + else throw new IndexOutOfBoundsException("invalid indices: " + indices); + } + + /** + * {@inheritDoc} + * @see kodkod.engine.satlab.ResolutionTrace#directlyLearnable(kodkod.util.ints.IntSet) + */ + public IntSet directlyLearnable(IntSet indices) { + if (indices.isEmpty()) return Ints.EMPTY_SET; + else if (valid(indices)) { + final IntSet ret = new IntBitSet(trace.length); + ret.addAll(indices); + TOP: for(int i = axioms, length = trace.length; i < length; i++) { + int[] resolvent = trace[i]; + for(int j = 1, antes = resolvent[0]; j <= antes; j++) { + if (!indices.contains(resolvent[j])) { + continue TOP; + } + } + ret.add(i); + } + return ret; + } + + else throw new IndexOutOfBoundsException("invalid indices: " + indices); + } + + /** + * {@inheritDoc} + * @see java.lang.Object#toString() + */ + public String toString() { + final StringBuilder ret = new StringBuilder(); + for(int i = 0; i < axioms; i++) { + ret.append("AXIOM. Literals: "); + int[] clause = trace[i]; + for(int j = 0, c = clause.length; j < c; j++) { + ret.append(clause[j]); + ret.append(" "); + } + ret.append("\n"); + } + for(int i = axioms, max = trace.length; i < max; i++) { + ret.append("RESOLVENT. Antecedents: "); + int[] clause = trace[i]; + for(int j = 1, c = clause[0]; j <= c; j++) { + ret.append(clause[j]); + ret.append(" "); + } + ret.append("\n"); + } + return ret.toString(); + } + + /** + * A mutable implementation of the Clause interface. + * @author Emina Torlak + */ + private class ClauseView extends Clause { + private int[] clause; + private int litOffset; + + /** + * Constructs a clause view for the ith clause. + * @requires 0 <= index < trace.length + */ + ClauseView(int index) { + this.clause = trace[index]; + this.litOffset = litOffset(index); + } + + /** + * Constructs a clause view for the 0th clause. + */ + ClauseView() { this(0); } + + /** + * Sets the state of this clause view to represent + * the ith clause in the trace and returns this. + * @ensures sets the state of this clause view to represent + * the ith clause in the trace + * @return this + */ + ClauseView set(int index) { + this.clause = trace[index]; + this.litOffset = litOffset(index); + return this; + } + + public int maxVariable() { return StrictMath.abs(clause[clause.length-1]); } + public int numberOfAntecedents() { return StrictMath.max(0, litOffset-1); } + public int size() { return clause.length - litOffset; } + public Iterator antecedents() { return new ClauseIterator(new IntArrayIterator(clause, 1, litOffset)); } + public IntIterator literals() { return new IntArrayIterator(clause, litOffset, clause.length); } + + public int[] toArray(int[] array) { + final int size = size(); + if (array.length < size) { + array = new int[size]; + } + System.arraycopy(clause, litOffset, array, 0, size); + return array; + } + } + + /** + * A clause iterator wrapper for an int iterator. + * @author Emina Torlak + */ + private final class ClauseIterator extends ClauseView implements Iterator { + private final IntIterator itr; + /** + * Constructs a clause iterator that will iterate over the clauses in this.trace + * located at the indices given by itr. The given iterator must return valid indices. + */ + ClauseIterator(IntIterator itr) { + this.itr = itr; + } + public boolean hasNext() { return itr.hasNext(); } + public Clause next() { return set(itr.next()); } + public void remove() { throw new UnsupportedOperationException(); } + } + + /** + * An int iterator that iterates over the portion of an integer array + * in the increasing order of indices. + * @author Emina Torlak + */ + private static final class IntArrayIterator implements IntIterator { + private final int[] array; + private int from; + private final int to; + /** + * Constructs an int iterator that iterates over the given array, + * returning the elements between from, inclusive, and to, exclusive. + * @requires 0 <= from < array.length < Integer.MAX_VALUE + */ + IntArrayIterator(int[] array, int from, int to) { + this.array = array; + this.from = from; + this.to = to; + } + public boolean hasNext() { return from >= 0 && from < to; } + public int next() { + if (!hasNext()) throw new NoSuchElementException(); + return array[from++]; + } + public void remove() { throw new UnsupportedOperationException(); } + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/satlab/Clause.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/satlab/Clause.java new file mode 100644 index 00000000..f774c310 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/satlab/Clause.java @@ -0,0 +1,155 @@ +package kodkod.engine.satlab; + +import java.util.Iterator; + +import kodkod.util.ints.IntIterator; +import kodkod.util.ints.Ints; + +/** + * A propositional clause. + * + * @specfield literals: set int + * @specfield antecedents: Clause[] + * @invariant 0 !in literals + * @invariant no lit: literals | -lit in literals + * @invariant some antecedents => #antecedents > 1 + * @invariant some antecedents => + * literals = { lit: antecedents[int].literals | + * no i: [0..#antecedents-1) | lit in antecedents[i].literals && -lit in antecedents[i+1].literals } + * @author Emina Torlak + */ +public abstract class Clause { + /** + * Constructs a new clause. + */ + Clause() {} + + /** + * Returns the size of this clause, measured in the + * number of literals. + * @return #this.literals + */ + public abstract int size(); + + /** + * Returns an iterator over the literals in this clause, + * in the ascending order of absolute values. + * @return an iterator over this.literals + */ + public abstract IntIterator literals(); + + /** + * Returns the largest variable identifier occurring in this.literals. + * @return max(abs(this.literals)) + */ + public abstract int maxVariable(); + + /** + * Copies this.literals into the specified array, provided + * that it is large enough, and returns it. If the array is not large enough, + * a new array is allocated, populated with this.literals, and returned. + * @return the given array, filled with this.literals, if + * the it is large enough; otherwise a new array containing this.literals + * @throws NullPointerException - array = null + */ + public abstract int[] toArray(int[] array); + + /** + * Returns a new array of length this.size(), initialized with this.literals. + * @return a new array of length this.size(), initialized with this.literals. + */ + public int[] toArray() { + return toArray(new int[size()]); + } + + /** + * Returns the number of antecedents of this clause. + * @return #this.antecedents + */ + public abstract int numberOfAntecedents(); + + /** + * Returns an iterator that traverses this.antecedents in proper sequence. + *

Note:The clause objects returned by the iterator are not + * required to be immutable. In particular, the state of a clause object + * returned by next() (as well as the state of any object obtained + * through that clause's {@linkplain Clause#antecedents()} methods) is guaranteed + * to remain the same only until the subsequent call to next().

+ * @return an iterator that traverses this.antecedents in proper sequence. + */ + public abstract Iterator antecedents(); + + /** +// * Returns the antecedent at the given index. +// * @requires 0 <= index < this.numberOfAntecedents() +// * @return this.antecedents[index] +// * @throws IndexOutOfBoundsException - index < 0 || index >= this.numberOfAntecedents() +// */ +// public abstract Clause antecedent(int index); + + /** + * Returns true if o is a Clause whose literals and antecedents + * are equal to those of this clause. + * @return o in Clause && o.literals.equals(this.literals) && o.antecedents.equals(this.antecedents) + */ + public boolean equals(Object o) { + if (o==this) return true; + if (o instanceof Clause) { + final Clause c = (Clause) o; + if (size()==c.size()) { + final IntIterator itr1 = literals(), itr2 = literals(); + while(itr1.hasNext()) { + if (itr1.next()!=itr2.next()) return false; + } + } + final int ante = numberOfAntecedents(); + if (ante > 0 && ante==c.numberOfAntecedents()) { + final Iterator itr1 = antecedents(), itr2 = c.antecedents(); + while(itr1.hasNext()) { + if (!itr1.next().equals(itr2.next())) return false; + } + } + return ante==0; + } + return false; + } + + /** + * Returns the hashcode for this clause. The hashcode for a clause is equivalent + * to Ints.superFastHash(x) where x is an array such that its first this.size() + * elements are the literals of this clause (as returned by this.literals()) + * and its remaining this.numberOfAntecedents() elements are the hashCodes of + * this.antecedents (as returned by this.antecedents()). + * @return hashcode for this clause + */ + public int hashCode() { + int hash = size() + numberOfAntecedents(); + for(IntIterator iter = literals(); iter.hasNext(); ) { + hash = Ints.superFastHashIncremental(iter.next(), hash); + } + for(Iterator iter = antecedents(); iter.hasNext(); ) { + hash = Ints.superFastHash(new int[] { iter.next().hashCode(), hash }); + } + return Ints.superFastHashAvalanche(hash); + } + + /** + * Returns a string representation of this clause. + * @return a string representation of this clause. + */ + public String toString() { + final StringBuilder ret = new StringBuilder(); + if (numberOfAntecedents()==0) { + ret.append("AXIOM"); + } else { + ret.append("RESOLVENT"); + } + ret.append(". Literals: {"); + for(IntIterator iter = literals(); iter.hasNext();) { + ret.append(" "); + ret.append(iter.next()); + } + ret.append(" }"); + return ret.toString(); + } +} \ No newline at end of file diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/satlab/ExternalSolver.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/satlab/ExternalSolver.java new file mode 100644 index 00000000..566e2e14 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/satlab/ExternalSolver.java @@ -0,0 +1,280 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.engine.satlab; + +import java.io.BufferedReader; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.RandomAccessFile; +import java.util.BitSet; + +/** + * An implementation of a wrapper for an external SAT solver, + * executed in a separate process. + * @author Emina Torlak + */ +final class ExternalSolver implements SATSolver { + private final StringBuilder buffer; + private final int capacity = 8192; + private final String executable, inTemp, outTemp; + private final String[] options; + private final RandomAccessFile cnf; + private final BitSet solution; + private volatile Boolean sat; + private volatile int vars, clauses; + + /** + * Constructs an ExternalSolver that will execute the specified binary + * with the given options on the inTemp file with the specified name. inTemp + * will be initialized to contain all clauses added to this solver via the + * {@link #addClause(int[])} method. If outTemp is the empty string, + * the solver is assumed to write its output to standard out. Otherwise, + * it is assumed to write its output to the outTemp file. + */ + ExternalSolver(String executable, String inTemp, String outTemp, String... options) { + try { + this.cnf = new RandomAccessFile(inTemp,"rw"); + this.cnf.setLength(0); + // get enough space into the buffer for the cnf header, which will be written last + this.buffer = new StringBuilder(); + for(int i = headerLength(); i > 0; i--) { + buffer.append(" "); + } + buffer.append("\n"); + this.sat = null; + this.solution = new BitSet(); + this.vars = 0; + this.clauses = 0; + this.executable = executable; + this.options = options; + this.inTemp = inTemp; + this.outTemp = outTemp; + } catch (FileNotFoundException e) { + throw new SATAbortedException(e); + } catch (IOException e) { + throw new SATAbortedException(e); + } + + } + + /** + * Returns the length, in characters, of the longest possible header + * for a cnf file: p cnf Integer.MAX_VALUE Integer.MAX_VALUE + * @return the length, in characters, of the longest possible header + * for a cnf file: p cnf Integer.MAX_VALUE Integer.MAX_VALUE + */ + private static final int headerLength() { + return String.valueOf(Integer.MAX_VALUE).length()*2 + 8; + } + + /** + * Flushes the contents of the string buffer to the cnf file. + */ + private final void flush(){ + try { + cnf.writeBytes(buffer.toString()); + } catch (IOException e) { + throw new SATAbortedException(e); + } + buffer.setLength(0); + } + + /** + * {@inheritDoc} + * @see kodkod.engine.satlab.SATSolver#addClause(int[]) + */ + public boolean addClause(int[] lits) { + if (lits.length>0) { + clauses++; + if (buffer.length()>capacity) flush(); + for(int lit: lits) { + buffer.append(lit); + buffer.append(" "); + } + buffer.append("0\n"); + return true; + } + return false; + } + + /** + * @see kodkod.engine.satlab.SATSolver#addVariables(int) + */ + public void addVariables(int numVars) { + if (numVars < 0) + throw new IllegalArgumentException("vars < 0: " + numVars); + vars += numVars; + } + + /** + * @see kodkod.engine.satlab.SATSolver#free() + */ + public synchronized void free() { + try { + cnf.close(); + } catch (IOException e) { + // do nothing + } + } + + + /** + * Releases the resources used by this external solver. + */ + protected final void finalize() throws Throwable { + super.finalize(); + free(); + } + + /** + * @see kodkod.engine.satlab.SATSolver#numberOfClauses() + */ + public int numberOfClauses() { + return clauses; + } + + /** + * @see kodkod.engine.satlab.SATSolver#numberOfVariables() + */ + public int numberOfVariables() { + return vars; + } + + /** + * @ensures |lit| <= this.vars && lit != 0 => this.solution'.set(|lit|, lit>0) + * @throws RuntimeException - lit=0 || |lit|>this.vars + */ + private final void updateSolution(int lit) { + int abs = StrictMath.abs(lit); + if (abs<=vars && abs>0) + solution.set(abs-1, lit>0); + else + throw new RuntimeException("invalid variable value: |" + lit + "| !in [1.."+vars+"]"); + } + + /** Helper class that drains the stderr. */ + private final class Drainer implements Runnable { + /** The stream to drain. */ + private final InputStream input; + /** Constructor that constructs a new Drainer. */ + public Drainer(InputStream input) { + this.input=input; + } + /** The run method that keeps reading from the InputStream until end-of-file. */ + public void run() { + try { + byte[] buffer=new byte[8192]; + while(true) { + int n=input.read(buffer); + if (n<0) break; + } + } catch (IOException ex) { + } + try { input.close(); } catch(IOException ex) { } + } + } + + /** + * @see kodkod.engine.satlab.SATSolver#solve() + */ + public boolean solve() throws SATAbortedException { + if (sat==null) { + flush(); + Process p = null; + try { + cnf.seek(0); + cnf.writeBytes("p cnf " + vars + " " + clauses); + cnf.close(); + + final String[] command = new String[options.length+2]; + command[0] = executable; + System.arraycopy(options, 0, command, 1, options.length); + command[command.length-1] = inTemp; + p = Runtime.getRuntime().exec(command); + new Thread(new Drainer(p.getErrorStream())).start(); + final BufferedReader out; + if (outTemp.length()==0) { + out = new BufferedReader(new InputStreamReader(p.getInputStream(), "ISO-8859-1")); + } else { + out = new BufferedReader(new FileReader(outTemp)); + } + String line = null; + while((line = out.readLine()) != null) { + String[] tokens = line.split("\\s"); + int tlength = tokens.length; + if (tlength>0) { + if (tokens[0].compareToIgnoreCase("s")==0) { + if (tlength==2) { + if (tokens[1].compareToIgnoreCase("SATISFIABLE")==0) { + sat = Boolean.TRUE; + continue; + } else if (tokens[1].compareToIgnoreCase("UNSATISFIABLE")==0) { + sat = Boolean.FALSE; + continue; + } + } + throw new RuntimeException("invalid " + executable + " output line: " + line); + } else if (tokens[0].compareToIgnoreCase("v")==0) { + int last = tlength-1; + for(int i = 1; i < last; i++) { + updateSolution(Integer.parseInt(tokens[i])); + } + int lit = Integer.parseInt(tokens[last]); + if (lit!=0) updateSolution(lit); + else if (sat!=null) break; + } // not a solution line or a variable line, so ignore it. + } + } + try { out.close(); } catch (IOException e) { } // do nothing, we are done + if (sat==null) { + throw new RuntimeException("invalid " + executable + " output"); + } + } catch (IOException e) { + throw new SATAbortedException(e); + } catch (NumberFormatException e) { + throw new RuntimeException("invalid "+ executable +" output", e); + } + } + return sat; + } + + /** + * @see kodkod.engine.satlab.SATSolver#valueOf(int) + */ + public boolean valueOf(int variable) { + if (!Boolean.TRUE.equals(sat)) + throw new IllegalStateException(); + if (variable < 1 || variable > vars) + throw new IllegalArgumentException(variable + " !in [1.." + vars+"]"); + return solution.get(variable-1); + } + + /** + * @see java.lang.Object#toString() + */ + public String toString() { + return executable + " " + options; + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/satlab/LazyTrace.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/satlab/LazyTrace.java new file mode 100644 index 00000000..771723f6 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/satlab/LazyTrace.java @@ -0,0 +1,704 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.engine.satlab; + +import java.util.Iterator; +import java.util.NoSuchElementException; + +import kodkod.util.collections.Containers; +import kodkod.util.ints.IntBitSet; +import kodkod.util.ints.IntIterator; +import kodkod.util.ints.IntSet; +import kodkod.util.ints.Ints; + +/** + * An array-backed implementation of the {@linkplain ResolutionTrace} interface. + * Resolvent literals are computed on-demand, and only the resolvents reachable + * from the conflict clause are stored. + * + * @author Emina Torlak + */ +final class LazyTrace implements ResolutionTrace { + /* The trace array encodes the resolution trace as follows. + * The first axioms entries in the trace array contain + * the literals of the clauses added to the prover, in the order + * in which they were added. The remaining entries encode the + * resolvents as follows. Let i >= axioms + * represent the ith resolvent. If resolved.contains(i-axioms), then trace[i][0] + * contains the number of the resolvent's antecedents; trace[i][1..trace[i][0]] + * contains the indices of the resolvent's antecedents in the trace; + * and trace[i][trace[i][0]+1..trace[i].length-1] contains the literals of the ith resolvent. + * Otherwise, the literals for the given resolvent have not yet been computed so + * trace[i][0..trace[i].length-1] contains the indices of the resolvent's antecedents. + * + * All computed and axiom literals are sorted in the increasing order of absolute values. + * All antecedents of a given resolvent precede it in the trace, + * and the conflict clause should be the last trace element. + */ + private final int[][] trace; + private final int axioms; + private final IntSet core, resolved; + + /** + * Constructs a resolution trace view for the given raw trace. + * The first axioms entries in the trace array should + * contain the literals of the clauses added to the prover, in + * the order in which they were added. The literals should be + * sorted in the increasing order of absolute values. The remaining entries + * should encode the resolvents as follows. Let i be the index + * of a resolvent in the raw trace. Then, for all 0 <= j < trace[i].length, + * trace[i][j] is the index of the resolvent's jth antecedents in the trace array. + * All antecedents of a given resolvent should precede it in the trace, + * and the conflict clause should be the last trace element. + * + *

Note: the given array's contents must not be modified while + * in use by this resolution trace object.

+ */ + LazyTrace(int[][] trace, int axioms) { + this.axioms = axioms; + + // find all the clauses that are reachable from the conflict + final IntSet reachable = reachable(trace, axioms); + + // get the core clauses + this.core = core(reachable, axioms); + + // trim the trace so that it contains all axioms but only those resolvents that are reachable from the conflict + this.trace = compress(trace, axioms, reachable, core); + + // we haven't computed any resolvent literals yet ... + this.resolved = new IntBitSet(this.trace.length-axioms); + } + + + /** + * Constructs a resolution trace from the given subtrace and partial + * trace. This constructor assumes that partial is the result + * of solving the subtrace of the original trace that is given by the + * specified set of indices. The first indices.size() of the partial + * trace are assumed to represent the clauses given by original.trace[indices], + * in the increasing order of indices; the remaining entries should encode + * the resolvents computed from original.trace[indices], as specified by + * {@linkplain #LazyTrace(int[][], int)}. The given subtrace of the original + * trace must be self-contained, i.e. original.reachable(indices).equals(indices). + * + *

Note: the given array's contents must not be modified while + * in use by this resolution trace object.

+ */ + LazyTrace(LazyTrace original, IntSet indices, int[][] partial) { + this.axioms = reconstruct(original, indices, partial); + + // find all the clauses that are reachable from the conflict + final IntSet reachable = reachable(partial, axioms); + + // get the core clauses + this.core = core(reachable, axioms); + + // trim the trace so that it contains all axioms but only those resolvents that are reachable from the conflict + this.trace = compress(partial, axioms, reachable, core); + + // we haven't computed any resolvent literals yet ... + this.resolved = new IntBitSet(this.trace.length-axioms); + } + + /** + * Fills the first indices.size() empty positions of the partial trace with the + * corresponding clauses from the original trace and returns the number of axioms + * in the reconstructed trace. + * @requires original, indices, and partial are as specified by {@linkplain #LazyTrace(LazyTrace, IntSet, int[][])} constructor + * @ensures modifies partial so that it conforms to the {@linkplain #trace LazyTrace.trace} spec + * using the provided original trace and indices. + * @return number of axioms in the modified partial trace + */ + private static int reconstruct(LazyTrace original, IntSet indices, int[][] partial) { + int axiomCount = indices.size(); + // fill the partial[0..indices.size()-1] with the corresponding clauses from original.trace[indices] + final int[][] originalTrace = original.trace; + final int[] position = new int[indices.max()+1]; + + IntIterator itr = indices.iterator(); + for(int i = 0, length = indices.size(); i < length; i++) { + int index = itr.next(); + position[index] = i; + if (original.axiom(index)) { // just set the ith pointer to original literals + partial[i] = originalTrace[index]; + } else { // copy the resolvent and adjust copy's antecedent indices + int antes = originalTrace[index][0]; + int[] resolvent = new int[antes]; + for(int j = 0; j < antes; j++) { + resolvent[j] = position[originalTrace[index][j+1]]; + } + partial[i] = resolvent; + axiomCount--; + } + } + + return axiomCount; + } + + /** + * Returns the indices of all clauses in the given trace that are + * reachable from the conflict clause through the resolvents + * in trace[roots..trace.length-1]. This method assumes that + * that the last trace[roots..trace.length-1] clauses encode + * resolvents as specified by the + * {@linkplain #LazyTrace(int[][], int)} constructor. + * @return indices of all clauses in the given trace that are + * reachable from the conflict clause through the resolvents in + * trace[roots..trace.length-1] + */ + private static IntSet reachable(int[][] trace, int roots) { + final IntSet reachable = new IntBitSet(trace.length); + reachable.add(trace.length-1); + for(int i = trace.length-1; i >= roots; i--) { + if (reachable.contains(i)) { + int[] resolvent = trace[i]; + for(int j = 0; j < resolvent.length; j++) { + reachable.add(resolvent[j]); + } + } + } + return reachable; + } + + /** + * Returns a set that contains the elements from the given + * reachable between 0, inclusive, and axioms, exclusive. + * @return a set that contains the elements from the given + * reachable between 0, inclusive, and axioms, exclusive. + */ + private static IntSet core(IntSet reachable, int axioms) { + final IntSet core = new IntBitSet(axioms); + for(IntIterator itr = reachable.iterator(0, axioms-1); itr.hasNext(); ) { + core.add(itr.next()); + } + return Ints.unmodifiableIntSet(core); + } + + /** + * Compresses the given src trace into a destination trace that contains the same axioms as the + * source but only the resolvents that are reachable from the conflict clause. + * @requires src and axioms are as specified by the {@linkplain #LazyTrace(int[][], int)} constructor + * @requires reachable.elts = reachable(src, axioms).elts + * @requires core.elts = core(reachable, axioms).elts + * @ensures invalidates the contents of src; src should not be used after this method returns + * @return a new trace that contains the same axioms as the + * source but only the resolvents that are reachable from the conflict clause. + */ + private static int[][] compress(int[][] src, int axioms, IntSet reachable, IntSet core) { + final int[][] dest = new int[reachable.size()-core.size()+axioms][]; + System.arraycopy(src, 0, dest, 0, axioms); + + final int[] pos = new int[src.length-axioms]; + final IntIterator srcIdxs = reachable.iterator(axioms, src.length); + for(int i = axioms; srcIdxs.hasNext(); i++) { + int srcIdx = srcIdxs.next(); + pos[srcIdx-axioms] = i; + // move the resolvent and adjust its antecedent indices + int[] resolvent = src[srcIdx]; + dest[i] = resolvent; + for(int j = 0, lastAnte = resolvent.length; j < lastAnte; j++) { + int ante = resolvent[j]; + resolvent[j] = ante < axioms ? ante : pos[ante-axioms]; + } + } + return dest; + } + + /** + * Returns an array of integers representing the result of + * resolving the clauses c1 and c2, sorted in the increasing order + * of absolute values. The parameters axiom1 and axiom2 specify + * whether c1 and c2 encode axioms or resolvents. In particular, + * if axiom1 (resp. axiom2) is true, then all integers in c1 (resp. c2) + * are assumed to be literals, sorted in the increasing order of absolute + * values. If axiom1 (resp. axiom2) is false, then the integers starting + * at c1[0]+1 (resp. c2[0]+1) are assumed to be literals, sorted in the increasing + * order of absolute values. + * @requires let off1 = axiom1 ? 0 : c1[0] + 1, off2 = axiom2 ? 0 : c2[0]+1 | + * (all i: [off1..c1.length), j: [off1..c1.length) | i < j => abs(c1[i]) < abs(c1[j])) and + * (all i: [off2..c2.length), j: [off2..c2.length) | i < j => abs(c2[i]) < abs(c2[j])) and + * (one i: [off1..c1.length), j: [off2..c2.length) | c1[i] = -c2[j]) + * @return an array of integers representing the result of + * resolving the clauses c1 and c2, sorted in the increasing order of absolute values + */ + private static int[] resolve(int[] c1, boolean axiom1, int[] c2, boolean axiom2) { + final int len1 = c1.length, len2 = c2.length; + int i = axiom1 ? 0 : c1[0] + 1; + int j = axiom2 ? 0 : c2[0] + 1; + int k = 0; + + final int[] tmp = new int[(len1-i + len2-j) - 2]; + + while(i < len1 && j < len2) { + int lit1 = c1[i], lit2 = c2[j]; + int var1 = StrictMath.abs(lit1), var2 = StrictMath.abs(lit2); + if (var1==var2) { + if (lit1==lit2) { + tmp[k++] = lit1; + } + i++; + j++; + } else if (var1 < var2) { + tmp[k++] = lit1; + i++; + } else { // var1 > var2 + tmp[k++] = lit2; + j++; + } + } + if (i=0 && index antecedents() { return Containers.emptyIterator(); } + public IntIterator literals() { return new IntArrayIterator(literals,0,literals.length); } + public int maxVariable() { return StrictMath.abs(literals[literals.length-1]); } + public int numberOfAntecedents() { return 0; } + public int size() { return literals.length; } + public int[] toArray(int[] array) { + if (array.length iterator() { + return new ClauseIterator(new IntIterator() { + int index = 0; + public boolean hasNext() { return index>=0 && index < trace.length; } + public int next() { + if (!hasNext()) throw new NoSuchElementException(); + return index++; + } + public void remove() { throw new UnsupportedOperationException(); } + }); + } + + /** + * Returns true if indices.min() >= 0 && indices.max() < this.size() + * @requires !indices.isEmpty() + * @return indices.min() >= 0 && indices.max() < this.size() + */ + private boolean valid(IntSet indices) { + return indices.min()>=0 && indices.max() iterator(IntSet indices) { + if (indices.isEmpty() || valid(indices)) { + return new ClauseIterator(indices.iterator()); + } + throw new IndexOutOfBoundsException("invalid indices: " + indices); + } + + /** + * {@inheritDoc} + * @see kodkod.engine.satlab.ResolutionTrace#reverseIterator(kodkod.util.ints.IntSet) + */ + public Iterator reverseIterator(IntSet indices) { + if (indices.isEmpty() || valid(indices)) { + return new ClauseIterator(indices.iterator(Integer.MAX_VALUE, Integer.MIN_VALUE)); + } + throw new IndexOutOfBoundsException("invalid indices: " + indices); + } + + /** + * {@inheritDoc} + * @see kodkod.engine.satlab.ResolutionTrace#implicants(kodkod.util.ints.IntSet) + */ + public IntSet reachable(IntSet indices) { + if (indices.isEmpty()) return Ints.EMPTY_SET; + else if (valid(indices)) { + final IntSet ret = new IntBitSet(trace.length); + ret.addAll(indices); + for(int i = indices.max(); i >= axioms; i--) { + if (ret.contains(i)) { + int[] resolvent = trace[i]; + if (resolved(i)) { + for(int j = 1, antes = resolvent[0]; j <= antes; j++) { + ret.add(resolvent[j]); + } + } else { + for(int j = 0; j < resolvent.length; j++) { + ret.add(resolvent[j]); + } + } + } + } + return ret; + } + else throw new IndexOutOfBoundsException("invalid indices: " + indices); + } + + /** + * {@inheritDoc} + * @see kodkod.engine.satlab.ResolutionTrace#backwardReachable(kodkod.util.ints.IntSet) + */ + public IntSet backwardReachable(IntSet indices) { + if (indices.isEmpty()) return Ints.EMPTY_SET; + else if (valid(indices)) { + final IntSet ret = new IntBitSet(trace.length); + ret.addAll(indices); + for(int i = axioms, length = trace.length; i < length; i++) { + int[] resolvent = trace[i]; + if (resolved(i)) { + for(int j = 1, antes = resolvent[0]; j <= antes; j++) { + if (ret.contains(resolvent[j])) { + ret.add(i); + break; + } + } + } else { + for(int j = 0; j < resolvent.length; j++) { + if (ret.contains(resolvent[j])) { + ret.add(i); + break; + } + } + } + } + return ret; + } + else throw new IndexOutOfBoundsException("invalid indices: " + indices); + } + + /** + * {@inheritDoc} + * @see kodkod.engine.satlab.ResolutionTrace#learnable(kodkod.util.ints.IntSet) + */ + public IntSet learnable(IntSet indices) { + if (indices.isEmpty()) return Ints.EMPTY_SET; + else if (valid(indices)) { + final IntSet ret = new IntBitSet(trace.length); + ret.addAll(indices); + TOP: for(int i = axioms, length = trace.length; i < length; i++) { + int[] resolvent = trace[i]; + if (resolved(i)) { + for(int j = 1, antes = resolvent[0]; j <= antes; j++) { + if (!ret.contains(resolvent[j])) { + continue TOP; + } + } + } else { + for(int j = 0; j < resolvent.length; j++) { + if (!ret.contains(resolvent[j])) { + continue TOP; + } + } + } + ret.add(i); + } + return ret; + } + else throw new IndexOutOfBoundsException("invalid indices: " + indices); + } + + /** + * {@inheritDoc} + * @see kodkod.engine.satlab.ResolutionTrace#directlyLearnable(kodkod.util.ints.IntSet) + */ + public IntSet directlyLearnable(IntSet indices) { + if (indices.isEmpty()) return Ints.EMPTY_SET; + else if (valid(indices)) { + final IntSet ret = new IntBitSet(trace.length); + ret.addAll(indices); + TOP: for(int i = axioms, length = trace.length; i < length; i++) { + int[] resolvent = trace[i]; + if (resolved(i)) { + for(int j = 1, antes = resolvent[0]; j <= antes; j++) { + if (!indices.contains(resolvent[j])) { + continue TOP; + } + } + } else { + for(int j = 0; j < resolvent.length; j++) { + if (!indices.contains(resolvent[j])) { + continue TOP; + } + } + } + ret.add(i); + } + return ret; + } + + else throw new IndexOutOfBoundsException("invalid indices: " + indices); + } + + /** + * {@inheritDoc} + * @see java.lang.Object#toString() + */ + public String toString() { + final StringBuilder ret = new StringBuilder(); + for(int i = 0; i < axioms; i++) { + ret.append("AXIOM. Literals: "); + int[] clause = trace[i]; + for(int j = 0, c = clause.length; j < c; j++) { + ret.append(clause[j]); + ret.append(" "); + } + ret.append("\n"); + } + for(int i = axioms, max = trace.length; i < max; i++) { + ret.append("RESOLVENT. Antecedents: "); + int[] clause = trace[i]; + if (resolved(i)) { + for(int j = 1, c = clause[0]; j <= c; j++) { + ret.append(clause[j]); + ret.append(" "); + } + } else { + for(int j = 0; j < clause.length; j++) { + ret.append(clause[j]); + ret.append(" "); + } + } + ret.append("\n"); + } + return ret.toString(); + } + + /** + * A mutable implementation of the Clause interface. + * @author Emina Torlak + */ + private class ClauseView extends Clause { + private int[] clause; + private int litOffset, index; + + /** + * Constructs a clause view for the ith clause. + * @requires 0 <= index < trace.length + */ + ClauseView(int index) { + this.index = index; + this.clause = trace[index]; + this.litOffset = litOffset(index); + } + + /** + * Constructs a clause view for the 0th clause. + */ + ClauseView() { this(0); } + + /** + * Sets the state of this clause view to represent + * the ith clause in the trace and returns this. + * @ensures sets the state of this clause view to represent + * the ith clause in the trace + * @return this + */ + ClauseView set(int index) { + this.index = index; + this.clause = trace[index]; + this.litOffset = litOffset(index); + return this; + } + void ensureLiterals() { + if (litOffset<0) { + resolve(index); + this.clause = trace[index]; + this.litOffset = litOffset(index); + } + } + public int maxVariable() { + ensureLiterals(); + return StrictMath.abs(clause[clause.length-1]); + } + public int numberOfAntecedents() { + return litOffset<0 ? clause.length : StrictMath.max(0, litOffset-1); + } + public int size() { + ensureLiterals(); + return clause.length - litOffset; + } + public Iterator antecedents() { + return new ClauseIterator(new IntArrayIterator(clause, 1, litOffset)); + } + public IntIterator literals() { + ensureLiterals(); + return new IntArrayIterator(clause, litOffset, clause.length); + } + public int[] toArray(int[] array) { + final int size = size(); + if (array.length < size) { + array = new int[size]; + } + System.arraycopy(clause, litOffset, array, 0, size); + return array; + } + } + + /** + * A clause iterator wrapper for an int iterator. + * @author Emina Torlak + */ + private final class ClauseIterator extends ClauseView implements Iterator { + private final IntIterator itr; + /** + * Constructs a clause iterator that will iterate over the clauses in this.trace + * located at the indices given by itr. The given iterator must return valid indices. + */ + ClauseIterator(IntIterator itr) { + this.itr = itr; + } + public boolean hasNext() { return itr.hasNext(); } + public Clause next() { return set(itr.next()); } + public void remove() { throw new UnsupportedOperationException(); } + } + + /** + * An int iterator that iterates over the portion of an integer array + * in the increasing order of indices. + * @author Emina Torlak + */ + private static final class IntArrayIterator implements IntIterator { + private final int[] array; + private int from; + private final int to; + /** + * Constructs an int iterator that iterates over the given array, + * returning the elements between from, inclusive, and to, exclusive. + * @requires 0 <= from < array.length < Integer.MAX_VALUE + */ + IntArrayIterator(int[] array, int from, int to) { + this.array = array; + this.from = from; + this.to = to; + } + public boolean hasNext() { return from >= 0 && from < to; } + public int next() { + if (!hasNext()) throw new NoSuchElementException(); + return array[from++]; + } + public void remove() { throw new UnsupportedOperationException(); } + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/satlab/MiniSat.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/satlab/MiniSat.java new file mode 100644 index 00000000..5fc65c50 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/satlab/MiniSat.java @@ -0,0 +1,86 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.engine.satlab; + + +/** + * Java wrapper for Niklas En and Niklas Srensson MiniSAT solver. + * @author Emina Torlak + */ +final class MiniSat extends NativeSolver { + + /** + * Constructs a new MiniSAT wrapper. + */ + public MiniSat() { + super(make()); + } + + static { + loadLibrary("minisat"); + } + + + /** + * {@inheritDoc} + * @see java.lang.Object#toString() + */ + public String toString() { + return "MiniSat"; + } + + /** + * Returns a pointer to an instance of MiniSAT. + * @return a pointer to an instance of minisat. + */ + private static native long make(); + + /** + * {@inheritDoc} + * @see kodkod.engine.satlab.NativeSolver#free(long) + */ + native void free(long peer); + + /** + * {@inheritDoc} + * @see kodkod.engine.satlab.NativeSolver#addVariables(long, int) + */ + native void addVariables(long peer, int numVariables); + + /** + * {@inheritDoc} + * @see kodkod.engine.satlab.NativeSolver#addClause(long, int[]) + */ + native boolean addClause(long peer, int[] lits); + + /** + * {@inheritDoc} + * @see kodkod.engine.satlab.NativeSolver#solve(long) + */ + native boolean solve(long peer); + + /** + * {@inheritDoc} + * @see kodkod.engine.satlab.NativeSolver#valueOf(long, int) + */ + native boolean valueOf(long peer, int literal); +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/satlab/MiniSatExternal.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/satlab/MiniSatExternal.java new file mode 100644 index 00000000..81b32a36 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/satlab/MiniSatExternal.java @@ -0,0 +1,223 @@ +package kodkod.engine.satlab; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; + +/** + * Runs an external version of minisat (whichever "minisat" is found in the PATH) + * + * @author aleks + */ +public class MiniSatExternal implements SATSolver { + + private File cnfFile; + private OutputStream cnfOut; + + private int vars = 0; + private int clauses = 0; + private boolean freed = false; + + private boolean sat = false; + private boolean[] model; + + public MiniSatExternal() { + init(); + } + + private void init() { + try { + cnfFile = File.createTempFile("kk_minisat", ".cnf"); + cnfOut = new BufferedOutputStream(new FileOutputStream(cnfFile)); + } catch (IOException e) { + throw new RuntimeException("Could not create cnf file: " + cnfFile.getAbsolutePath(), e); + } + } + + @Override + public boolean addClause(int[] lits) { + try { + clauses++; + StringBuilder sb = new StringBuilder(); + for (int l : lits) sb.append(l + " "); + sb.append("0\n"); + cnfOut.write(sb.toString().getBytes()); + return false; + } catch (IOException e) { + throw new RuntimeException("Could not write to cnf file " + cnfFile.getAbsolutePath(), e); + } + } + + @Override + public void addVariables(int numVars) { + assert numVars >= 0; + vars += numVars; + } + + @Override + public void free() { + try { + if (!freed) { + cnfOut.flush(); + cnfOut.close(); + cnfFile.deleteOnExit(); + freed = true; + } + } catch (IOException e) { + throw new RuntimeException("Could not close cnf output stream"); + } + } + + @Override + public int numberOfClauses() { + return clauses; + } + + @Override + public int numberOfVariables() { + return vars; + } + + @Override + public boolean solve() throws SATAbortedException { + finalizeCnf(); + File modelFile = runMinisat(); + readModel(modelFile); + if (sat) + assert checkModel(); + return sat; + } + + private boolean checkModel() { + BufferedReader br = null; + try { + br = new BufferedReader(new InputStreamReader(new FileInputStream(cnfFile))); + String line = br.readLine(); //header + line = br.readLine(); + while (line != null) { + String[] clause = line.trim().split("\\ "); + int i; + for (i = 0; i < clause.length - 1; i++) { + int lit = Integer.parseInt(clause[i]); + if ((lit > 0 ? valueOf(lit) : !valueOf(Math.abs(lit)))) + break; + } + if (i == clause.length - 1) + return false; + line = br.readLine(); + } + return true; + } catch (IOException e) { + throw new RuntimeException(e); + } finally { + try { + if (br != null) br.close(); + } catch (IOException e) { + } + } + } + + @Override + public boolean valueOf(int variable) { + if (!sat) + throw new RuntimeException("UNSAT"); + return model[variable-1]; + } + + private void finalizeCnf() { + try { + free(); + BufferedInputStream in = new BufferedInputStream(new FileInputStream(cnfFile)); + File tmp = new File(cnfFile + ".tmp"); + BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(tmp)); + byte[] header = String.format("p cnf %s %s\n", vars, clauses).getBytes(); + out.write(header); + int ch; + while ((ch = in.read()) != -1) { + out.write(ch); + } + in.close(); + out.close(); + tmp.renameTo(cnfFile); + } catch (IOException e) { + throw new RuntimeException("could not preprend header to cnf file: " + cnfFile.getAbsolutePath(), e); + } + } + + private File runMinisat() { + try { + File modelFile = File.createTempFile("minisat_model", ".txt"); + modelFile.deleteOnExit(); + String cmd = String.format("minisat -verb=0 %s %s", cnfFile.getAbsolutePath(), modelFile.getAbsolutePath()); + Process p = Runtime.getRuntime().exec(cmd); + int retVal = p.waitFor(); + if (retVal == 0) + throw new RuntimeException("couldn't run shell command: " + cmd); + return modelFile; + } catch (Exception e) { + throw new RuntimeException("error during solving", e); + } + } + + private void readModel(File modelFile) { + try { + BufferedInputStream in = new BufferedInputStream(new FileInputStream(modelFile)); + String line = readLine(in).trim(); + if (!"SAT".equals(line)) { + sat = false; + model = null; + return; + } + sat = true; + model = new boolean[vars]; + int chCode; + int cnt = 0; + int curr = 0; + boolean sign = true; + while ((chCode = in.read()) != -1) { + char ch = (char)chCode; + switch (ch) { + case '-': + sign = false; + break; + case ' ': case '\n': + if (curr == 0) + break; + assert curr == cnt + 1; + model[cnt] = sign; + cnt++; + curr = 0; + sign = true; + break; + default: + int x = ch - '0'; + assert x >= 0 && x <= 9; + curr = 10*curr + x; + break; + } + } + in.close(); + } catch (IOException e) { + throw new RuntimeException("couldn't read model file: " + model); + } + } + + private String readLine(InputStream in) throws IOException { + StringBuilder sb = new StringBuilder(); + while (true) { + int ch = in.read(); + if (ch == -1) break; + if (ch == '\n') break; + sb.append((char)ch); + } + return sb.toString(); + } + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/satlab/MiniSatProver.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/satlab/MiniSatProver.java new file mode 100644 index 00000000..822533bf --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/satlab/MiniSatProver.java @@ -0,0 +1,201 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.engine.satlab; + +import java.util.Iterator; + +import kodkod.util.ints.IntBitSet; +import kodkod.util.ints.IntIterator; +import kodkod.util.ints.IntSet; + +/** + * Java wrapper for Niklas En and Niklas Srensson MiniSAT solver + * with proof logging. + * @author Emina Torlak + */ +final class MiniSatProver extends NativeSolver implements SATProver { + private LazyTrace proof; + /** + * Constructs a new MiniSat prover wrapper. + */ + MiniSatProver() { + super(make()); + proof = null; + } + + /** + * Modifies the given raw trace so that it conforms to the + * specification of {@linkplain ArrayTrace#ArrayTrace(int[][], int)}, + * if the array contains no null entries, and to the specfication of + * {@linkplain ArrayTrace#ArrayTrace(ArrayTrace, IntSet, int[][])} + * otherwise. + * @ensures modifies the trace so that it conforms to the specification + * of one of the ArrayTrace constructors. + * @return trace + */ + private int[][] format(int[][] trace) { + final int length = trace.length; + final IntSet resolvents = new IntBitSet(length); + final int offset = numberOfVariables() + 1; + for(int i = 0; i < length; i++) { + int[] clause = trace[i]; + if (clause!=null && clause[0]>=offset) { + clause[0] -= offset; + resolvents.add(i); + } + } + + final int axioms = length - resolvents.size(); + if (resolvents.min() itr = proof.iterator(next); itr.hasNext();) { + Clause c = itr.next(); + if (!addClause(prover, c.toArray())) { + throw new AssertionError("could not add non-redundant clause: " + c); + } + } + + if (!solve(prover)) { + adjustClauseCount(next.size()); + int[][] trace = trace(prover, false); + free(prover); + proof = new LazyTrace(proof, next, format(trace)); + } else { + free(prover); + } + } + } + + /** + * {@inheritDoc} + * @see java.lang.Object#toString() + */ + public String toString() { + return "MiniSatProver"; + } + + + static { + loadLibrary("minisatprover"); + } + + /** + * Returns a pointer to an instance of MiniSAT. + * @return a pointer to an instance of minisat. + */ + private static native long make(); + + /** + * {@inheritDoc} + * @see kodkod.engine.satlab.NativeSolver#free(long) + */ + native void free(long peer); + + /** + * {@inheritDoc} + * @see kodkod.engine.satlab.NativeSolver#addVariables(long, int) + */ + native void addVariables(long peer, int numVariables); + + /** + * {@inheritDoc} + * @see kodkod.engine.satlab.NativeSolver#addClause(long, int[]) + */ + native boolean addClause(long peer, int[] lits); + + /** + * {@inheritDoc} + * @see kodkod.engine.satlab.NativeSolver#solve(long) + */ + native boolean solve(long peer); + + /** + * {@inheritDoc} + * @see kodkod.engine.satlab.NativeSolver#valueOf(long, int) + */ + native boolean valueOf(long peer, int literal); + + /** + * Returns an array of arrays that encodes the most recently generated + * resolution trace. The resolution trace is encoded as follows. Let + * R be the returned array. For all 0 <= i < trace.length such that + * R[i][0] > this.numberOfVariables(), the array R[i] encodes a + * resolvent clause. In particular, (R[i][0] - this.numberOfVariables() - 1) < i + * is the index of the 0th antecedent of R[i] in R; for each 0 < j < R[i].length, + * R[i][j] < i and R[i][j] is the index of the jth antecedent of R[i] in R. + * For all 0 <= i < trace.length-1 such that + * R[i][0] <= this.numberOfVariables(), R[i] contains the literals of the ith axiom, + * sorted in the increasing order of absolute values, if recordAxioms is true; otherwise R[i] is null. + * @return an array of arrays that encodes the most recently generated resolution trace + */ + native int[][] trace(long peer, boolean recordAxioms); +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/satlab/NativeSolver.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/satlab/NativeSolver.java new file mode 100644 index 00000000..f48454b3 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/satlab/NativeSolver.java @@ -0,0 +1,255 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.engine.satlab; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Arrays; + +import kodkod.engine.config.Options; + +/** + * A skeleton implementation of a wrapper for a sat solver + * accessed through JNI. + * + * @author Emina Torlak + */ +abstract class NativeSolver implements SATSolver { + /** + * The memory address of the native instance wrapped by this wrapper. + */ + private long peer; + private Boolean sat; + private int clauses, vars; + + private OutputStream cnfFile; + + /** + * Constructs a new wrapper for the given + * instance of the native solver. + */ + NativeSolver(long peer) { + this.peer = peer; + this.clauses = this.vars = 0; + this.sat = null; + try { + if (Options.isDebug()) + cnfFile = new BufferedOutputStream(new FileOutputStream(new File(System.getProperty("java.io.tmpdir"), "cnf_kk.cnf"))); + } catch (IOException e) { + } + } + + /** + * Loads the JNI library with the given name. + */ + static void loadLibrary(String library) { + try { System.loadLibrary(library); return; } catch(UnsatisfiedLinkError ex) { } + try { System.loadLibrary(library+"x1"); return; } catch(UnsatisfiedLinkError ex) { } + try { System.loadLibrary(library+"x2"); return; } catch(UnsatisfiedLinkError ex) { } + try { System.loadLibrary(library+"x3"); return; } catch(UnsatisfiedLinkError ex) { } + try { System.loadLibrary(library+"x4"); return; } catch(UnsatisfiedLinkError ex) { } + System.loadLibrary(library+"x5"); + } + + /** + * {@inheritDoc} + * @see kodkod.engine.satlab.SATSolver#numberOfVariables() + */ + public final int numberOfVariables() { + return vars; + } + + /** + * {@inheritDoc} + * @see kodkod.engine.satlab.SATSolver#numberOfClauses() + */ + public final int numberOfClauses() { + return clauses; + } + + /** + * Adjusts the internal clause count so that the next call to {@linkplain #numberOfClauses()} + * will return the given value. + * @requires clauseCount >= 0 + * @ensures adjusts the internal clause so that the next call to {@linkplain #numberOfClauses()} + * will return the given value. + */ + void adjustClauseCount(int clauseCount) { + assert clauseCount >= 0; + clauses = clauseCount; + } + + /** + * {@inheritDoc} + * @see kodkod.engine.satlab.SATSolver#addVariables(int) + * @see #addVariables(long, int) + */ + public final void addVariables(int numVars) { + if (numVars < 0) + throw new IllegalArgumentException("vars < 0: " + numVars); + else if (numVars > 0) { + vars += numVars; + addVariables(peer, numVars); + } + } + + /** + * {@inheritDoc} + * @see kodkod.engine.satlab.SATSolver#addClause(int[]) + * @see #addClause(long, int[]) + */ + public final boolean addClause(int[] lits) { + if (lits.length > 0) { + if (addClause(peer, lits)) { + clauses++; + try { + if (Options.isDebug()) cnfFile.write((Arrays.toString(lits) + " 0\n").getBytes()); + } catch (IOException e) {} + return true; + } + } + return false; + } + + + /** + * Returns a pointer to the C++ peer class (the native instance wrapped by this object). + * @return a pointer to the C++ peer class (the native instance wrapped by this object). + */ + final long peer() { return peer; } + + /** + * Returns the current sat of the solver. + * @return null if the sat is unknown, TRUE if the last + * call to solve() yielded SAT, and FALSE if the last call to + * solve() yielded UNSAT. + */ + final Boolean status() { return sat; } + + /** + * {@inheritDoc} + * @see kodkod.engine.satlab.SATSolver#solve() + * @see #solve(long) + */ + public final boolean solve() { + try { + if (Options.isDebug()) { + cnfFile.write(String.format("p cnf %s %s\n", vars, clauses).getBytes()); + cnfFile.flush(); + cnfFile.close(); + } + } catch (IOException e) { + } + return (sat = solve(peer)); + } + + + /** + * Throws an IllegalArgumentException if variable !in this.variables. + * Otherwise does nothing. + * @throws IllegalArgumentException - variable !in this.variables + */ + final void validateVariable(int variable) { + if (variable < 1 || variable > vars) + throw new IllegalArgumentException(variable + " !in [1.." + vars+"]"); + } + + /** + * {@inheritDoc} + * @see kodkod.engine.satlab.SATSolver#valueOf(int) + */ + public final boolean valueOf(int variable) { + if (!Boolean.TRUE.equals(sat)) + throw new IllegalStateException(); + validateVariable(variable); + return valueOf(peer, variable); + } + + /** + * {@inheritDoc} + * @see kodkod.engine.satlab.SATSolver#free() + */ + public synchronized final void free() { + if (peer!=0) { +// System.out.println("freeing " + peer + " " + getClass()); + free(peer); + peer = 0; + } // already freed + } + + + /** + * Releases the resources used by this native solver. + */ + protected final void finalize() throws Throwable { + super.finalize(); + free(); + } + + /** + * Releases the resources associated with + * the native solver at the given memory address. + * This method must be called when the object holding the + * given reference goes out of scope to avoid + * memory leaks. + * @ensures releases the resources associated + * with the given native peer + */ + abstract void free(long peer); + + /** + * Adds the specified number of variables to the given native peer. + * @ensures increases the vocabulary of the given native peer by + * the specified number of variables + */ + abstract void addVariables(long peer, int numVariables); + + /** + * Ensures that the given native peer logically contains the + * specified clause and returns true if the solver's clause database + * changed as a result of the call. + * @requires all i: [0..lits.length) | abs(lits[i]) in this.variables + * @requires all disj i,j: [0..lits.length) | abs(lits[i]) != abs(lits[j]) + * @ensures ensures that the given native peer logically contains the specified clause + * @return true if the peer's clause database changed as a result of the call; a negative integer if not. + */ + abstract boolean addClause(long peer, int[] lits); + + /** + * Calls the solve method on the given native peer. + * @return true if the clauses in the solver are SAT; + * otherwise returns false. + */ + abstract boolean solve(long peer); + + /** + * Returns the assignment for the given literal + * by the specified native peer + * @requires the last call to {@link #solve(long) solve(peer)} returned SATISFIABLE + * @return the assignment for the given literal + */ + abstract boolean valueOf(long peer, int literal); + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/satlab/ReductionStrategy.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/satlab/ReductionStrategy.java new file mode 100644 index 00000000..efed092d --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/satlab/ReductionStrategy.java @@ -0,0 +1,65 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.engine.satlab; + +import kodkod.util.ints.IntSet; + + +/** + * Strategy for reducing the unsatisfiable core of + * a {@link SATProver}. + * @specfield traces: ResolutionTrace[] + * @specfield nexts: IntSet[] + * @invariant #traces = #nexts + * @invariant no disj i,j: [0..#nexts) | traces[i] = traces[j] && nexts[i] = nexts[j] + * @see SATProver#reduce(ReductionStrategy) + * @author Emina Torlak + */ +public interface ReductionStrategy { + + /** + * Returns the next subtrace of the specified trace to be analyzed, given + * as a set of indices into the trace. + * If there are no more subtraces to be analyzed (i.e. the given trace is + * minimal according to the minimality measure used by this strategy), + * returns the empty set. + * @requires + *
+	 * let t = this.traces[#this.traces-1], n = this.nexts[#this.nexts-1] | 
+	 *  unsat(t.elts[n].literals) => 
+	 *   (all i: n.ints | let j = #{k: n.ints | k < i} | t.elts[i].equals(trace.elts[j])) 
+	 *  else
+	 *   trace = t
+	 * 
+ * @ensures + *
 
+	 *  let next = { i: int | 0 <= i < trace.size()-1 } |
+	 *   trace.elts[next].antecedents in trace.elts[next] and 
+	 *   (some i: [0..#trace) | i !in next and no trace[i].antecedents) and  
+	 *   this.nexts' = this.nexts + #this.nexts->next and
+	 *   this.traces' = this.traces + #this.traces->trace 
+	 * 
+ * @return this.nexts'[#this.nexts-1] + */ + public IntSet next(ResolutionTrace trace); + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/satlab/ResolutionTrace.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/satlab/ResolutionTrace.java new file mode 100644 index 00000000..abfeeeb8 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/satlab/ResolutionTrace.java @@ -0,0 +1,178 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.engine.satlab; + +import java.util.Iterator; + +import kodkod.util.ints.IntSet; + + +/** + *

A proof of unsatisfiability generated by a {@linkplain SATProver}. + * Formally, a resolution trace is a sequence of inferences made by a + * prover that ends in a conflict (empty clause). An element in a resolution + * trace is called a clause. There are two kinds of clauses in the trace: + * axioms and resolvents. Axioms are the clauses given to the + * prover via the {@linkplain SATSolver#addClause(int[])} method, and resolvents are + * the clauses derived by the prover from axioms or previously learned resolvents through + * resolution.

+ * + *

Clauses in a resolution trace are ordered as follows. The first |A| elements + * in the trace correspond to the axioms given to the prover (i.e. prover.clauses). + * An axiom a1 precedes an axiom a2 in the trace if and only if + * a1 was added to the prover before a2. (An axiom is "added" to the + * prover, and appears in the trace, if and only if the corresponding call to {@linkplain SATSolver#addClause(int[])}) + * returned true.) The remaining elements in the trace + * are the resolvents. A resolvent r succeeds all of the resolvents needed for its derivation + * (i.e. all resolvents reachable from r via the {@linkplain Clause#antecedents()} relation). + * The last element in the trace is the conflict resolvent. The axioms that are reachable from the conflict + * form the unsatisfiable core of the trace.

+ * + * @specfield prover: SATProver + * @specfield elts: Clause[] + * @specfield conflict: elts[#elts-1] + * @invariant #elts = #(prover.clauses + prover.resolvents) + * @invariant elts[[0..#prover.clauses)].literals = prover.clauses + * @invariant elts[[#prover.clauses..#elts)].literals = prover.resolvents + * @invariant all i: [0..#prover.clauses) | no elts[i].antecedents + * @invariant all i: [#prover.clauses..#elts) | all ante: elts[i].antecedents[int] | elts.ante < i + * @invariant no conflict.literals + * + * @author Emina Torlak + */ +public interface ResolutionTrace extends Iterable { + + /** + * Returns the length of this trace. + * @return #this.elts + */ + public int size(); + + /** + * Returns an iterator over the elements in this trace in proper sequence. + *

Note:The clause objects returned by the iterator are not + * required to be immutable. In particular, the state of a clause object + * returned by next() (as well as the state of any object obtained + * through that clause's {@linkplain Clause#antecedents()} method) is guaranteed + * to remain the same only until the subsequent call to the next() method + * of the iterator instance.

+ * @return an iterator over the elements in this trace in proper sequence. + */ + public abstract Iterator iterator(); + + /** + * Returns an iterator over the elements at the given indices in this trace, in proper sequence. + *

Note:The clause objects returned by the iterator are not + * required to be immutable. In particular, the state of a clause object + * returned by next() (as well as the state of any object obtained + * through that clause's {@linkplain Clause#antecedents()} method) is guaranteed + * to remain the same only until the subsequent call to the next() method + * of the iterator instance.

+ * @requires indices.min() >= 0 && indices.max() < this.size() + * @return an iterator over the elements at the given indices in this trace, in proper sequence. + * @throws IndexOutOfBoundsException - indices.min() < 0 || indices.max() >= this.size() + */ + public abstract Iterator iterator(IntSet indices); + + /** + * Returns an iterator over the elements at the given indices in this trace, in the + * reverse order of indices. + *

Note:The clause objects returned by the iterator are not + * required to be immutable. In particular, the state of a clause object + * returned by next() (as well as the state of any object obtained + * through that clause's {@linkplain Clause#antecedents()} method) is guaranteed + * to remain the same only until the subsequent call to the next() method + * of the iterator instance.

+ * @requires indices.min() >= 0 && indices.max() < this.size() + * @return an iterator over the elements at the given indices in this trace, in the + * reverse order of indices. + * @throws IndexOutOfBoundsException - indices.min() < 0 || indices.max() >= this.size() + */ + public abstract Iterator reverseIterator(IntSet indices); + + /** + * Returns the indices of the axioms that form the unsatisfiable core of this trace. + * @return { i: int | no this.elts[i].antecedents and this.elts[i] in this.conflict.^antecedents } + */ + public abstract IntSet core(); + + /** + * Returns the indices of the axioms in this trace. + * @return { i: int | this.elts[i] in this.prover.clauses } + */ + public abstract IntSet axioms(); + + /** + * Returns the indices of the resolvents in this trace. + * @return { i: int | this.elts[i] in this.prover.resolvents } + */ + public abstract IntSet resolvents(); + + /** + * Returns the indices of all clauses reachable from the clauses at the given indices + * by following the antecedent relation zero or more times. + * @requires indices.min() >= 0 && indices.max() < this.size() + * @return { i: int | this.elts[i] in this.elts[indices].*antecedents } + * @throws IllegalArgumentException - indices.min() < 0 || indices.max() >= this.size() + */ + public abstract IntSet reachable(IntSet indices); + + /** + * Returns the indices of all clauses reachable from the clauses at the given indices + * by following the transpose of the antecedent relation zero or more times. + * @requires indices.min() >= 0 && indices.max() < this.size() + * @return { i: int | this.elts[i] in this.elts[indices].*~antecedents } + * @throws IllegalArgumentException - indices.min() < 0 || indices.max() >= this.size() + */ + public abstract IntSet backwardReachable(IntSet indices); + + /** + * Returns the indices of all clauses in this trace that can be learned solely from the + * clauses with the given indices. + * @requires indices.min() >= 0 && indices.max() < this.size() + * @return { i: int | this.elts[i].*antecedents = this.elts[indices].*antecedents + this.elts[i].*antecedents & this.elts[indices].*~antecedents } + * @throws IllegalArgumentException - indices.min() < 0 || indices.max() >= this.size() + */ + public abstract IntSet learnable(IntSet indices); + + /** + * Returns the indices of all clauses in this trace that can be learned solely and directly from the + * clauses with the given indices. + * @requires indices.min() >= 0 && indices.max() < this.size() + * @return { i: int | this.elts[i].antecedents in this.elts[indices] } + * @throws IllegalArgumentException - indices.min() < 0 || indices.max() >= this.size() + */ + public abstract IntSet directlyLearnable(IntSet indices); + + + /** + * Returns the clause at the given index. Note that this method is not required + * to always return the same Clause object; it is only required to return Clause + * objects that are equal according to their equals methods. The Clause + * objects returned by this method are guaranteed to be immutable. + * @requires 0 <= index < this.size() + * @return this.elts[index] + * @throws IndexOutOfBoundsException - 0 < index || index >= this.size() + */ + public abstract Clause get(int index); + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/satlab/SAT4J.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/satlab/SAT4J.java new file mode 100644 index 00000000..dc07ccd6 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/satlab/SAT4J.java @@ -0,0 +1,356 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.engine.satlab; + +import java.util.NoSuchElementException; +import org.sat4j.specs.ContradictionException; +import org.sat4j.specs.ISolver; +import org.sat4j.specs.IVecInt; +import org.sat4j.specs.IteratorInt; + +/** + * A wrapper class that provides + * access to the basic funcionality of the MiniSAT solvers + * (org.sat4j.specs.ISolver) from CRIL. + * + * @author Emina Torlak + */ +final class SAT4J implements SATSolver { + private ISolver solver; + private final ReadOnlyIVecInt wrapper; + private Boolean sat; + private int vars, clauses; + + /** + * Constructs a wrapper for the given instance + * of ISolver. + * @throws NullPointerException - solver = null + */ + SAT4J(ISolver solver) { + if (solver==null) + throw new NullPointerException("solver"); + this.solver = solver; + this.wrapper = new ReadOnlyIVecInt(); + this.sat = null; + this.vars = this.clauses = 0; + } + + /** + * {@inheritDoc} + * @see kodkod.engine.satlab.SATSolver#numberOfVariables() + */ + public int numberOfVariables() { + return vars; + } + + /** + * {@inheritDoc} + * @see kodkod.engine.satlab.SATSolver#numberOfClauses() + */ + public int numberOfClauses() { + return clauses; + } + + /** + * {@inheritDoc} + * @see kodkod.engine.satlab.SATSolver#addVariables(int) + */ + public void addVariables(int numVars) { + if (numVars < 0) + throw new IllegalArgumentException("numVars < 0: " + numVars); + else if (numVars > 0) { + vars += numVars; + solver.newVar(vars); + } + } + + /** + * {@inheritDoc} + * @see kodkod.engine.satlab.SATSolver#addClause(int[]) + */ + public boolean addClause(int[] lits) { + try { + if (!Boolean.FALSE.equals(sat)) { + clauses++; + solver.addClause(wrapper.wrap(lits)); +// for(int lit : lits) { +// System.out.print(lit + " "); +// } +// System.out.println(0); + return true; + } + + } catch (ContradictionException e) { + sat = Boolean.FALSE; + } + return false; + } + + /** + * {@inheritDoc} + * @see kodkod.engine.satlab.SATSolver#solve() + */ + public boolean solve() { + try { + if (!Boolean.FALSE.equals(sat)) + sat = Boolean.valueOf(solver.isSatisfiable()); + return sat; + } catch (org.sat4j.specs.TimeoutException e) { + throw new RuntimeException("timed out"); + } + } + + /** + * {@inheritDoc} + * @see kodkod.engine.satlab.SATSolver#valueOf(int) + */ + public final boolean valueOf(int variable) { + if (!Boolean.TRUE.equals(sat)) + throw new IllegalStateException(); + if (variable < 1 || variable > vars) + throw new IllegalArgumentException(variable + " !in [1.." + vars+"]"); + return solver.model(variable); + } + + /** + * {@inheritDoc} + * @see kodkod.engine.satlab.SATSolver#free() + */ + public synchronized final void free() { + solver = null; + } + + /** + * A wrapper for an int array that provides + * read-only access to the array via the IVecInt interface. + * + * @author Emina Torlak + */ + private static final class ReadOnlyIVecInt implements IVecInt { + private static final long serialVersionUID = 1451465675531453944L; + + private int[] vec; + + /** + * Sets this.vec to the given vector + * and returns this. + */ + IVecInt wrap(int[] vec) { + this.vec = vec; + return this; + } + + public int size() { + return vec.length; + } + + public boolean isEmpty() { + return size() == 0; + } + + public void shrink(int arg0) { + throw new UnsupportedOperationException(); + } + + public void shrinkTo(int arg0) { + throw new UnsupportedOperationException(); + } + + public IVecInt pop() { + throw new UnsupportedOperationException(); + } + + public void growTo(int arg0, int arg1) { + throw new UnsupportedOperationException(); + } + + public void ensure(int arg0) { + throw new UnsupportedOperationException(); + } + + public IVecInt push(int arg0) { + throw new UnsupportedOperationException(); + } + + public void unsafePush(int arg0) { + throw new UnsupportedOperationException(); + } + + public int unsafeGet(int arg0) { + return vec[arg0]; + } + + public void clear() { + throw new UnsupportedOperationException(); + } + + public int last() { + return vec[vec.length - 1]; + } + + public int get(int arg0) { + if (arg0 < 0 || arg0 >= vec.length) + throw new IndexOutOfBoundsException("arg0: " + arg0); + return vec[arg0]; + } + + public void set(int arg0, int arg1) { + throw new UnsupportedOperationException(); + } + + public boolean contains(int arg0) { + for(int i : vec) { + if (i==arg0) return true; + } + return false; + } + + public void copyTo(IVecInt arg0) { + int argLength = arg0.size(); + arg0.ensure(argLength + vec.length); + for(int i : vec) { + arg0.set(argLength++, i); + } + } + + public void copyTo(int[] arg0) { + assert arg0.length >= vec.length; + System.arraycopy(vec,0, arg0, 0, vec.length); + } + + public void moveTo(IVecInt arg0) { + throw new UnsupportedOperationException(); + } + + public void moveTo2(IVecInt arg0) { + throw new UnsupportedOperationException(); + } + + public void moveTo(int[] arg0) { + throw new UnsupportedOperationException(); + } + + public void moveTo(int arg0, int arg1) { + throw new UnsupportedOperationException(); + } + + public void insertFirst(int arg0) { + throw new UnsupportedOperationException(); + } + + public void remove(int arg0) { + throw new UnsupportedOperationException(); + } + + public int delete(int arg0) { + throw new UnsupportedOperationException(); + } + + public void sort() { + throw new UnsupportedOperationException(); + } + + public void sortUnique() { + throw new UnsupportedOperationException(); + } + + public IteratorInt iterator() { + return new IteratorInt() { + int cursor = 0; + public boolean hasNext() { + return cursor < vec.length; + } + public int next() { + if (!hasNext()) + throw new NoSuchElementException(); + return vec[cursor++]; + } + }; + } + + public int containsAt(int e) { + for(int n=vec.length, i=0; iSAT4J solvers, + * the zchaff solver from Princeton, + * and the MiniSat solver by + * Niklas Eén and Niklas Sörensson. + * @author Emina Torlak + */ +public abstract class SATFactory { + + /** + * Constructs a new instance of SATFactory. + */ + protected SATFactory() {} + + /** + * The factory that produces instances of the default sat4j solver. + * @see org.sat4j.core.ASolverFactory#defaultSolver() + */ + public static final SATFactory DefaultSAT4J = new SATFactory() { + public SATSolver instance() { + return new SAT4J(SolverFactory.instance().defaultSolver()); + } + public String toString() { return "DefaultSAT4J"; } + }; + + /** + * The factory that produces instances of the "light" sat4j solver. The + * light solver is suitable for solving many small instances of SAT problems. + * @see org.sat4j.core.ASolverFactory#lightSolver() + */ + public static final SATFactory LightSAT4J = new SATFactory() { + public SATSolver instance() { + return new SAT4J(SolverFactory.instance().lightSolver()); + } + public String toString() { return "LightSAT4J"; } + }; + + /** + * The factory that produces instances of the zchaff solver from Princeton; + * the returned instances + * support only basic sat solver operations (adding variables/clauses, + * solving, and obtaining a satisfying solution, if any). ZChaff is not incremental. + */ + public static final SATFactory ZChaff = new SATFactory() { + public SATSolver instance() { + return new ZChaff(); + } + public boolean incremental() { return false; } + public String toString() { return "ZChaff"; } + }; + + + + /** + * The factory the produces {@link SATMinSolver cost-minimizing} + * instances of the zchaff solver from Princeton. Note that cost minimization + * can incur a time and/or memory overhead during solving, + * so if you do not need this functionality, use the {@link #ZChaff} factory + * instead. ZChaffMincost is not incremental. + */ + public static final SATFactory ZChaffMincost = new SATFactory() { + public SATSolver instance() { + return new ZChaffMincost(); + } + @Override + public boolean minimizer() { return true; } + public boolean incremental() { return false; } + public String toString() { return "ZChaffMincost"; } + }; + + /** + * The factory the produces {@link SATProver proof logging} + * instances of the MiniSat solver. Note that core + * extraction can incur a significant time overhead during solving, + * so if you do not need this functionality, use the {@link #MiniSat} factory + * instead. + */ + public static final SATFactory MiniSatProver = new SATFactory() { + public SATSolver instance() { + return new MiniSatProver(); + } + @Override + public boolean prover() { return true; } + public String toString() { return "MiniSatProver"; } + }; + + /** + * The factory that produces instances of Niklas Eén and Niklas Sörensson's + * MiniSat solver. + */ + public static final SATFactory MiniSat = new SATFactory() { + public SATSolver instance() { + return new MiniSat(); + } + public String toString() { return "MiniSat"; } + }; + + public static final SATFactory MiniSatExternal = new SATFactory() { + public SATSolver instance() { + return new MiniSatExternal(); + } + public String toString() { return "MiniSatExternal"; } + }; + + + /** + * Returns a SATFactory that produces instances of the specified + * SAT4J solver. For the list of available SAT4J solvers see + * {@link org.sat4j.core.ASolverFactory#solverNames() org.sat4j.core.ASolverFactory#solverNames()}. + * @requires solverName is a valid solver name + * @return a SATFactory that returns the instances of the specified + * SAT4J solver + * @see org.sat4j.core.ASolverFactory#solverNames() + */ + public static final SATFactory sat4jFactory(final String solverName) { + return new SATFactory() { + @Override + public SATSolver instance() { + return new SAT4J(SolverFactory.instance().createSolverByName(solverName)); + } + public String toString() { return solverName; } + }; + } + + /** + * Returns a SATFactory that produces SATSolver wrappers for the external + * SAT solver specified by the executable parameter. The solver's input + * and output formats must conform to the SAT competition standards + * (http://www.satcompetition.org/2004/format-solvers2004.html). The solver + * will be called with the specified options, and the given tempInput file name will + * be used to store the generated CNF files. If the tempOutput string is empty, + * the solver specified by the executable string is assumed to write its output + * to standard out; otherwise, the + * solver is assumed to write its output to the tempOutput file. It is the caller's responsibility to + * delete the temporary file(s) when no longer needed. External solvers are never incremental. + * @return SATFactory that produces interruptible SATSolver wrappers for the specified external + * SAT solver + */ + public static final SATFactory externalFactory(final String executable, final String tempInput, final String tempOutput, final String... options) { + return new SATFactory() { + + @Override + public SATSolver instance() { + return new ExternalSolver(executable, tempInput, tempOutput, options); + } + + @Override + public boolean incremental() { + return false; + } + }; + } + + + /** + * Returns an instance of a SATSolver produced by this factory. + * @return a SATSolver instance + */ + public abstract SATSolver instance(); + + /** + * Returns true if the solvers returned by this.instance() are + * {@link SATProver SATProvers}. Otherwise returns false. + * @return true if the solvers returned by this.instance() are + * {@link SATProver SATProvers}. Otherwise returns false. + */ + public boolean prover() { + return false; + } + + /** + * Returns true if the solvers returned by this.instance() are + * {@link SATMinSolver SATMinSolvers}. Otherwise returns false. + * @return true if the solvers returned by this.instance() are + * {@link SATMinSolver SATMinSolvers}. Otherwise returns false. + */ + public boolean minimizer() { + return false; + } + + /** + * Returns true if the solvers returned by this.instance() are incremental; + * i.e. if clauses/variables can be added to the solver between multiple + * calls to solve(). + * @return true if the solvers returned by this.instance() are incremental + */ + public boolean incremental() { + return true; + } + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/satlab/SATMinSolver.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/satlab/SATMinSolver.java new file mode 100644 index 00000000..d003cdae --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/satlab/SATMinSolver.java @@ -0,0 +1,58 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.engine.satlab; + +/** + * Provides an interface to a SAT solver that produces + * minimal cost solutions. That is, given a CNF formula + * and a function f from the variables to non-negative integer, + * the solver produces the solution that minimizes the expression + * sum(f(v)*valueOf(v)) for all variables v, where valueOf(v) is + * 1 if the variable is set to TRUE and 0 otherwise. + * + * @specfield variables: set [1..) + * @specfield cost: variables -> one [0..) + * @specfield clauses: set Clause + * @invariant all i: [2..) | i in variables => i-1 in variables + * @invariant all c: clauses | all lit: c.literals | lit in variables || -lit in variables + * @invariant all c: clauses | all disj i,j: c.literals | abs(i) != abs(j) + * @author Emina Torlak + */ +public interface SATMinSolver extends SATSolver { + + /** + * Sets the cost of the given variable to the specified value. + * @requires variable in this.variables && cost >= 0 + * @ensures this.cost' = this.cost ++ variable -> cost + * @throws IllegalArgumentException - variable !in this.variables || cost < 0 + */ + public abstract void setCost(int variable, int cost); + + /** + * Returns the cost of setting the given variable to TRUE. + * @requires variable in this.variables + * @return this.cost[variable] + * @throws IllegalArgumentException - variable !in this.variables + */ + public abstract int costOf(int variable); + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/satlab/SATProver.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/satlab/SATProver.java new file mode 100644 index 00000000..f29daaf4 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/satlab/SATProver.java @@ -0,0 +1,77 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.engine.satlab; + +/** + * Provides an interface to a SAT solver that can generate + * proofs of unsatisfiability. + * + * @specfield variables: set [1..) + * @specfield clauses: set Clause + * @specfield resolvents: set Clause + * @invariant all i: [2..) | i in variables => i-1 in variables + * @invariant all c: clauses + resolvents | all lit: c.lits | lit in variables || -lit in variables + * @invariant all c: clauses + resolvents | all disj i,j: c.lits | abs(i) != abs(j) + * @author Emina Torlak + */ +public interface SATProver extends SATSolver { + + /** + * Returns a resolution-based proof of unsatisfiability of this.clauses. + * @requires {@link SATSolver#solve()} has been called, and it returned false + * @return { t: ResolutionTrace | t.prover = this } + * @throws IllegalStateException - {@link SATSolver#solve()} has not been called, + * or the last call to {@link SATSolver#solve()} returned true + */ + public ResolutionTrace proof(); + + /** + * Uses the given reduction strategy to remove irrelevant clauses from + * the set of unsatisfiable clauses stored in this prover. + * A clause c is irrelevant iff this.clauses - c is unsatisfiable. + * The removal algorithm works as follows: + *
+	 * for (IntSet next = strategy.next(this.proof()); !next.isEmpty(); next = strategy.next(this.proof())) {
+	 *  let oldClauses = this.clauses, oldResolvents = this.resolvents
+	 *  clear this.clauses
+	 *  clear this.resolvents
+	 *  for(Clause c : this.proof().elts[next]) {
+	 *    if (no c.antecedents)
+	 *      add c to this.clauses
+	 *    else
+	 *      add c to this.resolvents
+	 *  }
+	 *  if (this.solve()) {
+	 *   this.clauses = oldClauses 
+	 *   this.resolvents = oldResolvents
+	 *  }
+	 * }
+	 * 
+ * @requires {@link SATSolver#solve()} has been called, and it returned false + * @ensures modifies this.clauses and this.resolvents according to the algorithm described above + * @throws IllegalStateException - {@link SATSolver#solve()} has not been called, + * or the last call to {@link SATSolver#solve()} returned true + * @see ReductionStrategy + */ + public void reduce(ReductionStrategy strategy); + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/satlab/SATSolver.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/satlab/SATSolver.java new file mode 100644 index 00000000..dd8a6ff2 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/satlab/SATSolver.java @@ -0,0 +1,111 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.engine.satlab; + + +/** + * Provides an interface to a SAT solver. + * + * @specfield variables: set [1..) + * @specfield clauses: set Clause + * @invariant all i: [2..) | i in variables => i-1 in variables + * @invariant all c: clauses | all lit: c.literals | lit in variables || -lit in variables + * @invariant all c: clauses | all disj i,j: c.literals | abs(i) != abs(j) + * @author Emina Torlak + */ +public interface SATSolver { + + /** + * Returns the size of this solver's vocabulary. + * @return #this.variables + */ + public abstract int numberOfVariables(); + + /** + * Returns the number of clauses in this solver. + * @return #this.clauses + */ + public abstract int numberOfClauses(); + + /** + * Adds the specified number of new variables + * to the solver's vocabulary. The behavior of this + * method is undefined if it is called after this.solve() + * has returned false. + * @requires numVars >= 0 + * @ensures this.variables' = [1..#this.variables + numVars] + * @throws IllegalArgumentException - numVars < 0 + */ + public abstract void addVariables(int numVars); + + /** + * Ensures that this solver logically contains the given + * clause, and returns true if this.clauses changed as a + * result of the call. No reference to the specified array + * is kept, so it can be reused. The contents of the array may, + * however, be modified. It is the client's responsibility to + * ensure that no literals in the given array are repeated, or that + * both a literal and its negation are present. The behavior of this + * method is undefined if it is called after this.solve() + * has returned false. + * @requires all i: [0..lits.length) | abs(lits[i]) in this.variables + * @requires all disj i,j: [0..lits.length) | abs(lits[i]) != abs(lits[j]) + * @ensures [[this.clauses']] = ([[this.clauses]] and [[lits]]) + * @return #this.clauses' > #this.clauses + * @throws NullPointerException - lits = null + */ + public abstract boolean addClause(int[] lits); + + /** + * Returns true if there is a satisfying assignment for this.clauses. + * Otherwise returns false. If this.clauses are satisfiable, the + * satisfying assignment for a given variable + * can be obtained by calling {@link #valueOf(int)}. + * If the satisfiability of this.clauses cannot be determined within + * the given number of seconds, a TimeoutException is thrown. + * @return true if this.clauses are satisfiable; otherwise false. + * @throws SATAbortedException -- the call to solve was cancelled or + * could not terminate normally. + */ + public abstract boolean solve() throws SATAbortedException; + + /** + * Returns the boolean value assigned to the given variable by the + * last successful call to {@link #solve()}. + * @requires {@link #solve() } has been called and the + * outcome of the last call was true. + * @return the boolean value assigned to the given variable by the + * last successful call to {@link #solve()}. + * @throws IllegalArgumentException - variable !in this.variables + * @throws IllegalStateException - {@link #solve() } has not been called or the + * outcome of the last call was not true. + */ + public abstract boolean valueOf(int variable); + + /** + * Frees the memory used by this solver. Once free() is called, + * all subsequent calls to methods other than free() may fail. + * @ensures frees the memory used by this solver + */ + public abstract void free(); + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/satlab/ZChaff.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/satlab/ZChaff.java new file mode 100644 index 00000000..7f55cfd9 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/satlab/ZChaff.java @@ -0,0 +1,86 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.engine.satlab; + +/** + * Wrapper for an instance of zchaff that provides + * access to basic functionality. + */ +final class ZChaff extends NativeSolver { + /** + * Constructs an instance of ZChaffBasic. + */ + ZChaff() { + super(make()); + } + + /** + * {@inheritDoc} + * @see java.lang.Object#toString() + */ + public String toString() { + return "ZChaff"; + } + + static { + loadLibrary("zchaff"); + } + + /** + * Creates an instance of zchaff and returns + * its address in memory. + * @return the memory address of an instance + * of the zchaff solver + */ + private static native long make(); + + /** + * {@inheritDoc} + * @see kodkod.engine.satlab.NativeSolver#free(long) + */ + native void free(long peer); + + /** + * {@inheritDoc} + * @see kodkod.engine.satlab.NativeSolver#addVariables(long, int) + */ + native void addVariables(long peer, int numVariables); + + /** + * {@inheritDoc} + * @see kodkod.engine.satlab.NativeSolver#addClause(long, int[]) + */ + native boolean addClause(long peer, int[] lits); + + /** + * {@inheritDoc} + * @see kodkod.engine.satlab.NativeSolver#solve(long) + */ + native boolean solve(long peer); + + /** + * {@inheritDoc} + * @see kodkod.engine.satlab.NativeSolver#valueOf(long, int) + */ + native boolean valueOf(long peer, int literal); + +} \ No newline at end of file diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/satlab/ZChaffMincost.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/satlab/ZChaffMincost.java new file mode 100644 index 00000000..10c6cdb5 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/satlab/ZChaffMincost.java @@ -0,0 +1,126 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.engine.satlab; + +/** + * Wrapper for an instance of MinCostZChaff. + * + * @author Emina Torlak + */ +final class ZChaffMincost extends NativeSolver implements SATMinSolver { + + /** + * Constructs an instance of ZChaffMincost. + */ + ZChaffMincost() { + super(make()); + } + + /** + * {@inheritDoc} + * @see kodkod.engine.satlab.SATMinSolver#setCost(int, int) + */ + public void setCost(int variable, int cost) { + validateVariable(variable); + if (cost < 0) + throw new IllegalArgumentException("invalid cost: " + cost); + setCost(peer(), variable, cost); + } + + /** + * {@inheritDoc} + * @see kodkod.engine.satlab.SATMinSolver#costOf(int) + */ + public int costOf(int variable) { + validateVariable(variable); + return costOf(peer(), variable); + } + + /** + * {@inheritDoc} + * @see java.lang.Object#toString() + */ + public String toString() { + return "ZChaffMincost"; + } + + static { + loadLibrary("zchaffmincost"); + } + + /** + * Creates an instance of zchaff and returns + * its address in memory. + * @return the memory address of an instance + * of the zchaff solver + */ + private static native long make(); + + /** + * Sets the cost of the given variable to the specified value in the + * native zchaff instance at the given address. + * @requires variable is a valid variable identifier && cost >= 0 + * @ensures sets the cost of the given variable to the specified value in the + * native zchaff instance at the given address. + */ + private native void setCost(long peer, int variable, int cost); + + /** + * Retrieves the cost of the given variable in the native zchaff instance at the + * given address. + * @requires variable is a valid variable identifier + * @return the cost of the given variable in the native zchaff instance at the + * given address. + */ + private native int costOf(long peer, int variable); + + /** + * {@inheritDoc} + * @see kodkod.engine.satlab.NativeSolver#free(long) + */ + native void free(long peer); + + /** + * {@inheritDoc} + * @see kodkod.engine.satlab.NativeSolver#addVariables(long, int) + */ + native void addVariables(long peer, int numVariables); + + /** + * {@inheritDoc} + * @see kodkod.engine.satlab.NativeSolver#addClause(long, int[]) + */ + native boolean addClause(long peer, int[] lits); + + /** + * {@inheritDoc} + * @see kodkod.engine.satlab.NativeSolver#solve(long) + */ + native boolean solve(long peer); + + /** + * {@inheritDoc} + * @see kodkod.engine.satlab.NativeSolver#valueOf(long, int) + */ + native boolean valueOf(long peer, int literal); + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/satlab/package.html b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/satlab/package.html new file mode 100644 index 00000000..76b48632 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/satlab/package.html @@ -0,0 +1,42 @@ + + + + + + + +Provides access to various Java and C++ SAT solvers through a +common SAT Solver interface. + +

Package Specification

+ +

Provides access to various Java and C++ SAT solvers through the +{@linkplain kodkod.engine.satlab.SATSolver}, +{@linkplain kodkod.engine.satlab.SATProver}, and {@linkplain kodkod.engine.satlab.SATMinSolver} +interfaces. The {@linkplain kodkod.engine.satlab.SATFactory} class contains a selection of +static instances that can be used to generate specific SAT solvers.

+ +

Related Documentation

+ +@see kodkod.engine.satlab.SATFactory +@see kodkod.engine.satlab.SATSolver +@see kodkod.engine.satlab.SATProver +@see kodkod.engine.satlab.SATMinSolver + + + diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/ucore/AdaptiveRCEStrategy.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/ucore/AdaptiveRCEStrategy.java new file mode 100644 index 00000000..0f51a158 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/ucore/AdaptiveRCEStrategy.java @@ -0,0 +1,172 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.engine.ucore; + +import kodkod.engine.fol2sat.TranslationLog; +import kodkod.engine.fol2sat.Translator; +import kodkod.engine.satlab.ReductionStrategy; +import kodkod.engine.satlab.ResolutionTrace; +import kodkod.util.ints.IntCollection; +import kodkod.util.ints.IntIterator; +import kodkod.util.ints.IntSet; +import kodkod.util.ints.Ints; +/** + * Adaptive Recycling Core Extraction is a strategy for generating unsat cores that are minimal at the logic level. + * Specifically, let C be a core that is minimal according to this strategy, + * and let F(C) be the top-level logic constraints + * corresponding to C. Then, this strategy guarantees that there is no clause + * c in C such that F(C - c) is a strict subset of F(C). Furthermore, it also + * guarantees that for all f in F(C), F(C) - f is satisfiable. This is a stronger + * guarantee than that of {@linkplain HybridStrategy}. In general, using this strategy + * is more expensive, timewise, than using {@linkplain HybridStrategy}. + * + *

Unlike RCE, ARCE is parameterized with 3 values that control the amount of recycling. The + * first is the noRecycleRatio, which completely disables recycling if it is greater than + * the ratio of the size of the core passed to {@linkplain #next(ResolutionTrace)} and the number of axioms in the + * trace. The default value is .03; if the core makes up only 3 percent of the axioms, no recycling + * will happen. The remaining two parameters are the recycleLimit and the hardnessCutOff. + * If the hardness of the proof passed to {@linkplain #next(ResolutionTrace)} is greater than hardnessCutOff, + * then the number of relevant axioms, |A_r|, plus the number of recycled resolvents is no greater than + * |A_r|*recycleLimit. Otherwise, all valid + * resolvents are recycled (i.e. added to the relevant axioms). + * Proof hardness is the ratio of the trace size to the number of axioms in the trace. + * Default value for hardnessCutOff is 2.0, and default value for recycleLimit is 1.2. + * + *

This implementation of ARCE will work properly only on CNFs generated by the kodkod {@linkplain Translator}.

+ * + * @specfield noRecycleRatio: double + * @specfield hardnessCutOff: double + * @specfield recycleLimit: double + * @invariant noRecycleRatio in [0..1] + * @invariant recycleLimit >= 1 + * @invariant hardnessCutOff >= 1 + * @author Emina Torlak + * @see HybridStrategy + */ +public final class AdaptiveRCEStrategy implements ReductionStrategy { + private final IntCollection varsToTry; + private final double noRecycleRatio, recycleLimit, hardnessCutOff; + private static final boolean DBG = true; + + /** + * Constructs an ARCE strategy that will use the given translation + * log to relate the cnf clauses back to the logic constraints from + * which they were generated. + * @ensures this.hardnessCutOff' = 2 and this.recycleLimit' = 1.2 and this.noRecycleRatio' = .03 + */ + public AdaptiveRCEStrategy(final TranslationLog log) { + this(log, .03, 2.0, 1.2); + } + + /** + * Constructs an ARCE strategy that will use the given translation + * log to relate the cnf clauses back to the logic constraints from + * which they were generated. + * @ensures this.hardnessCutOff' = hardnessCutOff and this.recycleLimit' = recycleLimit and + * this.noRecycleRatio' = noRecycleRatio + */ + public AdaptiveRCEStrategy(final TranslationLog log, double noRecycleRatio, double hardnessCutOff, double recycleLimit) { + varsToTry = StrategyUtils.rootVars(log); + if (noRecycleRatio<0 || noRecycleRatio>1) + throw new IllegalArgumentException("noRecycleRatio must be in [0..1]: " + noRecycleRatio); + if (hardnessCutOff < 1) + throw new IllegalArgumentException("hardnessCutOff must be >=1: " + hardnessCutOff); + if (recycleLimit < 1) + throw new IllegalArgumentException("recycleLimit must be >=1: " + recycleLimit); + this.noRecycleRatio = noRecycleRatio; + this.hardnessCutOff = hardnessCutOff; + this.recycleLimit = recycleLimit; + } + + /** + * {@inheritDoc} + * @see kodkod.engine.satlab.ReductionStrategy#next(kodkod.engine.satlab.ResolutionTrace) + */ + public IntSet next(ResolutionTrace trace) { + if (varsToTry.isEmpty()) return Ints.EMPTY_SET; // tried everything + + final IntSet relevantVars = StrategyUtils.coreTailUnits(trace); + + + for(IntIterator varItr = varsToTry.iterator( ); varItr.hasNext();) { + final int var = varItr.next(); + varItr.remove(); + if (relevantVars.remove(var)) { // remove maxVar from the set of relevant variables + if (relevantVars.isEmpty()) break; // there was only one root formula left + // get all axioms and resolvents corresponding to the clauses that + // form the translations of formulas identified by relevant vars + final IntSet relevantClauses = clausesFor(trace, relevantVars); + assert !relevantClauses.isEmpty() && !relevantClauses.contains(trace.size()-1); + + if (DBG) System.out.println("relevant clauses: " + relevantClauses.size() + ", removed " + var); + + return relevantClauses; + } + } + + varsToTry.clear(); + return Ints.EMPTY_SET; + } + + + /** + * Returns the indices of all axioms and resolvents + * in the given trace that form the translations of the formulas + * identified by the given variables. This method assumes that + * the axioms in the given trace were generated by the Kodkod + * {@linkplain Translator}. + * @return + * let C = { c: trace.prover.clauses | c.maxVariable() in relevantVars }, + * T = { c1, c2: C | c2.maxVariable() in abs(c1.literals) }, + * A = C.*T | + * trace.backwardReachable(A) - trace.backwardReachable(trace.axioms() - A) + */ + private IntSet clausesFor(ResolutionTrace trace, IntSet relevantVars) { + final double hardness = (double) trace.size() / (double) trace.axioms().size(); + final double coreRatio = ((double) trace.core().size() / (double) trace.axioms().size()); + + if (DBG) System.out.println("\ntrace size: " + trace.size() + ", axioms: " + trace.axioms().size() + ", core: " + trace.core().size() + ", resolvents: " + trace.resolvents().size()); + if (DBG) System.out.println("hardness: " + hardness + ", coreRatio: " + coreRatio); + + final IntSet relevantAxioms = StrategyUtils.clausesFor(trace, relevantVars); + if (DBG) System.out.println("relevant axioms: " + relevantAxioms.size()); + + if (coreRatio < noRecycleRatio) { + return relevantAxioms; + } else if (hardness < hardnessCutOff) { + return trace.learnable(relevantAxioms); + } else { + IntSet current = relevantAxioms, last; + final int maxRelevant = (int) Math.rint(relevantAxioms.size()*recycleLimit); + do { + last = current; + current = trace.directlyLearnable(current); + } while (last.size() < current.size() && current.size() < maxRelevant); + + if (DBG) System.out.println("last: " + last.size() +", current: " + current.size() + ", maxRelevant: " + maxRelevant); + + return current.size() < maxRelevant ? current : last; + } + + } + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/ucore/CRRStrategy.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/ucore/CRRStrategy.java new file mode 100644 index 00000000..ea8e3e87 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/ucore/CRRStrategy.java @@ -0,0 +1,88 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.engine.ucore; + +import java.util.HashSet; +import java.util.Set; + +import kodkod.engine.satlab.Clause; +import kodkod.engine.satlab.ReductionStrategy; +import kodkod.engine.satlab.ResolutionTrace; +import kodkod.util.ints.IntIterator; +import kodkod.util.ints.IntSet; +import kodkod.util.ints.Ints; + + +/** + * A basic implementation of the Complete ResolutionTrace Refutation algorithm + * for for producing locally minimal cores. + * An unsatisfiable core is locally minimal iff removing any single clause from + * the core will make the resulting formula satisfiable. No heuristic is used + * to pick the clauses to be excluded from the core. + * @specfield traces: [0..)->ResolutionTrace + * @specfield nexts: [0..)->Set + * @invariant traces.ResolutionTrace = nexts.Set + * @invariant all i: [1..) | some traces[i] => some traces[i-1] + * @invariant all i: [0..#nexts) | nexts[i] in traces[i].conflict.^antecedents + * @invariant no disj i,j: [0..#nexts) | traces[i] = traces[j] && nexts[i] = nexts[j] + * @author Emina Torlak + * @see N. Dershowitz, Z. Hanna, and A. Nadel. A scalable algorithm for minimal unsatisfiable core + * extraction. In Proceedings of Ninth International Conference on Theory and Applications of + * Satisfiability Testing (SAT '06). 2006. + */ +public final class CRRStrategy implements ReductionStrategy { + private Set excluded; + + /** + * Constructs a new instance of CRRStrategy. + * @ensures no this.traces' and no this.nexts' + **/ + public CRRStrategy() { + excluded = null; + } + + /** + * Returns the next subset of clauses in the given trace to be analyzed. + * @requires {@inheritDoc} + * @ensures {@inheritDoc} + * @return last(this.nexts') + */ + public final IntSet next(final ResolutionTrace trace) { + final IntSet core = trace.core(); + if (excluded==null) { // the first time this method is called + excluded = new HashSet((int)(StrictMath.round(core.size()*.75))); + } + + for(IntIterator iter = core.iterator(Integer.MAX_VALUE, Integer.MIN_VALUE); iter.hasNext();) { + int index = iter.next(); + if (excluded.add(trace.get(index))) { // haven't tried excluding this one + // get all clauses reachable from the conflict clause + IntSet next = trace.reachable(Ints.singleton(trace.size()-1)); + // remove all clauses backward reachable from the excluded clause + next.removeAll(trace.backwardReachable(Ints.singleton(index))); + return next; + } + } + + return Ints.EMPTY_SET; + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/ucore/DynamicRCEStrategy.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/ucore/DynamicRCEStrategy.java new file mode 100644 index 00000000..a529865e --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/ucore/DynamicRCEStrategy.java @@ -0,0 +1,248 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.engine.ucore; + +import java.util.Arrays; +import java.util.Iterator; + +import kodkod.engine.fol2sat.TranslationLog; +import kodkod.engine.fol2sat.Translator; +import kodkod.engine.satlab.Clause; +import kodkod.engine.satlab.ReductionStrategy; +import kodkod.engine.satlab.ResolutionTrace; +import kodkod.util.ints.IntBitSet; +import kodkod.util.ints.IntIterator; +import kodkod.util.ints.IntSet; +import kodkod.util.ints.Ints; +import kodkod.util.ints.SparseSequence; +import kodkod.util.ints.TreeSequence; + +/** + * Dynamic Recycling Core Extraction is a strategy for generating unsat cores that are minimal at the logic level. + * Specifically, let C be a core that is minimal according to this strategy, + * and let F(C) be the top-level logic constraints + * corresponding to C. Then, this strategy guarantees that there is no clause + * c in C such that F(C - c) is a strict subset of F(C). Furthermore, it also + * guarantees that for all f in F(C), F(C) - f is satisfiable. This is a stronger + * guarantee than that of {@linkplain HybridStrategy}. In general, using this strategy + * is more expensive, timewise, than using {@linkplain HybridStrategy}. + * + *

Like Adaptive RCE, DRCE is parameterized with 3 values that control the amount of recycling. The + * first is the noRecycleRatio, which completely disables recycling if it is greater than + * the ratio of the size of the core passed to {@linkplain #next(ResolutionTrace)} and the number of axioms in the + * trace. The default value is .03; if the core makes up only 3 percent of the axioms, no recycling + * will happen. The remaining two parameters are the recycleLimit and the hardnessCutOff. + * If the hardness of the proof passed to {@linkplain #next(ResolutionTrace)} is greater than hardnessCutOff, + * then the number of relevant axioms, |A_r|, plus the number of recycled resolvents is no greater than + * |A_r|*recycleLimit. Otherwise, all valid + * resolvents are recycled (i.e. added to the relevant axioms). + * Proof hardness is the ratio of the trace size to the number of axioms in the trace. + * Default value for hardnessCutOff is 2.0, and default value for recycleLimit is 1.2. + * + *

Unlike ARCE, DRCE uses proof information to determine the order in which the constraints are tested for + * membership in a minimal core. ARCE, RCE, SCE and NCE all use the same (arbitrary but deterministic) ordering.

+ * + *

This implementation of DRCE will work properly only on CNFs generated by the kodkod {@linkplain Translator}.

+ * + * @specfield noRecycleRatio: double + * @specfield hardnessCutOff: double + * @specfield recycleLimit: double + * @invariant noRecycleRatio in [0..1] + * @invariant recycleLimit >= 1 + * @invariant hardnessCutOff >= 1 + * @author Emina Torlak + * @see HybridStrategy + */ +public final class DynamicRCEStrategy implements ReductionStrategy { + private final double noRecycleRatio, recycleLimit, hardnessCutOff; + private static final boolean DBG = true; + private final SparseSequence hits; + /** + * Constructs an ARCE strategy that will use the given translation + * log to relate the cnf clauses back to the logic constraints from + * which they were generated. + * @ensures this.hardnessCutOff' = 2 and this.recycleLimit' = 1.2 and this.noRecycleRatio' = .03 + */ + public DynamicRCEStrategy(final TranslationLog log) { + this(log, .03, 2.0, 1.2); + } + + /** + * Constructs an ARCE strategy that will use the given translation + * log to relate the cnf clauses back to the logic constraints from + * which they were generated. + * @ensures this.hardnessCutOff' = hardnessCutOff and this.recycleLimit' = recycleLimit and + * this.noRecycleRatio' = noRecycleRatio + */ + public DynamicRCEStrategy(final TranslationLog log, double noRecycleRatio, double hardnessCutOff, double recycleLimit) { + if (noRecycleRatio<0 || noRecycleRatio>1) + throw new IllegalArgumentException("noRecycleRatio must be in [0..1]: " + noRecycleRatio); + if (hardnessCutOff < 1) + throw new IllegalArgumentException("hardnessCutOff must be >=1: " + hardnessCutOff); + if (recycleLimit < 1) + throw new IllegalArgumentException("recycleLimit must be >=1: " + recycleLimit); + this.noRecycleRatio = noRecycleRatio; + this.hardnessCutOff = hardnessCutOff; + this.recycleLimit = recycleLimit; + this.hits = new TreeSequence(); + for(IntIterator itr = StrategyUtils.rootVars(log).iterator(); itr.hasNext(); ) { + hits.put(itr.next(), null); + } + } + + /** + * {@inheritDoc} + * @see kodkod.engine.satlab.ReductionStrategy#next(kodkod.engine.satlab.ResolutionTrace) + */ + public IntSet next(ResolutionTrace trace) { + if (hits.isEmpty()) return Ints.EMPTY_SET; // tried everything + final IntSet relevantVars = StrategyUtils.coreTailUnits(trace); + + final long[] byRelevance = sortByRelevance(trace, relevantVars); + if (DBG) printRelevant(byRelevance); + for(int i = byRelevance.length-1; i>=0; i--) { + final int var = (int)byRelevance[i]; + if (hits.remove(var)!=null) { + // remove maxVar from the set of relevant variables + relevantVars.remove(var); + if (relevantVars.isEmpty()) break; // there was only one root formula left + // get all axioms and resolvents corresponding to the clauses that + // form the translations of formulas identified by relevant vars + final IntSet relevantClauses = clausesFor(trace, relevantVars); + assert !relevantClauses.isEmpty() && !relevantClauses.contains(trace.size()-1); + + if (DBG) System.out.println("relevant clauses: " + relevantClauses.size() + ", removed " + var); + + return relevantClauses; + } + } + + hits.clear(); + return Ints.EMPTY_SET; + } + + private final void printRelevant(long[] byRelevance) { + System.out.print("\nsorted by relevance: "); + for(long r : byRelevance) { + System.out.print((int)(r>>>32) + ":" + (int)r + " "); + } + System.out.println(); + } + + /** + * Returns an array R of longs such that for each i, j in [0..R.length) i < j implies + * that the formula identified by (int)R[i] in this.hits contributes fewer clauses to + * the core of the given trace than the formula identified by (int)R[j]. + * @return an array as described above + */ + private long[] sortByRelevance(ResolutionTrace trace, IntSet relevantVars) { + hits.indices().retainAll(relevantVars); + + if (hits.get(hits.indices().min())==null) { // first call, initialize the hits + for(IntIterator varItr = relevantVars.iterator(); varItr.hasNext(); ) { + final int var = varItr.next(); + final IntSet varReachable = new IntBitSet(var+1); + varReachable.add(var); + hits.put(var, varReachable); + } + for(Iterator clauseItr = trace.reverseIterator(trace.axioms()); clauseItr.hasNext();) { + final Clause clause = clauseItr.next(); + final int maxVar = clause.maxVariable(); + for(IntSet reachableVars : hits.values()) { + if (reachableVars.contains(maxVar)) { + for(IntIterator lits = clause.literals(); lits.hasNext(); ) { + reachableVars.add(StrictMath.abs(lits.next())); + } + } + } + } + } + + final long[] counts = new long[hits.size()]; + + for(Iterator clauseItr = trace.iterator(trace.core()); clauseItr.hasNext(); ) { + final Clause clause = clauseItr.next(); + final int maxVar = clause.maxVariable(); + int i = 0; + for(IntSet reachableVars : hits.values()) { + if (reachableVars.contains(maxVar)) { + counts[i]++; + } + i++; + } + } + + + int i = 0; + for(IntIterator varItr = hits.indices().iterator(); varItr.hasNext();) { + final int var = varItr.next(); + counts[i] = (counts[i]<<32) | var; + i++; + } + + Arrays.sort(counts); + + return counts; + } + + /** + * Returns the indices of all axioms and resolvents + * in the given trace that form the translations of the formulas + * identified by the given variables. This method assumes that + * the axioms in the given trace were generated by the Kodkod + * {@linkplain Translator}. + * @return + * let C = { c: trace.prover.clauses | c.maxVariable() in relevantVars }, + * T = { c1, c2: C | c2.maxVariable() in abs(c1.literals) }, + * A = C.*T | + * trace.backwardReachable(A) - trace.backwardReachable(trace.axioms() - A) + */ + private IntSet clausesFor(ResolutionTrace trace, IntSet relevantVars) { + final double hardness = (double) trace.size() / (double) trace.axioms().size(); + final double coreRatio = ((double) trace.core().size() / (double) trace.axioms().size()); + + if (DBG) System.out.println("trace size: " + trace.size() + ", axioms: " + trace.axioms().size() + ", core: " + trace.core().size() + ", resolvents: " + trace.resolvents().size()); + if (DBG) System.out.println("hardness: " + hardness + ", coreRatio: " + coreRatio); + + final IntSet relevantAxioms = StrategyUtils.clausesFor(trace, relevantVars); + if (DBG) System.out.println("relevant axioms: " + relevantAxioms.size()); + + if (coreRatio < noRecycleRatio) { + return relevantAxioms; + } else if (hardness < hardnessCutOff) { + return trace.learnable(relevantAxioms); + } else { + IntSet current = relevantAxioms, last; + final int maxRelevant = (int) Math.rint(relevantAxioms.size()*recycleLimit); + do { + last = current; + current = trace.directlyLearnable(current); + } while (last.size() < current.size() && current.size() < maxRelevant); + + if (DBG) System.out.println("last: " + last.size() +", current: " + current.size() + ", maxRelevant: " + maxRelevant); + + return current.size() < maxRelevant ? current : last; + } + + } + +} \ No newline at end of file diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/ucore/ECFPStrategy.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/ucore/ECFPStrategy.java new file mode 100644 index 00000000..8b993c3b --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/ucore/ECFPStrategy.java @@ -0,0 +1,61 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.engine.ucore; + +import kodkod.engine.satlab.ReductionStrategy; +import kodkod.engine.satlab.ResolutionTrace; +import kodkod.util.ints.IntSet; +import kodkod.util.ints.Ints; + +/** + * A non-optimal minimization strategy based on the Empty Clause Cone algorithm. + * @author Emina Torlak + * @see L. Zhang and S. Malik. Extracting small unsatisfiable cores from unsatisfiable + * Boolean formula. In Proceedings of Sixth International Conference on Theory and Applications of + * Satisfiability Testing (SAT '03). 2003. + */ +public final class ECFPStrategy implements ReductionStrategy { + private int lastCore; + /** + * Constructs a new instance of the empty clause cone strategy for + * minimizing unsatisfiable cores. + */ + public ECFPStrategy() { + lastCore = Integer.MAX_VALUE; + } + + /** + * {@inheritDoc} + * @see kodkod.engine.satlab.ReductionStrategy#next(kodkod.engine.satlab.ResolutionTrace) + */ + public IntSet next(final ResolutionTrace trace) { + final IntSet core = trace.core(); + if (lastCore > core.size()) { + lastCore = core.size(); + return core; + } else { + lastCore = Integer.MIN_VALUE; + return Ints.EMPTY_SET; + } + } + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/ucore/HybridStrategy.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/ucore/HybridStrategy.java new file mode 100644 index 00000000..1c1932e1 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/ucore/HybridStrategy.java @@ -0,0 +1,110 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.engine.ucore; + +import java.util.Iterator; + +import kodkod.engine.fol2sat.TranslationLog; +import kodkod.engine.fol2sat.Translator; +import kodkod.engine.satlab.Clause; +import kodkod.engine.satlab.ReductionStrategy; +import kodkod.engine.satlab.ResolutionTrace; +import kodkod.util.ints.IntBitSet; +import kodkod.util.ints.IntIterator; +import kodkod.util.ints.IntSet; +import kodkod.util.ints.Ints; + +/** + * A hybrid strategy for generating unsat cores that are minimal when mapped + * back onto the logic level. Specifically, let C be a core that is minimal + * according to this strategy, and let F(C) be the top-level logic constraints + * corresponding to C. Then, this strategy guarantees that there is no clause + * c in C such that F(C - c) is a strict subset of F(C). Note that this does + * not guarantee that F(C) itself is minimal. In other words, there could be + * an f in F(C) such that F(C) - f is unsatisfiable. To get a minimal logic core, + * use {@linkplain RCEStrategy}. + * + *

This strategy will work properly only on CNFs generated by the kodkod {@linkplain Translator}.

+ * @author Emina Torlak + * @see RCEStrategy + */ +public final class HybridStrategy implements ReductionStrategy { + private final IntSet topVars; + + /** + * Constructs a hybrid strategy that will use the given translation + * log to relate the cnf clauses back to the logic constraints from + * which they were generated. + */ + public HybridStrategy(TranslationLog log) { + topVars = StrategyUtils.rootVars(log); + } + + /** + * {@inheritDoc} + * @see kodkod.engine.satlab.ReductionStrategy#next(kodkod.engine.satlab.ResolutionTrace) + */ + public IntSet next(ResolutionTrace trace) { + if (topVars.isEmpty()) return Ints.EMPTY_SET; // tried everything + final IntSet core = trace.core(); + + for(Iterator iter = trace.iterator(core); iter.hasNext();) { + Clause clause = iter.next(); + int maxVar = clause.maxVariable(); + if (topVars.remove(maxVar)) { + // get all core clauses with the given maximum variable + IntSet exclude = coreClausesWithMaxVar(trace, maxVar); + assert !exclude.isEmpty(); + // get all clauses reachable from the conflict clause + IntSet next = trace.reachable(Ints.singleton(trace.size()-1)); + // remove all clauses backward reachable from the clauses with the given maxVar + next.removeAll(trace.backwardReachable(exclude)); + if (!next.isEmpty()) { + return next; + } + } + } + + topVars.clear(); + return Ints.EMPTY_SET; + } + + /** + * Returns the indices of the clauses in the unsatisfiable core of the + * given trace that have the specified maximum variable. + * @return { i: trace.core() | trace[i].maxVariable() = maxVariable } + */ + private static IntSet coreClausesWithMaxVar(ResolutionTrace trace, int maxVariable) { + final IntSet core = trace.core(); + final IntSet restricted = new IntBitSet(core.max()+1); + final Iterator clauses = trace.iterator(core); + final IntIterator indices = core.iterator(); + while(clauses.hasNext()) { + Clause clause = clauses.next(); + int index = indices.next(); + if (clause.maxVariable()==maxVariable) + restricted.add(index); + } + return restricted; + } + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/ucore/NCEStrategy.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/ucore/NCEStrategy.java new file mode 100644 index 00000000..ea603c93 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/ucore/NCEStrategy.java @@ -0,0 +1,81 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.engine.ucore; + +import kodkod.engine.fol2sat.TranslationLog; +import kodkod.engine.fol2sat.Translator; +import kodkod.engine.satlab.ReductionStrategy; +import kodkod.engine.satlab.ResolutionTrace; +import kodkod.util.ints.IntCollection; +import kodkod.util.ints.IntSet; +import kodkod.util.ints.IntTreeSet; +import kodkod.util.ints.Ints; + +/** +* Naive Core Extraction is a strategy for generating unsat cores that are minimal at the logic level. + * Specifically, let C be a core that is minimal according to this strategy, + * and let F(C) be the top-level logic constraints + * corresponding to C. Then, this strategy guarantees that there is no clause + * c in C such that F(C - c) is a strict subset of F(C). Furthermore, it also + * guarantees that for all f in F(C), F(C) - f is satisfiable. This is a stronger + * guarantee than that of {@linkplain HybridStrategy}. In general, using this strategy + * is more expensive, timewise, than using {@linkplain HybridStrategy}. + * + *

This implementation of NCE will work properly only on CNFs generated by the kodkod {@linkplain Translator}.

+* @author Emina Torlak +* @see HybridStrategy +*/ +public final class NCEStrategy implements ReductionStrategy { + private final IntCollection varsToTry; + private final IntSet coreVars; + + /** + * Constructs an NCE strategy that will use the given translation + * log to relate the cnf clauses back to the logic constraints from + * which they were generated. + */ + public NCEStrategy(final TranslationLog log) { + varsToTry = StrategyUtils.rootVars(log); + coreVars = new IntTreeSet();//new IntTreeSet(varsToTry); + coreVars.addAll(varsToTry); + } + + /** + * {@inheritDoc} + * @see kodkod.engine.satlab.ReductionStrategy#next(kodkod.engine.satlab.ResolutionTrace) + */ + public IntSet next(ResolutionTrace trace) { + if (varsToTry.isEmpty()) return Ints.EMPTY_SET; + // if the last attempt at reduction was unsuccessful, + // add the unit clauses that we tried to discard back to coreVars + coreVars.addAll(StrategyUtils.coreTailUnits(trace)); + final int first = varsToTry.iterator().next();//varsToTry.min(); + varsToTry.remove(first); + coreVars.remove(first); + // get all axioms corresponding to the clauses that + // form the translations of formulas identified by coreVars + final IntSet relevantClauses = StrategyUtils.clausesFor(trace, coreVars); + assert !relevantClauses.isEmpty() && !relevantClauses.contains(trace.size()-1); + return relevantClauses; + } + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/ucore/RCEStrategy.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/ucore/RCEStrategy.java new file mode 100644 index 00000000..e1534891 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/ucore/RCEStrategy.java @@ -0,0 +1,134 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.engine.ucore; + +import kodkod.engine.fol2sat.TranslationLog; +import kodkod.engine.fol2sat.Translator; +import kodkod.engine.satlab.ReductionStrategy; +import kodkod.engine.satlab.ResolutionTrace; +import kodkod.util.ints.IntCollection; +import kodkod.util.ints.IntIterator; +import kodkod.util.ints.IntSet; +import kodkod.util.ints.Ints; + +/** + * Recycling Core Extraction is a strategy for generating unsat cores that are minimal at the logic level. + * Specifically, let C be a core that is minimal according to this strategy, + * and let F(C) be the top-level logic constraints + * corresponding to C. Then, this strategy guarantees that there is no clause + * c in C such that F(C - c) is a strict subset of F(C). Furthermore, it also + * guarantees that for all f in F(C), F(C) - f is satisfiable. This is a stronger + * guarantee than that of {@linkplain HybridStrategy}. In general, using this strategy + * is more expensive, timewise, than using {@linkplain HybridStrategy}. + * + *

This implementation of RCE will work properly only on CNFs generated by the kodkod {@linkplain Translator}.

+ * @author Emina Torlak + * @see HybridStrategy + */ +public class RCEStrategy implements ReductionStrategy { + private final IntCollection varsToTry; + private final int dist; + + /** + * Constructs an RCE strategy that will use the given translation + * log to relate the cnf clauses back to the logic constraints from + * which they were generated. By default, all relevant resolvents + * are used in each iteration. + */ + public RCEStrategy(final TranslationLog log) { + this(log, Integer.MAX_VALUE); + } + + + /** + * Constructs an RCE strategy that will use the given translation + * log to relate the cnf clauses back to the logic constraints from + * which they were generated. The relevant resolvents + * used in each iteration are reachable from the relevant axioms + * in at most dist steps. + * @requires dist >= 0 + */ + public RCEStrategy(final TranslationLog log, int dist) { + if (dist<0) throw new IllegalArgumentException("Resolution distance must be non-negative: " + dist); + varsToTry = StrategyUtils.rootVars(log); + this.dist = dist; + } + + + /** + * {@inheritDoc} + * @see kodkod.engine.satlab.ReductionStrategy#next(kodkod.engine.satlab.ResolutionTrace) + */ + public IntSet next(ResolutionTrace trace) { + if (varsToTry.isEmpty()) return Ints.EMPTY_SET; // tried everything + final IntSet relevantVars = StrategyUtils.coreTailUnits(trace); + + for(IntIterator varItr = varsToTry.iterator(); varItr.hasNext();) { + final int var = varItr.next(); + varItr.remove(); + if (relevantVars.remove(var)) { // remove maxVar from the set of relevant variables + if (relevantVars.isEmpty()) break; // there was only root formula left + // get all axioms and resolvents corresponding to the clauses that + // form the translations of formulas identified by relevant vars + final IntSet relevantClauses = clausesFor(trace, relevantVars); + assert !relevantClauses.isEmpty() && !relevantClauses.contains(trace.size()-1); + return relevantClauses; + } + } + + varsToTry.clear(); + return Ints.EMPTY_SET; + } + + + /** + * Returns the indices of all axioms and resolvents + * in the given trace that form the translations of the formulas + * identified by the given variables. This method assumes that + * the axioms in the given trace were generated by the Kodkod + * {@linkplain Translator}. + * @return + * let C = { c: trace.prover.clauses | c.maxVariable() in relevantVars }, + * T = { c1, c2: C | c2.maxVariable() in abs(c1.literals) }, + * A = C.*T | + * trace.backwardReachable(A) - trace.backwardReachable(trace.axioms() - A) + */ + private IntSet clausesFor(ResolutionTrace trace, IntSet relevantVars) { + + final IntSet relevantAxioms = StrategyUtils.clausesFor(trace, relevantVars); + + if (distThis implementation of SCE will work properly only on CNFs generated by the kodkod {@linkplain Translator}.

+ * @author Emina Torlak + * @see HybridStrategy + */ +public final class SCEStrategy implements ReductionStrategy { + private final IntCollection varsToTry; + + /** + * Constructs an SCE strategy that will use the given translation + * log to relate the cnf clauses back to the logic constraints from + * which they were generated. + */ + public SCEStrategy(final TranslationLog log) { + varsToTry = StrategyUtils.rootVars(log); + } + + /** + * {@inheritDoc} + * @see kodkod.engine.satlab.ReductionStrategy#next(kodkod.engine.satlab.ResolutionTrace) + */ + public IntSet next(ResolutionTrace trace) { + if (varsToTry.isEmpty()) return Ints.EMPTY_SET; // tried everything + final IntSet relevantVars = StrategyUtils.coreTailUnits(trace); + + for(IntIterator varItr = varsToTry.iterator(); varItr.hasNext();) { + final int var = varItr.next(); + varItr.remove(); + if (relevantVars.remove(var)) { // remove maxVar from the set of relevant variables + if (relevantVars.isEmpty()) break; // there was only root formula left + // get all axioms corresponding to the clauses that + // form the translations of formulas identified by relevant vars + final IntSet relevantClauses = StrategyUtils.clausesFor(trace, relevantVars); + assert !relevantClauses.isEmpty() && !relevantClauses.contains(trace.size()-1); + return relevantClauses; + } + } + varsToTry.clear(); + return Ints.EMPTY_SET; + } + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/ucore/StrategyUtils.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/ucore/StrategyUtils.java new file mode 100644 index 00000000..0f5e8e46 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/ucore/StrategyUtils.java @@ -0,0 +1,285 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.engine.ucore; + +import java.util.IdentityHashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; + +import kodkod.ast.Formula; +import kodkod.ast.Node; +import kodkod.ast.Variable; +import kodkod.engine.fol2sat.RecordFilter; +import kodkod.engine.fol2sat.TranslationLog; +import kodkod.engine.fol2sat.TranslationRecord; +import kodkod.engine.fol2sat.Translator; +import kodkod.engine.satlab.Clause; +import kodkod.engine.satlab.ResolutionTrace; +import kodkod.instance.TupleSet; +import kodkod.util.ints.IntBitSet; +import kodkod.util.ints.IntIterator; +import kodkod.util.ints.IntSet; +import kodkod.util.ints.IntTreeSet; +import kodkod.util.ints.Ints; +import kodkod.util.ints.SparseSequence; +import kodkod.util.ints.TreeSequence; + +/** + * A collection of utility methods for implementing + * logic-level reduction strategies. + * + * @author Emina Torlak + */ +public final class StrategyUtils { + private StrategyUtils() {} + + + /** + * Returns the variables that correspond to the roots of log.formula. + * @return + *
 
+	 * { v: int | some r: log.records | 
+	 *   r.node in log.roots() and 
+	 *   r.env.isEmpty() and
+	 *   abs(r.literal) != Integer.MAX_VALUE and
+	 *   v = abs(r.literal) and
+	 *   no r': log.records | r'.node = r.node && log.replay.r' > log.replay.r }
+	 * 
+ */ + public static IntSet rootVars(TranslationLog log) { + final IntSet rootVars = new IntTreeSet(); + final Set roots = log.roots(); + final Map maxRootVar = new LinkedHashMap(roots.size()); + final RecordFilter filter = new RecordFilter() { + public boolean accept(Node node, Formula translated, int literal, Map env) { + return roots.contains(translated) && env.isEmpty(); + } + }; + for(Iterator itr = log.replay(filter); itr.hasNext();) { + TranslationRecord record = itr.next(); + int[] var = maxRootVar.get(record.translated()); + if (var==null) { + var = new int[1]; + maxRootVar.put(record.translated(), var); + } + var[0] = StrictMath.abs(record.literal()); + } + + for(int[] var : maxRootVar.values()) { + int topVar = var[0]; + if (topVar != Integer.MAX_VALUE) // formula simplified to TRUE + rootVars.add(var[0]); + } + +// for(Map.Entry entry : maxRootVar.entrySet()) { +// final int topVar = entry.getValue()[0]; +// if (topVar != Integer.MAX_VALUE) // formula simplified to TRUE +// rootVars.add(topVar); +// System.out.println(topVar + " ==>" + entry.getKey()); +// } + + return rootVars; + } + + /** + * Returns a map from variables to the corresponding roots of log.formula. + * @return + *
 
+	 * { v: int, f: Formula | some r: log.records | 
+	 *   r.translated in log.roots() and 
+	 *   r.translated = f and
+	 *   r.env.isEmpty() and
+	 *   abs(r.literal) != Integer.MAX_VALUE and
+	 *   v = abs(r.literal) and
+	 *   no r': log.records | r'.node = r.node && log.replay.r' > log.replay.r }
+	 * 
+ */ + static SparseSequence roots(TranslationLog log) { + final SparseSequence rootVars = new TreeSequence(); + final Set roots = log.roots(); + final Map maxRootVar = new IdentityHashMap(roots.size()); + final RecordFilter filter = new RecordFilter() { + public boolean accept(Node node, Formula translated, int literal, Map env) { + return roots.contains(translated) && env.isEmpty(); + } + }; + for(Iterator itr = log.replay(filter); itr.hasNext();) { + TranslationRecord record = itr.next(); + int[] var = maxRootVar.get(record.translated()); + if (var==null) { + var = new int[1]; + maxRootVar.put(record.translated(), var); + } + var[0] = StrictMath.abs(record.literal()); + } + + for(Map.Entry entry : maxRootVar.entrySet()) { + final int topVar = entry.getValue()[0]; + if (topVar != Integer.MAX_VALUE) // formula simplified to TRUE + rootVars.put(topVar, entry.getKey()); + } + return rootVars; + } + + /** + * Returns the variables that correspond to the roots of log.formula, in the order + * in which they were specified in log.formula. + * @return variables that correspond to the roots of log.formula, in the order + * in which they were specified in log.formula. + */ +// static IntVector orderedRootVars(TranslationLog log) { +// final Set roots = log.roots(); +// final Map maxRootVar = new LinkedHashMap(roots.size()); +// final RecordFilter filter = new RecordFilter() { +// public boolean accept(Node node, int literal, Map env) { +// return roots.contains(node) && env.isEmpty(); +// } +// }; +// for(Iterator itr = log.replay(filter); itr.hasNext();) { +// TranslationRecord record = itr.next(); +// int[] var = maxRootVar.get(record.node()); +// if (var==null) { +// var = new int[1]; +// maxRootVar.put((Formula)record.node(), var); +// } +// var[0] = StrictMath.abs(record.literal()); +// } +// final IntSet uniqueRoots = new IntTreeSet(); +// final IntVector orderedRoots = new ArrayIntVector(roots.size()); +// for(int[] var : maxRootVar.values()) { +// int topVar = var[0]; +// if (topVar != Integer.MAX_VALUE) // formula simplified to TRUE +// if (uniqueRoots.add(var[0])) { +// orderedRoots.add(var[0]); +// }; +// } +// return orderedRoots; +// } + + /** + * Returns relevant core variables; that is, all variables that occur both in the positive and + * negative phase in trace.core. + * @return { v: [1..) | (some p, n: trace.core | v in trace.elts[p].literals and -v in trace.elts[n].literals) } + */ + public static IntSet coreVars(ResolutionTrace trace) { + + final IntSet posVars = new IntTreeSet(), negVars = new IntTreeSet(); + + for(Iterator iter = trace.iterator(trace.core()); iter.hasNext();) { + Clause clause = iter.next(); + for(IntIterator lits = clause.literals(); lits.hasNext(); ) { + int lit = lits.next(); + if (lit > 0) posVars.add(lit); + else negVars.add(-lit); + } + } + + posVars.retainAll(negVars); + + assert !posVars.isEmpty(); + final IntSet ret = new IntBitSet(posVars.max()+1); + ret.addAll(posVars); + + return ret; + } + + /** + * Returns the set of all variables in the core of the given trace + * that form unit clauses. + * @return { v: [1..) | some c: trace.core | c.size() = 1 and c.maxVariable() = v } + */ + public static IntSet coreUnits(ResolutionTrace trace) { + final IntSet units = new IntTreeSet(); + + for(Iterator itr = trace.reverseIterator(trace.core()); itr.hasNext(); ) { + Clause c = itr.next(); + if (c.size()==1) { + units.add(c.maxVariable()); + } + } + + if (units.isEmpty()) return Ints.EMPTY_SET; + + return Ints.asSet(units.toArray()); + } + + /** + * Returns the consecutive variables at the tail of the core of the given trace + * that form unit clauses. + * @return the consecutive variables at the tail of the core of the given trace + * that form unit clauses + */ + static IntSet coreTailUnits(ResolutionTrace trace) { + final IntSet units = new IntTreeSet(); + + for(Iterator itr = trace.reverseIterator(trace.core()); itr.hasNext(); ) { + Clause c = itr.next(); + if (c.size()==1) { + units.add(c.maxVariable()); + } else { + break; + } + } + + return units; + } + + /** + * Returns the indices of all axioms + * in the given trace that form the translations of the formulas + * identified by the given variables. This method assumes that + * the axioms in the given trace were generated by the Kodkod + * {@linkplain Translator}. + * @return + * let C = { c: trace.prover.clauses | c.maxVariable() in relevantVars }, + * T = { c1, c2: C | c2.maxVariable() in abs(c1.literals) } | + * C.*T + */ + static IntSet clausesFor(ResolutionTrace trace, IntSet relevantVars) { +// System.out.println("relevant: " + relevantVars); + final IntSet axioms = trace.axioms(); + + final IntSet reachableVars = new IntBitSet(relevantVars.max()+1); + reachableVars.addAll(relevantVars); + + final IntSet relevantAxioms = new IntBitSet(axioms.size()); + + final Iterator itr = trace.reverseIterator(axioms); + for(int i = axioms.max(); i >= 0; i--) { + Clause clause = itr.next(); + int maxVar = clause.maxVariable(); + if (reachableVars.contains(maxVar)) { + for(IntIterator lits = clause.literals(); lits.hasNext(); ) { + reachableVars.add(StrictMath.abs(lits.next())); + } + relevantAxioms.add(i); + } + } + + return relevantAxioms; + } + + + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/ucore/package.html b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/ucore/package.html new file mode 100644 index 00000000..b8bc00ea --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/engine/ucore/package.html @@ -0,0 +1,37 @@ + + + + + + + +Contains strategies for minimizing unsatisfiable cores generated by SAT provers. + +

Package Specification

+ +

Contains implementations of various {@linkplain kodkod.engine.satlab.ReductionStrategy strategies} +for minimizing unsatisfiable cores generated by {@linkplain kodkod.engine.satlab.SATProver SAT provers}.

+ +

Related Documentation

+ +@see kodkod.engine.satlab.ReductionStrategy +@see kodkod.engine.satlab.SATProver + + + + diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/instance/Bounds.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/instance/Bounds.java new file mode 100644 index 00000000..ae8f6c4d --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/instance/Bounds.java @@ -0,0 +1,299 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.instance; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; + +import kodkod.ast.Relation; +import kodkod.util.ints.IntSet; +import kodkod.util.ints.Ints; +import kodkod.util.ints.SparseSequence; +import kodkod.util.ints.TreeSequence; + + +/** + *

A Bounds object maps a {@link kodkod.ast.Relation relation} r to two + * {@link kodkod.instance.TupleSet sets of tuples}, rL and rU, which represent the lower and upper + * bounds on the {@link kodkod.instance.Tuple set of tuples} to which an {@link kodkod.instance.Instance instance} + * based on these bounds may map r. The set rL represents all the tuples + * that a given relation must contain. The set rU represents all the tuples + * that a relation may contain. All bounding sets range over the same {@link kodkod.instance.Universe universe}. + *

+ *

A Bounds object also maps integers to singleton tupleset that represent them. A tupleset may represent more than + * one integer, but an integer is represented by at most one tupleset.

+ * @specfield universe: Universe + * @specfield relations: set Relation + * @specfield intBound: int -> lone TupleSet + * @specfield lowerBound: relations -> one TupleSet + * @specfield upperBound: relations -> one TupleSet + * @invariant all i: intBound.TupleSet | intBound[i].size() = 1 && intBound[i].arity() = 1 + * @invariant lowerBound[relations].universe = upperBound[relations].universe = universe + * @invariant all r: relations | lowerBound[r].arity = upperBound[r].arity = r.arity + * @invariant all r: relations | lowerBound[r].tuples in upperBound[r].tuples + * @author Emina Torlak + **/ +public final class Bounds implements Cloneable { + private final TupleFactory factory; + private final Map lowers, uppers; + private final SparseSequence intbounds; + + /** + * Constructs a Bounds object with the given factory and mappings. + */ + private Bounds(TupleFactory factory, Map lower, Map upper, SparseSequence intbounds) { + this.factory = factory; + this.lowers = lower; + this.uppers = upper; + this.intbounds = intbounds; + } + + /** + * Constructs new Bounds over the given universe. + * @ensures this.universe' = universe && no this.relations' && no this.intBound' + * @throws NullPointerException - universe = null + */ + public Bounds(Universe universe) { + this.factory = universe.factory(); + lowers = new LinkedHashMap(); + uppers = new LinkedHashMap(); + intbounds = new TreeSequence(); + } + + /** + * Returns this.universe. + * @return this.universe + */ + public Universe universe() { return factory.universe(); } + + + /** + * Returns the set of all relations bound by this Bounds. + * The returned set does not support the add operation. + * It supports removal iff this is not an unmodifiable + * Bounds. + * @return this.relations + */ + public Set relations() { + return lowers.keySet(); + } + + /** + * Returns the set of all integers bound by this Bounds. + * The returned set does not support the add operation. + * It supports removal iff this is not an unmodifiable Bounds. + * @return this.intBounds.TupleSet + */ + public IntSet ints() { + return intbounds.indices(); + } + + /** + * Returns the set of tuples that r must contain (the lower bound on r's contents). + * If r is not mapped by this, null is returned. + * @return r in this.relations => lowerBound[r], null + */ + public TupleSet lowerBound(Relation r) { + return lowers.get(r); + } + + /** + * Returns a map view of this.lowerBound. The returned map is not modifiable. + * @return a map view of this.lowerBound + */ + public Map lowerBounds() { + return Collections.unmodifiableMap(lowers); + } + + /** + * Returns the set of tuples that r may contain (the upper bound on r's contents). + * If r is not mapped by this, null is returned. + * @return r in this.relations => upperBound[r], null + */ + public TupleSet upperBound(Relation r) { + return uppers.get(r); + } + + /** + * Returns a map view of this.upperBound. The returned map is not modifiable. + * @return a map view of this.upperBound + */ + public Map upperBounds() { + return Collections.unmodifiableMap(uppers); + } + + /** + * Returns the set of tuples representing the given integer. If i is not + * mapped by this, null is returned. + * @return this.intBound[i] + */ + public TupleSet exactBound(int i) { + return intbounds.get(i); + } + + /** + * Returns a sparse sequence view of this.intBound. The returned sequence + * is not modifiable. + * @return a sparse sequence view of this.intBound + */ + public SparseSequence intBounds() { + return Ints.unmodifiableSequence(intbounds); + } + + /** + * @throws IllegalArgumentException - arity != bound.arity + * @throws IllegalArgumentException - bound.universe != this.universe + */ + private void checkBound(int arity, TupleSet bound) { + if (arity != bound.arity()) + throw new IllegalArgumentException("bound.arity != r.arity"); + if (!bound.universe().equals(factory.universe())) + throw new IllegalArgumentException("bound.universe != this.universe"); + } + + /** + * Sets both the lower and upper bounds of the given relation to + * the given set of tuples. + * + * @requires tuples.arity = r.arity && tuples.universe = this.universe + * @ensures this.relations' = this.relations + r + * this.lowerBound' = this.lowerBound' ++ r->tuples && + * this.upperBound' = this.lowerBound' ++ r->tuples + * @throws NullPointerException - r = null || tuples = null + * @throws IllegalArgumentException - tuples.arity != r.arity || tuples.universe != this.universe + */ + public void boundExactly(Relation r, TupleSet tuples) { + checkBound(r.arity(), tuples); + final TupleSet unmodifiableTuplesCopy = tuples.clone().unmodifiableView(); + lowers.put(r, unmodifiableTuplesCopy); + uppers.put(r, unmodifiableTuplesCopy); + } + + /** + * Sets the lower and upper bounds for the given relation. + * + * @requires lower.tuples in upper.tuples && lower.arity = upper.arity = r.arity && + * lower.universe = upper.universe = this.universe + * @ensures this.relations' = this.relations + r && + * this.lowerBound' = this.lowerBound ++ r->lower && + * this.upperBound' = this.upperBound ++ r->upper + * @throws NullPointerException - r = null || lower = null || upper = null + * @throws IllegalArgumentException - lower.arity != r.arity || upper.arity != r.arity + * @throws IllegalArgumentException - lower.universe != this.universe || upper.universe != this.universe + * @throws IllegalArgumentException - lower.tuples !in upper.tuples + */ + public void bound(Relation r, TupleSet lower, TupleSet upper) { + if (!upper.containsAll(lower)) + throw new IllegalArgumentException("lower.tuples !in upper.tuples"); + if (upper.size()==lower.size()) { + // upper.containsAll(lower) && upper.size()==lower.size() => upper.equals(lower) + boundExactly(r, lower); + } else { + checkBound(r.arity(), lower); + checkBound(r.arity(), upper); + lowers.put(r, lower.clone().unmodifiableView()); + uppers.put(r, upper.clone().unmodifiableView()); + } + } + + /** + * Makes the specified tupleset the upper bound on the contents of the given relation. + * The lower bound automatically becomen an empty tupleset with the same arity as + * the relation. + * + * @requires upper.arity = r.arity && upper.universe = this.universe + * @ensures this.relations' = this.relations + r + * this.lowerBound' = this.lowerBound ++ r->{s: TupleSet | s.universe = this.universe && s.arity = r.arity && no s.tuples} && + * this.upperBound' = this.upperBound ++ r->upper + * @throws NullPointerException - r = null || upper = null + * @throws IllegalArgumentException - upper.arity != r.arity || upper.universe != this.universe + */ + public void bound(Relation r, TupleSet upper) { + checkBound(r.arity(), upper); + lowers.put(r, factory.noneOf(r.arity()).unmodifiableView()); + uppers.put(r, upper.clone().unmodifiableView()); + } + + /** + * Makes the specified tupleset an exact bound on the relational value + * that corresponds to the given integer. + * @requires ibound.arity = 1 && i.bound.size() = 1 + * @ensures this.intBound' = this.intBound' ++ i -> ibound + * @throws NullPointerException - ibound = null + * @throws IllegalArgumentException - ibound.arity != 1 || ibound.size() != 1 + * @throws IllegalArgumentException - ibound.universe != this.universe + */ + public void boundExactly(int i, TupleSet ibound) { + checkBound(1, ibound); + if (ibound.size() != 1) + throw new IllegalArgumentException("ibound.size != 1: " + ibound); + intbounds.put(i, ibound.clone().unmodifiableView()); + } + + + /** + * Returns an unmodifiable view of this Bounds object. + * @return an unmodifiable view of his Bounds object. + */ + public Bounds unmodifiableView() { + return new Bounds(factory, Collections.unmodifiableMap(lowers), Collections.unmodifiableMap(uppers), Ints.unmodifiableSequence(intbounds)); + } + + /** + * Returns a deep copy of this Bounds object. + * @return a deep copy of this Bounds object. + */ + public Bounds clone() { + try { + return new Bounds(factory, new LinkedHashMap(lowers), + new LinkedHashMap(uppers), intbounds.clone()); + } catch (CloneNotSupportedException cnse) { + throw new InternalError(); // should not be reached + } + } + + /** + * @see java.lang.Object#toString() + */ + public String toString() { + final StringBuilder str = new StringBuilder(); + str.append("relation bounds:"); + for(Map.Entry entry: lowers.entrySet()) { + str.append("\n "); + str.append(entry.getKey()); + str.append(": ["); + str.append(entry.getValue()); + TupleSet upper = uppers.get(entry.getKey()); + if (!upper.equals(entry.getValue())) { + str.append(", "); + str.append(upper); + } + str.append("]"); + } + str.append("\nint bounds: "); + str.append("\n "); + str.append(intbounds); + return str.toString(); + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/instance/Instance.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/instance/Instance.java new file mode 100644 index 00000000..44e2e315 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/instance/Instance.java @@ -0,0 +1,218 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.instance; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; + +import kodkod.ast.Relation; +import kodkod.util.ints.IntSet; +import kodkod.util.ints.Ints; +import kodkod.util.ints.SparseSequence; +import kodkod.util.ints.TreeSequence; + + +/** + * Represents a model (an instance) of a relational formula, which is a mapping + * from {@link kodkod.ast.Relation relations} and integers to {@link kodkod.instance.TupleSet sets of tuples} + * drawn from a given {@link kodkod.instance.Universe universe}. + * + * @specfield universe: Universe + * @specfield relations: set Relation + * @specfield tuples: (relations -> one TupleSet) + (int -> lone TupleSet) + * @invariant all r: tuples.TupleSet & Relation | r.arity = tuples[r].arity && tuples[r].universe = universe + * @invariant all i: tuples.TupleSet & int | ints[i].arity = 1 && ints[i].size() = 1 + * + * @author Emina Torlak + */ +public final class Instance implements Cloneable { + private final Map tuples; + private final SparseSequence ints; + private final Universe universe; + + private Instance(Universe u, Map tuples, SparseSequence ints) { + this.universe = u; + this.tuples = tuples; + this.ints = ints; + } + + /** + * Constructs an empty instance over the given universe + * + * @ensures this.universe' = universe && no this.tuples' + * @throws NullPointerException - universe = null + */ + public Instance(final Universe universe) { + if (universe==null) throw new NullPointerException("universe=null"); + this.universe = universe; + this.tuples = new LinkedHashMap(); + this.ints = new TreeSequence(); + } + + /** + * Returns the universe from which the tuples in this instance + * are drawn. + * @return this.universe + */ + public Universe universe() { + return universe; + } + + /** + * Returns true if this instance maps the given relation to a set + * of tuples; otherwise returns false. + * @return r in this.relations + */ + public boolean contains(Relation relation) { + return tuples.containsKey(relation); + } + + /** + * Returns true if this instance maps the given integer to a singleton + * tupleset; otherwise returns false. + * @return some this.tuples[i] + */ + public boolean contains(int i) { + return ints.containsIndex(i); + } + + /** + * Returns the relations mapped by this instance. The returned set + * does not support addition. It supports remval if this is not an + * unmodifiable instance. + * @return this.relations + */ + public Set relations() { + return tuples.keySet(); + } + + /** + * Returns the integers mapped by this instance. The returned set + * does not support addition. It supports remval if this is not an + * unmodifiable instance. + * @return this.ints.TupleSet + */ + public IntSet ints() { + return ints.indices(); + } + + /** + * Maps the given relation to the given tuple set. + * @ensures this.tuples' = this.tuples ++ relation->s + * @throws NullPointerException - relation = null || s = null + * @throws IllegalArgumentException - relation.arity != s.arity + * @throws IllegalArgumentException - s.universe != this.universe + * @throws UnsupportedOperationException - this is an unmodifiable instance + */ + public void add(final Relation relation, TupleSet s) { + if (!s.universe().equals(universe)) + throw new IllegalArgumentException("s.universe!=this.universe"); + if (relation.arity()!=s.arity()) + throw new IllegalArgumentException("relation.arity!=s.arity"); + tuples.put(relation, s.clone().unmodifiableView()); + } + + /** + * Maps the given integer to the given tuple set. + * @ensures this.tuples' = this.tuples ++ i->s + * @throws NullPointerException - s = null + * @throws IllegalArgumentException - s.arity != 1 || s.size() != 1 + * @throws IllegalArgumentException - s.universe != this.universe + * @throws UnsupportedOperationException - this is an unmodifiable instance + */ + public void add(int i, TupleSet s) { + if (!s.universe().equals(universe)) + throw new IllegalArgumentException("s.universe!=this.universe"); + if (s.arity()!=1) + throw new IllegalArgumentException("s.arity!=1: " + s); + if (s.size()!=1) + throw new IllegalArgumentException("s.size()!=1: " + s); + ints.put(i, s.clone().unmodifiableView()); + } + + /** + * Returns the set of tuples assigned to the given relation by this Instance. + * If the relation is not mapped by the model, null is returned. + * + * @return this.tuples[relation] + */ + public TupleSet tuples(Relation relation) { + return tuples.get(relation); + } + + /** + * Returns a map view of Relation<:this.tuples. The returned map is unmodifiable. + * @return a map view of Relation<:this.tuples. + */ + public Map relationTuples() { + return Collections.unmodifiableMap(tuples); + } + + /** + * Returns the set of tuples assigned to the given integer by this Instance. + * If the integer is not mapped by the model, null is returned. + * + * @return this.tuples[i] + */ + public TupleSet tuples(int i) { + return ints.get(i); + } + + /** + * Returns a sparse sequence view of int<:this.tuples. The returned sequence is unmodifiable. + * @return a sparse sequence view of int<:this.tuples. + */ + public SparseSequence intTuples() { + return Ints.unmodifiableSequence(ints); + } + + /** + * Returns an unmodifiable view of this instance. + * @return an unmodifiable view of this instance. + */ + public Instance unmodifiableView() { + return new Instance(universe, Collections.unmodifiableMap(tuples), Ints.unmodifiableSequence(ints)); + } + + /** + * Returns a deep copy of this Instance object. + * @return a deep copy of this Instance object. + */ + public Instance clone() { + try { + return new Instance(universe, new LinkedHashMap(tuples), ints.clone()); + } catch (CloneNotSupportedException cnse) { + throw new InternalError(); // should not be reached + } + } + + /** + * {@inheritDoc} + * @see java.lang.Object#toString() + */ + public String toString() { + return "relations: "+tuples.toString() + "\nints: " + ints; + } + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/instance/Tuple.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/instance/Tuple.java new file mode 100644 index 00000000..ab9405ea --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/instance/Tuple.java @@ -0,0 +1,133 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.instance; + + +/** + * Represents a sequence of atoms drawn from a given {@link kodkod.instance.Universe universe}. + * Note that a Tuple of arity n whose atoms belong to a Universe u encodes an n-digit number in + * base u.size. The decimal representation of this number is taken to be the index of the tuple + * in an n-dimensional space over the Universe u. + * + * @specfield arity: int + * @specfield universe: Universe + * @specfield atoms: [0..arity)->one Object + * @invariant atoms[int] in universe.atoms[int] + * + * @author Emina Torlak + */ +public abstract class Tuple { + + /** + * Returns the universe from which the atoms in this + * tuple are drawn. + * @return this.universe + */ + public abstract Universe universe(); + + /** + * Returns the arity of this tuple. + * @return this.arity + */ + public abstract int arity(); + + /** + * A Tuple encodes a number with this.arity digits in radix this.universe.size; + * a Tuple's index is the decimal representation of this number. + * + * @return sum({i: [0..arity) | universe.index(atoms[i]) * universe.size^(arity - 1 - i)}) + */ + public abstract int index(); + + /** + * Returns the atom at the specified index + * + * @return this.atoms[i] + * @throws IndexOutOfBoundsException - i < 0 || i >= this.arity + */ + public abstract Object atom(int i); + + /** + * Returns the index of the ith atom as given by this.universe. + * The effect of this method is the same as calling this.universe.index(this.atom(i)). + * + * @return this.universe.index(this.atoms[i]) + * @throws IndexOutOfBoundsException - i < 0 || i >= this.arity + */ + public abstract int atomIndex(int i); + + /** + * Returns true if atom is in this tuple, otherwise returns false. + * + * @return atom in this.atoms[int] + * @throws IllegalArgumentException - atom !in this.universe.atoms[int] + */ + public abstract boolean contains(Object atom); + + /** + * Returns the cross product of this and the specified tuple. + * + * @return {t : Tuple | t.atoms = this.atoms->tuple.atoms} + * @throws NullPointerException - tuple = null + * @throws IllegalArgumentException - tuple.universe != this.universe + */ + public abstract Tuple product(Tuple tuple); + + /** + * Returns true if o is a tuple with the same sequence of atoms as this, + * drawn from the same universe as this. Otherwise returns false. + * + * @return o in Tuple && o.universe = this.universe && o.atoms = this.atoms + */ + public boolean equals(Object o) { + if (this==o) return true; + else if (o instanceof Tuple) { + final Tuple t = (Tuple) o; + return universe().equals(t.universe()) && arity()==t.arity() && index()==t.index(); + } + else return false; + } + + /** + * Returns a hash code based on the tuple's arity, index, and the hash code + * of its universe, so that the general contract of Object.hashCode is obeyed. + * @return the hashcode for this tuple + */ + public int hashCode() { + return (arity() * 19 + index())^universe().hashCode(); + } + + /** + * @see java.lang.Object#toString() + */ + public String toString() { + final StringBuilder ret = new StringBuilder("["); + ret.append(atom(0)); + for (int i = 1; i < arity(); i++) { + ret.append(", "); + ret.append(atom(i)); + } + ret.append("]"); + return ret.toString(); + + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/instance/TupleFactory.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/instance/TupleFactory.java new file mode 100644 index 00000000..6a12b163 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/instance/TupleFactory.java @@ -0,0 +1,394 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.instance; + +import java.util.Collection; +import java.util.List; + +import kodkod.engine.CapacityExceededException; +import kodkod.util.ints.IntSet; +import kodkod.util.ints.Ints; + + +/** + * A factory class that facilitates creation of tuples + * and tuple sets drawn from a given universe. Only one + * factory per universe exists. + * + * @specfield universe: Universe + * @invariant no f: TupleFactory - this | f.universe = this.universe + * @author Emina Torlak + */ +public final class TupleFactory { + + private final Universe universe; + private final int base; + + /** + * Constructs a factory for the given universe. + * @requires no (TupleFactory<:universe).universe + * @ensures this.universe' = universe + * @throws NullPointerException - universe = null + */ + TupleFactory(Universe universe) { + this.universe = universe; + this.base = universe.size(); + } + + /** + * Returns the universe to which this factory belongs. + * @return this.universe + */ + public Universe universe() { return universe; } + + /** + * Returns a tuple that contains the specified sequence of atoms, + * drawn from this.universe. + * + * @return {t: Tuple | t.universe = this.universe && t.atoms = atoms } + * @throws NullPointerException - atoms = null + * @throws IllegalArgumentException - atoms.length < 1 + * @throws IllegalArgumentException - some a: atoms[int] | a !in this.universe.atoms[int] + */ + public Tuple tuple(Object... atoms) { + if (atoms.length<1) throw new IllegalArgumentException("atoms.length<1"); + return new IntTuple(atoms); + } + + /** + * Returns a tuple that contains the specified sequence of atoms, + * drawn from this.universe. + * + * @return {t: Tuple | t.universe = this.universe && t.atoms = atoms } + * @throws NullPointerException - atoms = null + * @throws IllegalArgumentException - atoms.size < 1 + * @throws IllegalArgumentException - some a: atoms[int] | a !in this.universe.atoms[int] + */ + public Tuple tuple(List atoms) { + if (atoms.size()<1) throw new IllegalArgumentException("atoms.size()<1"); + return new IntTuple(atoms.toArray()); + } + + /** + * Returns a tuple with the specified arity whose index in an arity-dimensional + * space over this.universe is given by the index parameter. + * + * @return {t: Tuple | t.universe = this.universe && t.arity = arity && + * index = sum({i : [0..arity) | universe.index(t.atoms[i]) * universe.size^(arity - 1 - i))}) } + * @throws IllegalArgumentException - arity < 1 || index < 0 || index >= universe.size^arity + */ + public Tuple tuple(final int arity, final int index) { + return new IntTuple(arity, index); + } + + /** + * Returns a set of all tuples of the given arity, drawn from this.universe. + * @return { s: TupleSet | s.universe = this.universe && s.arity = arity && + * s.tuples = {t: Tuple | t.universe = this.universe && t.arity = arity} } + * @throws IllegalArgumentException - arity < 1 + */ + public TupleSet allOf(int arity) { + return new TupleSet(universe, arity, + 0, ((int) Math.pow(base, arity)) - 1); + } + + /** + * Returns a set of tuples of arity 1, each of which wraps one of the given objects. + * The method requires that the specified object be atoms in this.universe. + * + * @return {s: TupleSet | s.universe = this.universe && s.arity = 1 && + * s.tuples = { t: Tuple | t.universe=this.universe && + * t.arity=1 && t.atoms[0] in atoms[int]}} + * @throws NullPointerException - atoms = null + * @throws IllegalArgumentException - some atoms[int] - this.universe.atoms[int] + */ + public TupleSet setOf(Object... atoms) { + final TupleSet ret = new TupleSet(universe, 1); + for (Object atom: atoms) { + ret.add(new IntTuple(atom)); + } + return ret; + } + + /** + * Returns a tuple set consisting of specified tuples. The method requires that + * all given tuples have the same arity and be drawn from this.universe. + * + * @return {s: TupleSet | s.universe = this.universe && s.arity = first.arity && + * s.tuples = first + rest[int] } + * @throws NullPointerException - first = null || rest = null + * @throws IllegalArgumentException - first.universe != this.universe + * @throws IllegalArgumentException - some t: rest[int] | t.universe != this.universe || t.arity != first.arity + */ + public TupleSet setOf(Tuple first, Tuple... rest) { + if (!first.universe().equals(universe)) + throw new IllegalArgumentException("first.universe != this.universe"); + + final TupleSet ret = new TupleSet(universe, first.arity(), first.index(), first.index()); + for(Tuple tuple: rest) { + ret.add(tuple); + } + return ret; + } + + /** + * Returns a tuple set consisting of specified tuples. The method requires that + * all given tuples have the same arity and be drawn from this.universe. + * + * @return {s: TupleSet | s.universe = this.universe && s.arity = first.arity && + * s.tuples = tuples } + * @throws NullPointerException - tuples = null + * @throws IllegalArgumentException - tuples.isEmpty() + * @throws IllegalArgumentException - tuples.universe != this.universe || #tuples.arity > 1 + */ + public TupleSet setOf(Collection tuples) { + if (tuples.isEmpty()) + throw new IllegalArgumentException("tuples.isEmpty()"); + final TupleSet ret = new TupleSet(universe, tuples.iterator().next().arity()); + for(Tuple t : tuples) { + ret.add(t); + } + return ret; + } + + /** + * Returns a set of the given arity that contains all tuples whose indeces + * are contained in the given int set. Throws an IllegalArgumentException + * if the set contains an index that is either negative or greater than + * this.universe.size()^arity - 1. The returned TupleSet is backed by a clone + * of tupleIndices. + * @requires tupleIndices is cloneable + * @return {s: TupleSet | s.universe = this.universe && s.arity = arity && + * s.tuples = {t: Tuple | t.index() in tupleIndices} } + * @throws NullPointerException - tupleIndices = null + * @throws IllegalArgumentException - tupleIndices is uncloneable + * @throws IllegalArgumentException - arity < 1 + * @throws IllegalArgumentException - tupleIndices.min() < 0 || tupleIndices.max() >= this.universe.size()^arity + */ + public TupleSet setOf(int arity, IntSet tupleIndices) { + try { + return new TupleSet(universe,arity,tupleIndices.clone()); + } catch (CloneNotSupportedException cne){ + throw new IllegalArgumentException("uncloneable int set"); + } + } + + /** + * Returns an initially empty tuple set of the given arity, based on this.universe. + * @return { s: TupleSet | s.universe = this.universe && s.arity = arity && no s.tuples } + * @throws IllegalArgumentException - arity < 1 + */ + public TupleSet noneOf(int arity) { + return new TupleSet(universe, arity); + } + + /** + * Returns a tuple set that contains all tuples between from + * and to, inclusive. More formally, the returned set contains + * all tuples whose indeces are in the range [from.index()..to.index()]. + * @return { s: TupleSet | s.universe = this.universe && s.arity = from.arity && + * s.tuples = {t: Tuple | t.universe = this.universe && + * t.arity = s.arity && + * from.index()<=t.index()<=to.index() }} + * @throws NullPointerException - from = null || to = null + * @throws IllegalArgumentException - from.arity != to.arity + * @throws IllegalArgumentException - from.universe != this.universe || to.universe != this.universe + * @throws IllegalArgumentException - from.index > to.index + */ + public TupleSet range(Tuple from, Tuple to) { + if (from.arity()!=to.arity()) + throw new IllegalArgumentException("from.arity!=to.arity"); + if (!(from.universe().equals(universe)&&to.universe().equals(universe))) + throw new IllegalArgumentException("from.universe != this.universe || to.universe != this.universe"); + return new TupleSet(universe, from.arity(), from.index(), to.index()); + } + + /** + * Returns a tuple set that contains all tuples in the specified area + * of the n-dimensional space, where n is the arity of the argument + * tuples. For example, suppose that this.universe consists of atoms + * {atom0, atom1, atom2, atom3}, where atom0 has index 0, atom1 has index 1, etc. + * Calling this method with tuples [atom0, atom2] and [atom1, atom3] as the + * first and second arguments would result in the set {[atom0, atom2], + * [atom0,atom3], [atom1,atom2], [atom1, atom3]}. That is, the returned set + * consists of all points in the rectangle whose upper left corner is the + * point [atom0, atom2] and whose lower right corner is at [atom1, atom3]. + * @return {s: TupleSet | s.arity = upperLeft.arity && + * s.universe = this.universe && + * s.tuples = {t: Tuple | all i: [0..s.arity) | + * this.universe.index(upperLeft.atoms[i]) <= + * this.universe.index(t.atoms[i]) <= + * this.universe.index(lowerRight.atoms[i]}} + * @throws NullPointerException - upperLeft = null || lowerRight = null + * @throws IllegalArgumentException - upperLeft.arity != lowerRight.arity + * @throws IllegalArgumentException - lowerRight.universe != this.universe || upperLeft.universe != this.universe + * @throws IllegalArgumentException - some i: [0..upperLeft.arity) | + * this.universe.index(upperLeft.atoms[i]) > + * this.universe.index(lowerRight.atoms[i]) + */ + public TupleSet area(Tuple upperLeft, Tuple lowerRight) { + if (!upperLeft.universe().equals(universe) || upperLeft.arity()!=lowerRight.arity()) + throw new IllegalArgumentException(); + TupleSet ret = new TupleSet(universe, 1, upperLeft.atomIndex(0),lowerRight.atomIndex(0)); + for(int i = 1; i < upperLeft.arity(); i++) { + ret = ret.product(new TupleSet(universe, 1, upperLeft.atomIndex(i),lowerRight.atomIndex(i))); + } + return ret; + } + + /** + * Throws a CapacityExceededException if all tuples of the given arity + * drawn from this.universe cannot be represented as an integer. + * @throws CapacityExceededException if all tuples of the given arity + * drawn from this.universe cannot be represented as an integer. + */ + void checkCapacity(int arity) { + if (StrictMath.pow(base,arity) > Integer.MAX_VALUE) { + throw new CapacityExceededException("Arity too large (" + arity + ") for a universe of size " + universe.size(), Ints.nCopies(arity, base)); + } + } + + /** + * Projects the tuple with the specified index and arity onto the + * specified column. + * @requires tupleIndex >= 0 && tupleIndex < this.universe.size() ^ arity + * @return this.universe.index(this.tuple(arity, tupleIndex).atoms[i]) + */ + int project(int tupleIndex, int arity, int column) { + if (column < 0 || column >= arity) throw new IndexOutOfBoundsException(column+""); + return (tupleIndex / ((int) Math.pow(base, arity-1-column))) % base; + } + + /** + * An implementation of the Tuple interface that stores + * only the tuple's arity and index, rather than the full + * sequence of atoms. Parts of the sequence are computed on + * demand, e.g. when the get method is invoked. + * + * @specfield universe: TupleFactory.this.universe + * @specfield arity: int + * @specfield index: int + * @invariant arity >= 1 && 0 <= index < TupleFactory.this.base^arity + * @invariant index = sum({i: [0..arity) | TupleFactory.this.universe.index(atoms[i]) * TupleFactory.this.base^(arity - 1 - i)) + * @author Emina Torlak + */ + private final class IntTuple extends Tuple { + private final int arity, index; + + /** + * Constructs a tuple with the specified arity and index, whose atoms + * are drawn from the factory's universe. + * + * @ensures this.arity' = arity && + * this.index' = index + * @throws IllegalArgumentException - arity < 1 || index < 0 || index >= TupleFactory.this.base^arity + */ + IntTuple(final int arity, final int index) { + checkCapacity(arity); + if (arity < 1 || index < 0 || index >= Math.pow(base, arity)) { + throw new IllegalArgumentException("arity < 1 || index < 0 || index >= universe.size^arity"); + } + this.arity = arity; + this.index = index; + } + + /** + * Constructs a tuple that contains the specified sequence of atoms, drawn from the + * enclosing factory's universe. + * + * @requires atoms.length > 0 + * @ensures this.atoms' = atoms + * @throws NullPointerException - atoms = null + * @throws IllegalArgumentException - some a: atoms[int] | a !in universe.atoms[int] + */ + IntTuple(final Object... atoms) { + this.arity = atoms.length; + checkCapacity(arity); + int tempIndex = 0, multiplier = 1; + for (int i = arity - 1; i >= 0; i--) { + tempIndex += universe.index(atoms[i]) * multiplier; + multiplier *= base; + } + this.index = tempIndex; + assert this.index >= 0; + } + + /** + * Constructs a tuple with the specified arity, with the specified atom + * at each position. + * @ensures this.arity' = arity && this.atoms = [0..arity)->atom + * @throws NullPointerException - atom = null + * @throws IllegalArgumentException - arity < 1 || atom !in this.universe.atoms[int] + */ + @SuppressWarnings("unused") + IntTuple(final int arity, final Object atom) { + checkCapacity(arity); + if (arity < 1) throw new IllegalArgumentException("arity < 1"); + this.arity = arity; + int tempIndex = 1; + for (int i = 0; i < arity; i++) { + tempIndex = tempIndex*base + 1; + } + this.index = universe.index(atom) * tempIndex; + assert this.index >= 0; + } + + /** {@inheritDoc} */ + public Universe universe() { return universe; } + + /** {@inheritDoc} */ + public int arity() { return arity; } + + /** {@inheritDoc} */ + public int index() { return index; } + + /** {@inheritDoc} */ + public Object atom(int i) { + return universe.atom(atomIndex(i)); + } + + /** {@inheritDoc} */ + public int atomIndex(int i) { + return project(index,arity,i); +// if (i < 0 || i >= arity) throw new IndexOutOfBoundsException("i < 0 || i >= this.arity"); +// return (index / ((int) Math.pow(base, arity-1-i))) % base; + } + + /** {@inheritDoc} */ + public boolean contains(Object atom) { + for (int remainder = index, atomIndex = universe.index(atom); + remainder > 0; remainder = remainder / base) { + if (remainder % base == atomIndex) return true; + } + return false; + } + + /** {@inheritDoc} */ + public Tuple product(Tuple tuple) { + if (!universe.equals(tuple.universe())) throw new IllegalArgumentException("tuple.universe != this.universe"); + return new IntTuple(arity + tuple.arity(), + index * ((int)Math.pow(base, tuple.arity())) + tuple.index()); + } + } + + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/instance/TupleSet.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/instance/TupleSet.java new file mode 100644 index 00000000..b63df63d --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/instance/TupleSet.java @@ -0,0 +1,418 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.instance; + +import java.util.AbstractSet; +import java.util.Collection; +import java.util.Iterator; + +import kodkod.util.ints.IntIterator; +import kodkod.util.ints.IntSet; +import kodkod.util.ints.Ints; + + +/** + * Represents a set of {@link kodkod.instance.Tuple tuples} + * of a given arity, constructed over a given {@link kodkod.instance.Universe universe}. + * All polymorphic methods throw a ClassCastException when passed + * an element that is not a Tuple. All methods throw a NullPointerException + * when passed null. The iterator of a TupleSet returns tuples in the order of their + * {@link kodkod.instance.Tuple#index() indeces}. + * + * @specfield tuples: set Tuple + * @specfield universe: Universe + * @specfield arity: int + * @invariant tuples.arity = arity && tuples.universe = universe + * + * @author Emina Torlak + */ +public final class TupleSet extends AbstractSet implements Cloneable { + private final Universe universe; + private final int arity; + private final IntSet tuples; + private IntSet indexView = null; + + /** + * Constructs an empty tuple set for storing tuples + * of the specified arity, over the given universe. + * + * @ensures this.universe' = universe && this.arity' = arity && no this.tuples' + * @throws NullPointerException - universe = null + * @throws IllegalArgumentException - arity < 1 + */ + TupleSet(Universe universe, int arity) { + if (arity < 1) throw new IllegalArgumentException("arity < 1"); + universe.factory().checkCapacity(arity); + this.universe = universe; + this.arity = arity; + tuples = Ints.bestSet(capacity()); + } + + /** + * Constructs a tuple set of the given arity, over the specified universe, + * which initially contains all tuples whose indeces are between fromIndex + * and toIndex, inclusive. + * + * @ensures this.universe' = universe && this.arity' = arity && + * this.tuples' = {t: Tuple | t.universe=universe && t.arity=arity && + * fromIndex()<=t.index()<=toIndex() } + * @throws NullPointerException - universe = null + * @throws IllegalArgumentException - arity < 1 || + * @throws IndexOutOfBoundsException - fromIndex !in [0..toIndex] || + * toIndex !in [0..universe.size()^arity - 1] + */ + TupleSet(Universe universe, int arity, int fromIndex, int toIndex) { + this(universe,arity); + checkRange(toIndex, 0, capacity() - 1); + checkRange(fromIndex, 0, toIndex); + for(int i = fromIndex; i <= toIndex; i++) { + tuples.add(i); + } + } + + /** + * Returns a set of the given arity that contains all tuples whose indeces + * are contained in the given int set. Throws an IllegalArgumentException + * if the set contains an index that is either negative or greater than + * this.universe.size()^arity - 1. An attempt to iterate over a tuple set backed by an invalid index + * set will result in a runtime exception. + * @return {s: TupleSet | s.universe = this.universe && s.arity = arity && + * s.tuples = {t: Tuple | t.index() in tupleIndeces} } + * @throws NullPointerException - tupleIndeces = null + * @throws IllegalArgumentException - arity < 1 + * @throws IllegalArgumentException - tupleIndeces.min() < 0 || tupleIndeces.max() >= this.universe.size()^arity + */ + TupleSet(Universe universe, int arity, IntSet tupleIndeces) { + if (arity < 1) throw new IllegalArgumentException("arity < 1"); + universe.factory().checkCapacity(arity); + this.universe = universe; + this.arity = arity; + if (!tupleIndeces.isEmpty()) { + if (tupleIndeces.min()<0 || tupleIndeces.max() >= capacity()) + throw new IllegalArgumentException(tupleIndeces.min() + "<0 || " + tupleIndeces.max()+">="+universe.size()+"^"+arity); + } + tuples = tupleIndeces; + } + + /** + * Copy constructor. + * @ensures constructs a deep copy of the given tupleset + */ + private TupleSet(TupleSet original) { + this.universe = original.universe; + this.arity = original.arity; + try { + this.tuples = original.tuples.clone(); + } catch (CloneNotSupportedException e) { + throw new InternalError(); // unreachable code + } + this.indexView = null; + } + + /** + * Throws an IndexOutOfBoundsException if index is not in [min..max] + */ + private final void checkRange(int index, int min, int max) { + if (index < min || index > max) + throw new IndexOutOfBoundsException(index + " !in " + "[" + min + ".." + max + "]"); + } + + /** + * Returns the capacity of this set -- the maximum number of tuples + * that it can hold, given its universe and arity. + * @return this.universe.size() ^ this.arity + */ + public final int capacity() { + return (int) StrictMath.pow(universe.size(),arity); + } + + /** + * Returns this.universe. + * @return this.universe + */ + public Universe universe() { return universe; } + + /** + * Returns this.arity + * @return this.arity + */ + public int arity() { return arity; } + + + + /** + * Returns an unmodifiable int set view of the tuples stored in this set. + * Specifically, the returned int set contains an integer i + * iff this set contains a tuple with the index i. The + * view is backed by this set, so changes to this set are + * reflected in the index set. + * @return { s: IntSet | s.ints = {i: int | some t: this.tuples | t.index = i} + */ + public IntSet indexView() { + if (indexView==null) { + indexView = Ints.unmodifiableIntSet(tuples); + } + return indexView; + } + + /** + * Returns an unmodifiable view of the this tupleset. This method allows modules to + * provide "read-only" access to internal tuple sets. Query operations on the returned set + * "read through" to the specified set, and attempts to modify the returned set, whether direct + * or via its iterator, result in an UnsupportedOperationException. + * @return an unmodifiable view of the this tupleset + */ + public TupleSet unmodifiableView() { + return new TupleSet(universe,arity,indexView()); + } + + /** + * Returns a tuple set that is the cross product of this and the + * specified set. + * @return {t: TupleSet | t.arity = this.arity + s.arity && + * t.universe = this.universe && + * t.tuples = this.tuples->s.tuples } + * @throws NullPointerException - s = null + * @throws IllegalArgumentException - s.universe != this.universe + */ + public TupleSet product(TupleSet s) { + if (!s.universe().equals(universe)) + throw new IllegalArgumentException("s.universe != this.universe"); + final TupleSet ret = new TupleSet(universe, arity+s.arity()); + if (!s.isEmpty()) { + final int mCapacity = (int) StrictMath.pow(universe.size(), s.arity); + for(IntIterator indeces0 = tuples.iterator(); indeces0.hasNext(); ) { + int i0 = mCapacity * indeces0.next(); + for(IntIterator indeces1 = s.tuples.iterator(); indeces1.hasNext(); ) { + ret.tuples.add(i0 + indeces1.next()); + } + } + } + return ret; + } + + /** + * Projects this TupleSet onto the given dimension. + * @return {s: TupleSet | s.arity = 1 && s.universe = this.universe && + * s.tuples = { t: Tuple | some q: this.tuples | q.atoms[dimension] = t.atoms[int] } } + * @throws IllegalArgumentException - dimension < 0 || dimension >= this.arity + */ + public TupleSet project(int dimension) { + if (dimension < 0 || dimension >= arity) { + throw new IllegalArgumentException("dimension < 0 || dimension >= this.arity"); + } + final IntSet projection = Ints.bestSet(universe.size()); + final TupleFactory factory = universe.factory(); + for(IntIterator indexIter = tuples.iterator(); indexIter.hasNext();) { + projection.add(factory.project(indexIter.next(), arity, dimension)); + } + return new TupleSet(universe,1,projection); + } + + /** + * Returns a deep copy of this tuple set. + * @return {s: TupleSet - this | s.universe = this.universe && s.tuples = this.tuples } + */ + public TupleSet clone() { + // ok to use a copy constructor to clone a final class + return new TupleSet(this); + } + + /** + * Returns an iterator over the tuples in this tupleset. + * @return an iterator over the tuples in this tupleset. + */ + @Override + public Iterator iterator() { + return new Iterator() { + IntIterator indexIter = tuples.iterator(); + public boolean hasNext() { + return indexIter.hasNext(); + } + + public Tuple next() { + return universe.factory().tuple(arity, indexIter.next()); + } + + public void remove() { + indexIter.remove(); + } + }; + } + + /** + * Returns the index of the given tuple, if the tuple has the same + * arity and universe as this. Otherwise throws an IllegalArgumentException. + * @return t.index + * @throws IllegalArgumentException - t.arity != this.arity || t.universe != this.universe + */ + private final int extractIndex(Tuple t) { + if (t.arity() != arity || !t.universe().equals(universe)) { + throw new IllegalArgumentException("t.arity != this.arity || t.universe != this.universe"); + } + return t.index(); + } + + /** + * Returns true if this contains the given object. + * @return o in this.tuples + * @throws IllegalArgumentException o.arity != this.arity || o.universe != this.universe + */ + @Override + public boolean contains(Object o) { + return tuples.contains(extractIndex((Tuple)o)); + } + + /** + * Returns the size of this tupleset. + * @return #this.tuples + */ + @Override + public int size() { return tuples.size(); } + + /** + * Removes all tuples from this tupleset. + * @ensures no this.tuples' + */ + @Override + public void clear() { + tuples.clear(); + } + + /** + * Adds the specified tuple to this tupleset. Returns + * true if this set was changed as the result of the + * operation. + * @ensures this.tuples' = this.tuples + t + * @return o !in this.tuples + * @throws IllegalArgumentException - t.universe != this.universe || t.arity != this.arity + */ + @Override + public boolean add(Tuple t) { + return tuples.add(extractIndex(t)); + } + + /** + * Removes the given object from this tupleset, if present, and + * returns true. Otherwise does nothing and returns false. + * @ensures this.tuples' = this.tuples - o + * @return o in this.tuples + * @throws IllegalArgumentException - o.universe != this.universe || o.arity != this.arity + */ + @Override + public boolean remove(Object o) { + return tuples.remove(extractIndex((Tuple)o)); + } + + /** + * If c is not a TupleSet or it is a tupleset with a universe different than + * this.universe, returns null. Otherwise, returns the tuples associated + * with the modifiable view of c. + * @requires c in TupleSet => c.arity = this.arity + * @return c in TupleSet && c.universe = this.universe && c.arity = this.arity => c.tuples, null + * @throws NullPointerException - s = null + * @throws IllegalArgumentException - this.arity!=s.arity + */ + private IntSet extractTuples(Collection c) { + if (c instanceof TupleSet) { + final TupleSet s = (TupleSet) c; + if (arity!=s.arity()) + throw new IllegalArgumentException("this.arity!=c.arity"); + return universe.equals(s.universe()) ? s.tuples : null; + } + return null; + } + + /** + * Returns true if this contains all tuples from c. Otherwise returns false. + * @return c.elements in this.tuples + * @throws IllegalArgumentException - some t: c.elements | t.universe != this.universe || t.arity != this.arity + */ + @Override + public boolean containsAll(Collection c) { + final IntSet cTuples = extractTuples(c); + return cTuples==null ? super.containsAll(c) : tuples.containsAll(cTuples); + } + + /** + * Adds all tuples from c to this, if not present, and returns + * true. Otherwise does nothing and returns false. + * @ensures this.tuples' = this.tuples + c.elements + * @return c.elements !in this.tuples + * @throws IllegalArgumentException - some t: c.elements | t.universe != this.universe || t.arity != this.arity + */ + @Override + public boolean addAll(Collection c) { + final IntSet cTuples = extractTuples(c); + return cTuples==null ? super.addAll(c) : tuples.addAll(cTuples); + } + + /** + * Removes all tuples in c from this, if present, and returns + * true. Otherwise does nothing and returns false. + * @ensures this.tuples' = this.tuples - c.elements + * @return some c.elements & this.tuples + * @throws IllegalArgumentException - some t: c.elements | t.universe != this.universe || t.arity != this.arity + */ + @Override + public boolean removeAll(Collection c) { + final IntSet cTuples = extractTuples(c); + return cTuples==null ? super.removeAll(c) : tuples.removeAll(cTuples); + } + + /** + * Removes all tuples from this that are not in c, if any, and + * returns true. Otherwise does nothing and returns false. + * @ensures this.tuples' = this.tuples & c.elements + * @return this.tuples !in c.elements + * @throws IllegalArgumentException - some t: c.elements | t.universe != this.universe || t.arity != this.arity + */ + @Override + public boolean retainAll(Collection c) { + final IntSet cTuples = extractTuples(c); + return cTuples==null ? super.retainAll(c) : tuples.retainAll(cTuples); + } + + + /** + * Returns true if o contains the same tuples as this. + * @return this.tuples = o.elements + */ + @Override + public boolean equals(Object o) { + if (this==o) return true; + if (o instanceof TupleSet) { + final TupleSet s = (TupleSet) o; + return arity==s.arity && universe.equals(s.universe) && + tuples.equals(s.tuples); + } + return super.equals(o); + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return tuples.hashCode(); + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/instance/Universe.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/instance/Universe.java new file mode 100644 index 00000000..4a6cde7c --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/instance/Universe.java @@ -0,0 +1,188 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.instance; + +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import kodkod.util.collections.Containers; + +/** + *

Represents an ordered set of unique atoms. Objects used as atoms must properly + * implement {@link java.lang.Object#equals equals} and {@link java.lang.Object#hashCode hashCode} + * methods. The behavior of a universe is not specified if the value of an object is changed in a + * manner that affects equals comparisons while the object is an atom in the universe.

+ * + *

Each universe provides a {@link kodkod.instance.TupleFactory tuple factory} + * to facilitate creation of {@link kodkod.instance.Tuple tuples} and + * {@link kodkod.instance.TupleSet sets of tuples} based on the atoms in the universe.

+ * + *

Implementation Note: although the atoms in a universe are not interpreted in any + * way by the Kodkod engine, it is strongly recommended that the atoms + * of the same 'type' be grouped together. For instance, suppose that a client model is specified in + * terms of disjoint types Person = {Person0, Person1, Person2} and Dog = {Dog0, Dog1}. Then, + * the client may observe an improvement in performance if he constructs the universe over the + * sequences {Person0, Person1, Person2, Dog0, Dog1} or {Dog0, Dog1, Person0, Person1, Person2} rather + * than any other permutation of these five atoms. + * + * @specfield size: int + * @specfield atoms: [0..size)->one Object + * @specfield factory: TupleFactory + * @invariant factory = (TupleFactory<:universe).this + * @invariant size > 0 + * @author Emina Torlak + */ +public final class Universe implements Iterable { + private final Object[] atoms; + private final Map indices; + private final TupleFactory factory; + + /** + * Constructs a new Universe consisting of the given atoms, in the order that they are returned + * by the specified Collection's Iterator. + * + * @ensures this.size' = atoms.size && this.atoms' = atoms.iterator + * @throws NullPointerException - atoms = null + * @throws IllegalArgumentException - atoms contains duplicates + * @throws IllegalArgumentException - atoms is empty + */ + public Universe(Collection atoms) { + if (atoms.isEmpty()) throw new IllegalArgumentException("Cannot create an empty universe."); + this.atoms = new Object[atoms.size()]; + this.indices = new HashMap(); + int i = 0; + for (Object atom : atoms) { + if (indices.containsKey(atom)) + throw new IllegalArgumentException(atom + " appears multiple times."); + indices.put(atom, i); + this.atoms[i] = atom; + i++; + } + this.factory = new TupleFactory(this); + } + + /** + * Constructs a new Universe consisting of the given atoms, in the order that they appear + * in the specified array + * + * @ensures this.size' = atoms.length && this.atoms' = atoms + * @throws NullPointerException - atoms = null + * @throws IllegalArgumentException - atoms contains duplicates + * @throws IllegalArgumentException - atoms is empty + */ + public Universe(Object...atoms) { + if (atoms.length==0) throw new IllegalArgumentException("Cannot create an empty universe."); + this.atoms = Containers.copy(atoms, new Object[atoms.length]); + this.indices = new HashMap(); + for (int i = 0; i < atoms.length; i++) { + if (indices.containsKey(atoms[i])) + throw new IllegalArgumentException(atoms[i] + " appears multiple times."); + indices.put(atoms[i], i); + } + this.factory = new TupleFactory(this); + } + + /** +// * Returns a universe that stores the given atoms explicitly, in the specified order. +// * The returned universe provides constant time index and atom lookup. +// * @ensures { u: Universe | u.size = atoms.length && u.atoms = atoms } +// * @throws IllegalArgumentException - atoms contains duplicates +// * @throws IllegalArgumentException - atoms is empty +// */ +// public static Universe universe(Object...atoms) { return new ExplicitUniverse(atoms); } +// +// /** +// * Returns a universe that stores the given atoms explicitly, in the order in which they +// * are returned by the collection's iterator. The returned universe provides constant +// * time index and atom lookup. +// * @ensures { u: Universe | u.size = atoms.size() && u.atoms[int] = atoms } +// * @throws IllegalArgumentException - atoms contains duplicates +// * @throws IllegalArgumentException - atoms is empty +// */ +// public static Universe universe(Collection atoms) { return new ExplicitUniverse(atoms); } + + /** + * Returns a TupleFactory that can be used to construct tuples and sets + * of tuples based on this universe. + * @return this.factory + */ + public TupleFactory factory() { return this.factory; } + + /** + * Returns the size of this universe + * + * @return #this.atoms + */ + public int size() { return atoms.length; } + + /** + * Returns true if atom is in this universe, otherwise returns false. + * + * @return atom in this.atoms[int] + */ + public boolean contains(Object atom) { return indices.containsKey(atom); } + + /** + * Returns the i_th atom in this universe + * + * @return this.atoms[i] + * @throws IndexOutOfBoundsException - i < 0 || i >= this.size + */ + public Object atom(int index) { + if (index<0||index>=atoms.length) throw new IndexOutOfBoundsException(); + return atoms[index]; + } + + /** + * Returns the index of the specified atom in this universe; if the + * atom is not in this universe, an IllegalArgumentException is thrown. + * + * @return this.atoms.atom + * @throws IllegalArgumentException - no this.atoms.atom + */ + public int index(Object atom) { + if (indices.containsKey(atom)) return indices.get(atom); + else throw new IllegalArgumentException("no this.atoms." + atom); + } + + /** + * Returns an iterator over atoms in this universe, according to their + * order in the universe. + * @return an iterator over atoms in this universe, according to their + * order in the universe + */ + public Iterator iterator() { + return Containers.iterate(atoms); + } + + /** + * Returns a string representation of this universe. + * @return string representation of this universe. + */ + public String toString() { + return Arrays.toString(atoms); + } + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/instance/package.html b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/instance/package.html new file mode 100644 index 00000000..ade655f5 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/instance/package.html @@ -0,0 +1,42 @@ + + + + + + + +Contains classes for creating tuples, sets of tuples, bounds, and instances +drawn from a finite universe of uninterpreted atoms. + +

Package Specification

+ +

Contains classes for creating {@linkplain kodkod.instance.Tuple tuples}, +{@linkplain kodkod.instance.TupleSet sets of tuples}, {@linkplain kodkod.instance.Bounds bounds}, and +{@linkplain kodkod.instance.Instance instances} drawn from a finite +{@linkplain kodkod.instance.Universe universe} of uninterpreted atoms.

+ +

Related Documentation

+ +@see kodkod.instance.Universe +@see kodkod.instance.TupleFactory +@see kodkod.instance.Bounds +@see kodkod.instance.Instance + + + + diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/collections/ArrayStack.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/collections/ArrayStack.java new file mode 100644 index 00000000..ba27de9c --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/collections/ArrayStack.java @@ -0,0 +1,166 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.util.collections; + +import java.util.EmptyStackException; +import java.util.Iterator; +import java.util.NoSuchElementException; + +/** + * A Stack implementation based on an array. + * + * @author Emina Torlak + */ +public class ArrayStack extends Stack { + /* stack elements: the last element in the array is at the top of the stack. */ + private T[] elems; + private int size; + + /** + * Constructs an empty stack with the inital capacity of 10. + * @ensures no this.elems' + */ + public ArrayStack() { + this(10); + } + + @SuppressWarnings("unchecked") + public ArrayStack(int initialCapacity) { + if (initialCapacity < 0) + throw new IllegalArgumentException(initialCapacity + "<0"); + elems = (T[]) new Object[initialCapacity]; + size = 0; + } + + /** + * Increases the capacity of this ArrayStack, if necessary, to ensure that + * it can hold at least the number of elements specified by the minimum + * capacity argument. + */ + @SuppressWarnings("unchecked") + public void ensureCapacity(int minCapacity) { + final int oldCapacity = elems.length; + if (minCapacity > oldCapacity) { + final T[] oldElems = elems; + elems = (T[]) new Object[StrictMath.max(minCapacity, (oldCapacity * 3) / 2)]; + System.arraycopy(oldElems,0,elems,0,size); + } + } + + /** + * Trims the capacity of this ArrayStack to be the + * stack's current size. An application can use this operation to minimize + * the storage of an ArrayStack instance. + */ + @SuppressWarnings("unchecked") + public void trimToSize() { + final int oldCapacity = elems.length; + if (size < oldCapacity) { + final Object oldElems[] = elems; + elems = (T[])new Object[size]; + System.arraycopy(oldElems, 0, elems, 0, size); + } + } + + /** + * @see kodkod.util.collections.Stack#size() + */ + public int size() { return size; } + + /** + * {@inheritDoc} + * @see kodkod.util.collections.Stack#push + */ + public T push(T item) { + ensureCapacity(size + 1); + elems[size++] = item; + return item; + } + + /** + * @see kodkod.util.collections.Stack#pop() + */ + public T pop() { + if (empty()) throw new EmptyStackException(); + final T top = elems[--size]; + elems[size] = null; + return top; + } + + /** + * @see kodkod.util.collections.Stack#peek() + */ + public T peek() { + if (empty()) throw new EmptyStackException(); + return elems[size-1]; + } + + /** + * @see kodkod.util.collections.Stack#search(java.lang.Object) + */ + public int search(Object o) { + for(int i = size-1; i >= 0; i--) { + if (equal(o, elems[i])) + return i; + } + return -1; + } + + /** + * @see kodkod.util.collections.Stack#empty() + */ + public boolean empty() { + return size==0; + } + + /** + * {@inheritDoc} + * @see kodkod.util.collections.Stack#iterator() + */ + public Iterator iterator() { + return new Iterator() { + int cursor = size - 1, lastReturned = -1; + + public boolean hasNext() { + return cursor >= 0; + } + + public T next() { + if (cursor < 0) + throw new NoSuchElementException(); + lastReturned = cursor; + return elems[cursor--]; + } + + public void remove() { + if (lastReturned < 0) + throw new UnsupportedOperationException(); + size--; + System.arraycopy(elems, lastReturned+1, elems, lastReturned, size - lastReturned); + elems[size] = null; + lastReturned = -1; + } + + }; + } + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/collections/CacheSet.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/collections/CacheSet.java new file mode 100644 index 00000000..8d18ff82 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/collections/CacheSet.java @@ -0,0 +1,439 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.util.collections; + +import java.util.AbstractSet; +import java.util.Collection; +import java.util.Iterator; +import java.util.NoSuchElementException; + +/** + * Implements the Set interface, backed by a hash table. + * It makes no guarantees as to the + * iteration order of the set; in particular, it does not guarantee that the + * order will remain constant over time. This class does not permit the null + * element.

+ * + * This class offers constant time performance for the basic operations + * (add, remove, contains and size), + * assuming the hash function disperses the elements properly among the + * buckets. Iterating over this set requires time proportional to the sum of + * the HashSet instance's size (the number of elements) plus the + * "capacity" of the backing map (the number of + * buckets). Thus, it's very important not to set the initial capacity too + * high (or the load factor too low) if iteration performance is important.

+ * + *

This set differs from Java's HashSet in that it provides methods for + * retrieving elements with a particular hashcode. This makes it + * easy to set as a cache in which cached objects' hashcodes are their keys.

+ * + *

Note that this implementation is not synchronized. + * The iterators returned by this class's iterator method are + * not fail-fast

+ * + * @specfield elts: set T + * @author Emina Torlak + */ +public final class CacheSet extends AbstractSet { + /* implementation adapted from java.util.HashMap and java.util.HashSet */ + /** + * The default initial capacity - MUST be a power of two. + */ + private static final int DEFAULT_INITIAL_CAPACITY = 16; + + /** + * The maximum capacity, used if a higher value is implicitly specified + * by either of the constructors with arguments. + * MUST be a power of two <= 1<<30. + */ + private static final int MAXIMUM_CAPACITY = 1 << 30; + + /** + * The load factor used when none specified in constructor. + **/ + private static final float DEFAULT_LOAD_FACTOR = 0.75f; + + /** + * The table, resized as necessary. Length MUST Always be a power of two. + */ + private Entry[] table; + + /** + * The number of key-value mappings contained in this identity hash map. + */ + private int size; + + /** + * The next size value at which to resize (capacity * load factor). + */ + private int threshold; + + /** + * The load factor for the hash table. + */ + final float loadFactor; + + /** + * Constructs a new, empty set; the backing map has + * default initial capacity (16) and load factor (0.75). + * @ensures no this.elts' + */ + @SuppressWarnings("unchecked") + public CacheSet() { + loadFactor = DEFAULT_LOAD_FACTOR; + threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR); + table = new Entry[DEFAULT_INITIAL_CAPACITY]; + } + + /** + * Constructs a new, empty set; the backing map has + * the specified initial capacity and the specified load factor. + * + * @param initialCapacity the initial capacity of the hash map. + * @param loadFactor the load factor of the hash map. + * @throws IllegalArgumentException if the initial capacity is less + * than zero, or if the load factor is nonpositive. + * @ensures no this.elts' + */ + @SuppressWarnings("unchecked") + public CacheSet(int initialCapacity, float loadFactor) { + if (initialCapacity < 0) + throw new IllegalArgumentException("Illegal initial capacity: " + + initialCapacity); + if (initialCapacity > MAXIMUM_CAPACITY) + initialCapacity = MAXIMUM_CAPACITY; + if (loadFactor <= 0 || Float.isNaN(loadFactor)) + throw new IllegalArgumentException("Illegal load factor: " + + loadFactor); + + // Find a power of 2 >= initialCapacity + int capacity = 1; + while (capacity < initialCapacity) + capacity <<= 1; + + this.loadFactor = loadFactor; + threshold = (int)(capacity * loadFactor); + table = new Entry[capacity]; + } + + /** + * Constructs a new set containing the elements in the specified + * collection. The HashMap is created with default load factor + * (0.75) and an initial capacity sufficient to contain the elements in + * the specified collection. + * + * @param c the collection whose elements are to be placed into this set. + * @throws NullPointerException if the specified collection is null. + */ + public CacheSet(Collection c) { + this(c.size(), .75f); + addAll(c); + } + + /** + * Returns a hash value for the specified integer. + * The shift distances in this function were chosen as the result + * of an automated search over the entire four-dimensional search space. + */ + private static int hash(int h) { + h += ~(h << 9); + h ^= (h >>> 14); + h += (h << 4); + h ^= (h >>> 10); + return h; + } + + /** + * Returns a hash value for the specified object. In addition to + * the object's own hashCode, this method applies a "supplemental + * hash function," which defends against poor quality hash functions. + * This is critical because HashMap uses power-of two length + * hash tables.

+ * + * The shift distances in this function were chosen as the result + * of an automated search over the entire four-dimensional search space. + */ + private static int hash(Object x) { + return hash(x.hashCode()); + } + + /** + * Returns index for hash code h. + */ + private static int indexFor(int h, int length) { + return h & (length-1); + } + + /** + * Returns the number of elements in this set. + * @return #this.elts + * @see java.util.Set#size() + */ + public int size() { + return size; + } + + /** + * Returns true if this set is empty. + * @return no this.elts + * @see java.util.Set#isEmpty() + */ + public boolean isEmpty() { + return size==0; + } + + /** + * Returns true if this set contains the given element. + * @return elt in this.elts + * @throws NullPointerException - elt = null + * @see java.util.Set#contains(java.lang.Object) + */ + public boolean contains(Object elt) { + Entry e = table[indexFor(hash(elt), table.length)]; + while (e != null) { + if (e.val.equals(elt)) + return true; + e = e.next; + } + return false; + } + + /** + * Returns an iterator over the elements in this set. + * @return an iterator over this.elts. + * @see java.util.Set#iterator() + */ + public Iterator iterator() { + return new SetIterator(); + } + + /** + * Adds the given element to this set, if not already present. + * @ensures this.elts' = this.elts + elt + * @throws NullPointerException - elt = null + * @return elt !in this.elts + */ + public boolean add(E elt) { + final int i = indexFor(hash(elt), table.length); + + for (Entry e = table[i]; e != null; e = e.next) { + if (e.val.equals(elt)) { + return false; + } + } + + table[i] = new Entry(elt, table[i]); + if (size++ >= threshold) + resize(2 * table.length); + + return true; + } + + /** + * Rehashes the contents of this map into a new array with a + * larger capacity. This method is called automatically when the + * number of keys in this map reaches its threshold. + * + * If current capacity is MAXIMUM_CAPACITY, this method does not + * resize the map, but sets threshold to Integer.MAX_VALUE. + * This has the effect of preventing future calls. + * + * @param newCapacity the new capacity, MUST be a power of two; + * must be greater than current capacity unless current + * capacity is MAXIMUM_CAPACITY (in which case value + * is irrelevant). + */ + @SuppressWarnings("unchecked") + private void resize(int newCapacity) { + Entry[] oldTable = table; + int oldCapacity = oldTable.length; + if (oldCapacity == MAXIMUM_CAPACITY) { + threshold = Integer.MAX_VALUE; + return; + } + + Entry[] newTable = new Entry[newCapacity]; + transfer(newTable); + table = newTable; + threshold = (int)(newCapacity * loadFactor); + } + + /** + * Transfer all entries from current table to newTable. + */ + private void transfer(Entry[] newTable) { + Entry[] src = table; + int newCapacity = newTable.length; + for (int j = 0; j < src.length; j++) { + Entry e = src[j]; + if (e != null) { + src[j] = null; + do { + Entry next = e.next; + int i = indexFor(hash(e.val), newCapacity); + e.next = newTable[i]; + newTable[i] = e; + e = next; + } while (e != null); + } + } + } + + /** + * Removes the specified object from this set, if present. + * @ensures this.elts' = this.elts - elt + * @return elt in this.elts + * @throws NullPointerException - elt = null + * @see java.util.Set#remove(java.lang.Object) + */ + public boolean remove(Object elt) { + int i = indexFor(hash(elt), table.length); + Entry prev = table[i]; + Entry e = prev; + + while (e != null) { + Entry next = e.next; + if (e.val.equals(elt)) { + size--; + if (prev == e) + table[i] = next; + else + prev.next = next; + return true; + } + prev = e; + e = next; + } + + return false; + + } + + /** + * Returns an iterator over the elements + * whose hashcode() method returns the given hash. + * @return an iterator over {e: this.elts | e.hashCode() = hash } + */ + public Iterator get(final int hash) { + final int i = indexFor(hash(hash), table.length); + return new Iterator() { + + Entry current = null, next = table[i]; + + public boolean hasNext() { + while(next != null && next.val.hashCode()!=hash) { + next = next.next; + } + return next != null; + } + + public E next() { + if (!hasNext()) throw new NoSuchElementException(); + current = next; + next = next.next; + return current.val; + } + + public void remove() { + if (current==null) + throw new IllegalStateException(); + Entry prev = table[i]; + Entry e = prev; + + while (e.next != current) { + prev = e; + e = e.next; + } + + size--; + if (prev==e) + table[i] = next; + else + prev.next = next; + current = null; + } + + }; + } + + /** + * Removes all elements from this set. + * @ensures no this.elts' + * @see java.util.Set#clear() + */ + public void clear() { + for (int i = 0; i < table.length; i++) + table[i] = null; + size = 0; + } + + private static final class Entry { + Entry next; + T val; + + Entry(T val, Entry next) { + this.val = val; + this.next = next; + } + } + + private final class SetIterator implements Iterator { + Entry next; // next entry to return + int index; // current slot + Entry current; // current entry + + SetIterator() { + index = table.length; + next = null; + if (size != 0) { // advance to first entry + while (index > 0 && (next = table[--index]) == null) + ; + } + } + + public boolean hasNext() { + return next != null; + } + + public E next() { + Entry e = next; + if (e == null) + throw new NoSuchElementException(); + + Entry n = e.next; + while (n == null && index > 0) + n = table[--index]; + next = n; + return (current = e).val; + + } + + public void remove() { + if (current == null) + throw new IllegalStateException(); + + CacheSet.this.remove(current.val); + current = null; + } + } + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/collections/Containers.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/collections/Containers.java new file mode 100644 index 00000000..66e34196 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/collections/Containers.java @@ -0,0 +1,395 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.util.collections; + +import java.util.AbstractSet; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.Set; + +/** + * Provides utility methods for working with arrays and collections. + * + * @author Emina Torlak + */ +public final class Containers { + private static Comparator identityComparator; + private static Comparator hashComparator; + + private Containers() {} + /** + * Returns a new iterator over the given array of items. + * The iterator is backed by the given array. The contents + * of the array are not modified by the iterator. The effect + * of this method is the same as calling Iterators.iterator(0, items.length, items). + * @throws NullPointerException - items = null + */ + public static final Iterator iterate(final E... items) { + return new AscendingArrayIterator(0, items.length, items); + } + + /** + * Returns a new iterator over the given array of items. + * The iterator is backed by the given array. The contents + * of the array are not modified by the iterator. + * The returned iterator enumerates the items located between + * indeces start, inclusive, and end, exclusive. If start < end, + * the elements are returned in the ascending order; otherwise, + * they are returned in the descending order. + * @throws NullPointerException - items = null + * @throws IllegalArgumentException - start < end && (start < 0 || end > items.length) || + * start > end && (start >= items.length || end < -1) + */ + @SuppressWarnings("unchecked") + public static final Iterator iterate(int start, int end, final E... items) { + if (start < end) + return new AscendingArrayIterator(start,end,items); + else if (start > end) + return new DescendingArrayIterator(start,end,items); + else + return emptyIterator(); + } + + /** + * Returns an iterator that has no elements. That is, + * calls to hasNext will return false, and all other + * calls will result in a runtime exception. + * @return an empty iterator + */ + @SuppressWarnings("unchecked") + public static final Iterator emptyIterator() { + return (Iterator) Collections.emptySet().iterator(); + } + + /** + * Calls System.arraycopy(src, srcPos, dest, destPos, length) and returns the destination array. + * @ensures System.arraycopy(src, srcPos, dest, destPos, length) + * @return dest + */ + public static final T[] copy(T[] src, int srcPos, T[] dest, int destPos, int length) { + System.arraycopy(src, srcPos, dest, destPos, length); + return dest; + } + + /** + * Calls System.arraycopy(src, 0, dest, 0, src.length) and returns the destination array. + * @requires dest.length >= src.length + * @ensures System.arraycopy(src, 0, dest, 0, src.length) + * @return dest + */ + public static final T[] copy(T[] src, T[] dest) { + System.arraycopy(src, 0, dest, 0, src.length); + return dest; + } + + /** + * Returns a comparator that compares objects according to their + * {@link System#identityHashCode(Object) identity hashcodes}. + * @return a comparator that compares objects according to their + * {@link System#identityHashCode(Object) identity hashcodes}. + */ + public static final Comparator identityComparator() { + if (identityComparator==null) { + identityComparator = new Comparator() { + public int compare(Object o1, Object o2) { + final int c1 = System.identityHashCode(o1); + final int c2 = System.identityHashCode(o2); + return c1 == c2 ? 0 : (c1 < c2 ? -1 : 1); + } + }; + } + return identityComparator; + } + + /** + * Returns 0 if o is null, otherwise returns o.hashCode(). + * @return o=null => 0, o.hashCode() + */ + static final int hash(Object o) { + return o==null ? 0 : o.hashCode(); + } + + /** + * Returns true if both o1 and o2 are null or if o1.equals(o2) + * @return true if both o1 and o2 are null or if o1.equals(o2) + */ + static final boolean eq(Object o1, Object o2) { + return o1==null ? o2==null : o1.equals(o2); + } + + /** + * Returns a comparator that compares objects according to their + * {@link Object#hashCode() hashcodes}. The null reference is + * considered to have a hashcode of 0. + * @return a comparator that compares objects according to their + * {@link Object#hashCode() hashcodes}. + */ + public static final Comparator hashComparator() { + if (hashComparator==null) { + hashComparator = new Comparator() { + public int compare(Object o1, Object o2) { + final int c1 = hash(o1); + final int c2 = hash(o2); + return c1 == c2 ? 0 : (c1 < c2 ? -1 : 1); + } + }; + } + return hashComparator; + } + + /** + * Calls {@link java.util.Arrays#sort(Object[], Comparator)} on the + * given array and returns it. The elements are sorted in the ascending + * order of their identity hashcodes. + * @ensures java.util.Arrays.sort(array, {@link #identityComparator()}) + * @return the given array, with its elements sorted in the increasing order of identity hashcodes + */ + public static final T[] identitySort(T[] array) { + java.util.Arrays.sort(array, identityComparator()); + return array; + } + + /** + * Calls {@link java.util.Arrays#sort(Object[], Comparator)} on the + * given array and returns it. The elements are sorted in the ascending + * order of their hashcodes. + * @ensures java.util.Arrays.sort(array, {@link #hashComparator()}) + * @return the given array, with its elements sorted in the increasing order of hashcodes + */ + public static final T[] hashSort(T[] array) { + java.util.Arrays.sort(array, hashComparator()); + return array; + } + + /** + * Searches the specified array for the specified object using the binary search algorithm + * and reference equality. + * The array must be sorted into ascending order according to the identity hashcodes of its elements + * (as by {@link #identitySort(Object[])}) prior to making this call. If it is not sorted, + * the results are undefined. If the array contains multiple occurences of the specified object, + * there is no guarantee which one will be found. + * @requires all i, j: [0..array.length) | i < j => array[i].hashCode() <= array[j].hashCode()) + * @return index of the search key, if it is contained in the array; otherwise, (-(insertion point) - 1). + * The insertion point is defined as the point at which the key would be inserted into the array: the + * index of the first element greater than the key, or array.length, if all elements in the array are less + * than the specified key. Note that this guarantees that the return value will be >= 0 if and only if the + * key is found. + */ + public static final int identityBinarySearch(Object[] array, Object key) { + int low = 0; + int high = array.length-1; + int index = System.identityHashCode(key); + + while (low <= high) { + int mid = (low + high) >>> 1; + int midIndex = System.identityHashCode(array[mid]); + if (midIndex < index) + low = mid + 1; + else if (midIndex > index) + high = mid - 1; + else { // index found, now check that variables are the same + if (array[mid]==key) return mid; + // check all variables with the same index (if any) + for(int i = mid+1; i < array.length && System.identityHashCode(array[i])==index; i++) { + if (array[i]==key) return i; + } + for(int i = mid-1; i >= 0 && System.identityHashCode(array[i])==index; i--) { + if (array[i]==key) return i; + } + return -(mid+1); // var not found + } + } + + return -(low + 1); // key not found. + } + + /** + * Searches the specified array for the specified object using the binary search algorithm + * and object equality. + * The array must be sorted into ascending order according to the hashcodes of its elements + * (as by {@link #hashSort(Object[])}) prior to making this call. If it is not sorted, + * the results are undefined. If the array contains multiple occurences of the specified object, + * there is no guarantee which one will be found. + * @requires all i, j: [0..array.length) | i < j => System.identityHashCode(array[i]) <= System.identityHashCode(array[j]) + * @return index of the search key, if it is contained in the array; otherwise, (-(insertion point) - 1). + * The insertion point is defined as the point at which the key would be inserted into the array: the + * index of the first element greater than the key, or array.length, if all elements in the array are less + * than the specified key. Note that this guarantees that the return value will be >= 0 if and only if the + * key is found. + */ + public static final int hashBinarySearch(Object[] array, Object key) { + int low = 0; + int high = array.length-1; + int index = hash(key); + + while (low <= high) { + int mid = (low + high) >>> 1; + int midIndex = hash(array[mid]); + if (midIndex < index) + low = mid + 1; + else if (midIndex > index) + high = mid - 1; + else { // index found, now check that variables are the same + if (eq(array[mid], key)) return mid; + // check all variables with the same index (if any) + for(int i = mid+1; i < array.length && hash(array[i])==index; i++) { + if (eq(array[i], key)) return i; + } + for(int i = mid-1; i >= 0 && hash(array[i])==index; i--) { + if (eq(array[i], key)) return i; + } + return -(mid+1); // var not found + } + } + + return -(low + 1); // key not found. + } + + /** + * Returns an identity set backed by the given array (i.e. a set that uses + * reference equality for comparisons). + * The array must contain no duplicates, its elements must be + * sorted in the increasing order of identity hashcodes (as by {@link #identitySort(Object[])}), and its contents + * must not be changed while it is in use by the returned set. + * @requires all i, j: [0..array.length) | i < j => System.identityHashCode(array[i]) <= System.identityHashCode(array[j]) + * @return an unmodifiable identity Set view of the given array + */ + public static final Set asIdentitySet(final T[] array) { + return new AbstractSet() { + public boolean contains(Object o) { + return identityBinarySearch(array, o) >= 0; + } + public Iterator iterator() { return iterate(array); } + public int size() { return array.length; } + public int hashCode() { + int result = 0; + for (Object o : array) { result += System.identityHashCode(o); } + return result; + } + }; + } + + /** + * Returns a set backed by the given array (i.e. a set that uses + * object equality for comparisons). + * The array must contain no duplicates, its elements must be + * sorted in the increasing order of hashcodes (as by {@link #hashSort(Object[])}), and its contents + * must not be changed while it is in use by the returned set. + * @requires all i, j: [0..array.length) | i < j => array[i].hashCode() <= array[j].hashCode + * @return an unmodifiable Set view of the given array + */ + public static final Set asHashSet(final T[] array) { + return new AbstractSet() { + public boolean contains(Object o) { + return hashBinarySearch(array, o) >= 0; + } + public Iterator iterator() { return iterate(array);} + public int size() { return array.length;} + }; + } + + /** + * An unmodifying iterator over an array. + */ + private static abstract class ArrayIterator implements Iterator { + final T[] items; + final int end; + int cursor; + + /** + * Constructs a new iterator over the given array of items. + * The iterator is backed by the given array. The contents + * of the array are not modified by the iterator. The + * constructed iterator returns the items located between the + * indeces start, inclusive, and end, exclusive. + * @requires items != null && + * start < end => end in [0..items.length] && start in [0..end], + * start in [0..items.length) && end in [-1..start] + */ + ArrayIterator(int start, int end, final E... items) { + this.items = items; + this.cursor = start; + this.end = end; + } + + public final void remove() { + throw new UnsupportedOperationException(); + } + } + + /** + * An ascending iterator over an array. + */ + private static final class AscendingArrayIterator extends ArrayIterator { + + /** + * Constructs a new iterator over the given array of items. + * @requires items != null && start < end + * @throws IllegalArgumentException - start < 0 || end > items.length + */ + AscendingArrayIterator(int start, int end, E[] items) { + super(start, end, items); + if (start < 0 || end > items.length) { + throw new IllegalArgumentException("start < end && (start < 0 || end > items.length)"); + } + } + + public boolean hasNext() { + return cursor >= 0 && cursor < end; + } + + public T next() { + if (!hasNext()) throw new NoSuchElementException(); + return items[cursor++]; + } + } + + /** + * A descending iterator over an array. + */ + private static final class DescendingArrayIterator extends ArrayIterator { + /** + * Constructs a new iterator over the given array of items. + * @requires items != null && start > end + * @throws IllegalArgumentException - start >= items.length || end < -1 + */ + DescendingArrayIterator(int start, int end, E[] items) { + super(start, end, items); + if (start >= items.length || end < -1) { + throw new IllegalArgumentException("start > end && (start >= items.length || end < -1)"); + } + } + + public boolean hasNext() { + return cursor > end; + } + + public T next() { + if (!hasNext()) throw new NoSuchElementException(); + return items[cursor--]; + } + + } + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/collections/FixedMap.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/collections/FixedMap.java new file mode 100644 index 00000000..e6e55bf5 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/collections/FixedMap.java @@ -0,0 +1,410 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.util.collections; + +import java.util.AbstractMap; +import java.util.AbstractSet; +import java.util.Iterator; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; + + +/** + * A map with a fixed set of keys that acts as an indexer, assigning a + * unique integer in the range [0..#keys) to each key. This class implements the + * Map interface with an array, using + * reference-equality in place of object-equality when comparing keys (and + * values). In other words, in a FixedMap, two keys + * k1 and k2 are considered equal if and only if + * (k1==k2). (In normal Map implementations (like + * HashMap) two keys k1 and k2 are considered equal + * if and only if (k1==null ? k2==null : k1.equals(k2)).)

+ * + *

This class is not a general-purpose Map + * implementation! While this class implements the Map interface, it + * intentionally violates Map's general contract, which mandates the + * use of the equals method when comparing objects.

+ * + *

This class provides {@link #put(Object, Object)} operation for the keys + * within its fixed key set and permits + * null values and the null key. The {@link #remove(Object)} + * operation is not supported. This class guarantees + * that the order of the map will remain constant over time.

+ * + *

This class provides log-time performance for the basic + * operations (get and put), assuming the system + * identity hash function ({@link System#identityHashCode(Object)}) + * disperses elements properly among the buckets.

+ * + *

This implementation is not synchronized, and its iterators are not fail-fast.

+ * + * @specfield keys: set K + * @specfield map: keys -> one V + * @specfield indices: keys lone->one [0..#keys) + * @author Emina Torlak + */ +public final class FixedMap extends AbstractMap implements Indexer { + + private final Object[] keys; + private final Object[] values; + + /** + * Constructs a new fixed map from the given map. + * @ensures this.keys' = keys && this.map = map.map + */ + public FixedMap(Map map) { + this(map.keySet()); + for(int i = 0, size = map.size(); i < size; i++) { + values[i] = map.get(keys[i]); + } + } + + /** + * Constructs a new fixed map for the given set of keys. + * @ensures this.keys' = keys && no this.map' + */ + public FixedMap(Set keys) { + final int size = keys.size(); + this.keys = Containers.identitySort(keys.toArray(new Object[size])); + values = new Object[size]; + } + + /** + * Constructs a new fixed map, backed by the given array of keys. The provided + * array must not contain duplicates, its entries must be sorted + * in the increasing order of identity hashcodes, and it must not + * be modified while in use by this map. The map will not behave correctly if + * these requirements are violated. + * @requires no disj i, j: [0..keys.length) | keys[i] == keys[j] + * @requires all i, j: [0..keys.length) | i < j => System.identityHashCode(keys[i]) <= System.identityHashCode(keys[j]) + * @ensures this.keys' = keys && no this.map' + */ + public FixedMap(K[] keys) { + this.keys = keys; + this.values = new Object[keys.length]; + } + + /** + * Returns the index of the given key, if it is in this.keys. + * Otherwise returns a negative number. + * @return key in this.keys => this.indices[key], {i: int | i < 0 } + */ + public final int indexOf(K key) { + return Containers.identityBinarySearch(keys, key); + } + + /** + * Returns the key at the given index. + * @return this.indices.index + * @throws IndexOutOfBoundsException - index !in this.indices[this.keys] + */ + @SuppressWarnings("unchecked") + public final K keyAt(int index) { + try { + return (K)keys[index]; + } catch (ArrayIndexOutOfBoundsException e) { + throw new IndexOutOfBoundsException(); + } + } + + /** + * Tests whether the specified object reference is a key in this fixed map. + * + * @return key in this.keys + */ + @SuppressWarnings("unchecked") + public final boolean containsKey(Object key) { + return indexOf((K)key) >= 0; + } + + /** + * Tests whether the specified object reference is a value in this fixed map. + * + * @return value in this.map[this.keys] + */ + public final boolean containsValue(Object value) { + for(Object o : values) { + if (o==value) return true; + } + return false; + } + + /** + * Returns a set view of the mappings contained in this map. Each element + * in the returned set is a reference-equality-based Map.Entry. + * The set is backed by the map, so changes to the map are reflected in + * the set, and vice-versa. If the map is modified while an iteration + * over the set is in progress, the results of the iteration are + * undefined. The set does support neither removal nor addition. + * + *

Like the backing map, the Map.Entry objects in the set + * returned by this method define key and value equality as + * reference-equality rather than object-equality. This affects the + * behavior of the equals and hashCode methods of these + * Map.Entry objects. A reference-equality based Map.Entry + * e is equal to an object o if and only if o is a + * Map.Entry and e.getKey()==o.getKey() && + * e.getValue()==o.getValue(). To accommodate these equals + * semantics, the hashCode method returns + * System.identityHashCode(e.getKey()) ^ + * System.identityHashCode(e.getValue()). + * + *

Owing to the reference-equality-based semantics of the + * Map.Entry instances in the set returned by this method, + * it is possible that the symmetry and transitivity requirements of + * the {@link Object#equals(Object)} contract may be violated if any of + * the entries in the set is compared to a normal map entry, or if + * the set returned by this method is compared to a set of normal map + * entries (such as would be returned by a call to this method on a normal + * map). However, the Object.equals contract is guaranteed to + * hold among identity-based map entries, and among sets of such entries. + * + * + * @return a set view of the identity-mappings contained in this map. + */ + public final Set> entrySet() { + return new AbstractSet>() { + + @SuppressWarnings("unchecked") + public boolean contains(Object o) { + final Map.Entry e = (Map.Entry) o; + final int index = FixedMap.this.indexOf(e.getKey()); + return index < 0 ? false : values[index]==e.getValue(); + } + + public Iterator> iterator() { return new EntryIterator(); } + + public int size() { return keys.length; } + + public Object[] toArray() { + int size = size(); + Object[] result = new Object[size]; + for (int i = 0; i < size; i++) + result[i] = new Entry(i); + return result; + } + + @SuppressWarnings("unchecked") + public T[] toArray(T[] a) { + int size = size(); + if (a.length < size) + a = (T[])java.lang.reflect.Array.newInstance(a.getClass().getComponentType(), size); + for (int i = 0; i < size; i++) + a[i] = (T) new Entry(i); + if (a.length > size) + a[size] = null; + return a; + } + }; + } + + /** + * Returns the value to which the specified key is mapped in this fixed map, + * or null if the map contains no mapping for + * this key. A return value of null does not necessarily + * indicate that the map contains no mapping for the key; it is also + * possible that the map explicitly maps the key to null. The + * containsKey method may be used to distinguish these two + * cases. + * + * @return this.map[key] + */ + @SuppressWarnings("unchecked") + public final V get(Object key) { + final int index = indexOf((K)key); + return index < 0 ? null : (V)values[index]; + } + + /** + * Returns the value to which the key at the specified index is mapped in this fixed map. + * @requires index in this.indices[this.keys] + * @return this.map[this.indices.index] + * @throws IndexOutOfBoundsException - index !in this.indices[this.keys] + */ + @SuppressWarnings("unchecked") + public final V get(int index) { + try { + return (V) values[index]; + } catch (ArrayIndexOutOfBoundsException e) { + throw new IndexOutOfBoundsException(); + } + } + + /** + * @see java.util.Map#isEmpty() + */ + public final boolean isEmpty() { + return keys.length==0; + } + + /** + * Associates the specified value with the specified key in this fixed + * map. If the map previously contained a mapping for this key, the + * old value is replaced. This method assumes that the given key is + * in the fixed keyset of this map. + * + * @requires key in this.keys + * @return this.map[key] + * @ensures this.map' = this.map ++ key->value + * @throws IllegalArgumentException - key !in this.keys + */ + + @SuppressWarnings("unchecked") + public final V put(K key, V value) { + final int index = indexOf((K)key); + if (index < 0) throw new IllegalArgumentException(); + final V oldValue = (V) values[index]; + values[index] = value; + return oldValue; + } + + /** + * Throws an {@link UnsupportedOperationException} exception. + * @see java.util.Map#remove(java.lang.Object) + */ + public final V remove(Object key) { + throw new UnsupportedOperationException(); + } + + /** + * @see java.util.Map#size() + */ + public final int size() { + return keys.length; + } + + /** + * Returns the hash code value for this map. The hash code of a map + * is defined to be the sum of the hashcode of each entry in the map's + * entrySet view. This ensures that t1.equals(t2) implies + * that t1.hashCode()==t2.hashCode() for any two + * FixedMap instances t1 and t2, as + * required by the general contract of {@link Object#hashCode()}. + * + *

Owing to the reference-equality-based semantics of the + * Map.Entry instances in the set returned by this map's + * entrySet method, it is possible that the contractual + * requirement of Object.hashCode mentioned in the previous + * paragraph will be violated if one of the two objects being compared is + * an FixedMap instance and the other is a normal map. + * + * @return the hash code value for this map. + * @see Object#hashCode() + * @see Object#equals(Object) + * @see #equals(Object) + */ + public int hashCode() { + int result = 0; + for(int i = 0 ; i < keys.length; i++) + result += System.identityHashCode(keys[i]) ^ System.identityHashCode(values[i]); + return result; + } + + /** + * Compares the specified object with this map for equality. Returns + * true if the given object is also a map and the two maps + * represent identical object-reference mappings. More formally, this + * map is equal to another map m if and only if + * map this.entrySet().equals(m.entrySet()). + * + *

Owing to the reference-equality-based semantics of this map it is + * possible that the symmetry and transitivity requirements of the + * Object.equals contract may be violated if this map is compared + * to a normal map. However, the Object.equals contract is + * guaranteed to hold among FixedMap instances. + * + * @param o object to be compared for equality with this map. + * @return true if the specified object is equal to this map. + * @see Object#equals(Object) + */ + public boolean equals(Object o) { + if (o == this) { + return true; + } else if (o instanceof FixedMap) { + FixedMap m = (FixedMap) o; + if (m.size() != size()) + return false; + for(int i = 0; i < keys.length; i++) { + if (keys[i] != m.keys[i] || values[i] != m.values[i]) + return false; + } + return true; + } else if (o instanceof Map) { + Map m = (Map)o; + return entrySet().equals(m.entrySet()); + } else { + return false; // o is not a Map + } + } + + private class Entry implements Map.Entry { + int index; + Entry(int index) { + this.index = index; + } + @SuppressWarnings("unchecked") + public final K getKey() { return (K)keys[index]; } + + @SuppressWarnings("unchecked") + public final V getValue() { return (V)values[index]; } + + @SuppressWarnings("unchecked") + public V setValue(V value) { throw new UnsupportedOperationException(); } + + public int hashCode() { + return System.identityHashCode(keys[index]) ^ System.identityHashCode(values[index]); + } + @SuppressWarnings("unchecked") + public boolean equals(Object o) { + if (o instanceof Map.Entry) { + final Map.Entry e = (Map.Entry) o; + return keys[index] == e.getKey() && values[index] == e.getValue(); + } else return false; + } + public String toString() { + return keys[index] + "=" + values[index]; + } + } + + private final class EntryIterator extends Entry implements Iterator> { + int next = 0; + EntryIterator() { super(-1); } + @SuppressWarnings("unchecked") + public V setValue(V value) { + final V oldValue = (V) values[index]; + values[index] = value; + return oldValue; + } + public boolean hasNext() { return next < keys.length; } + + public Map.Entry next() { + if (!hasNext()) throw new NoSuchElementException(); + index = next++; + return this; + } + public int hashCode() { return index < 0 ? System.identityHashCode(this) : super.hashCode(); } + public boolean equals(Object o) { return index < 0 ? this==o : super.equals(o); } + public void remove() { throw new UnsupportedOperationException(); } + public String toString() { return index < 0 ? "[]" : super.toString(); } + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/collections/IdentityHashSet.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/collections/IdentityHashSet.java new file mode 100644 index 00000000..5c91743a --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/collections/IdentityHashSet.java @@ -0,0 +1,593 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.util.collections; + +import java.util.AbstractSet; +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.NoSuchElementException; + + +/** + *

Implements the Set interface with a hash table, using + * reference-equality in place of object-equality when comparing elements. + * In other words, in an IdentityHashSet, two elements + * e1 and e2 are considered equal if and only if + * (e1==e2). (In normal Set implementations (like + * Set) two elements e1 and e2 are considered equal + * if and only if (e1==null ? e2==null : e1.equals(e2)).) + * + *

This class is not a general-purpose Set + * implementation! While this class implements the Set interface, it + * intentionally violates Set's general contract, which mandates the + * use of the equals method when comparing objects. This class is + * designed for use only in the rare cases wherein reference-equality + * semantics are required. + * + *

This class provides all of the optional set operations, and permits + * null elements. This class makes no + * guarantees as to the order of the set; in particular, it does not guarantee + * that the order will remain constant over time. + * + *

This class provides constant-time performance for the basic + * operations (get and put), assuming the system + * identity hash function ({@link System#identityHashCode(Object)}) + * disperses elements properly among the buckets. + * + *

This class has one tuning parameter (which affects performance but not + * semantics): expected maximum size. This parameter is the maximum + * number of elements that the set is expected to hold. Internally, + * this parameter is used to determine the number of buckets initially + * comprising the hash table. The precise relationship between the expected + * maximum size and the number of buckets is unspecified. + * + *

If the size of the set sufficiently + * exceeds the expected maximum size, the number of buckets is increased + * Increasing the number of buckets ("rehashing") may be fairly expensive, so + * it pays to create identity hash sets with a sufficiently large expected + * maximum size. On the other hand, iteration requires + * time proportional to the number of buckets in the hash table, so it + * pays not to set the expected maximum size too high if you are especially + * concerned with iteration performance or memory usage. + * + *

Note that this implementation is not synchronized. The iterators + * returned by all of this class + * are not fail-fast: in the face of concurrent + * modification, the iterator risks + * arbitrary, non-deterministic behavior at an undetermined time in the + * future. + * + *

Implementation note: This is a simple linear-probe hash table, + * as described for example in texts by Sedgewick and Knuth. For many JRE + * implementations and operation mixes, this class will yield better performance than + * {@link HashSet} (which uses chaining rather than linear-probing). + * + * @specfield elems: set T + * @author Emina Torlak + */ +public final class IdentityHashSet extends AbstractSet { + /* implementation adapted from java.util.IdentityHashMap */ + /** + * The minimum capacity, used if a lower value is implicitly specified + * by either of the constructors with arguments. The value 4 corresponds + * to an expected maximum size of 2, given a load factor of 2/3. + * MUST be a power of two. + */ + private static final int MINIMUM_CAPACITY = 4; + + /** + * The maximum capacity, used if a higher value is implicitly specified + * by either of the constructors with arguments. + * MUST be a power of two <= 1<<29. + */ + private static final int MAXIMUM_CAPACITY = 1 << 29; + + /** + * Value representing null elements inside tables. + */ + private static final Object NULL = new Object(); + + /** + * Use NULL for key if it is null. + */ + private static Object maskNull(Object o) { + return (o == null ? NULL : o); + } + + /** + * Return internal representation of null key back to caller as null + */ + private static Object unmaskNull(Object o) { + return (o == NULL ? null : o); + } + + /** + * The table, resized as necessary. Length MUST always be a power of two. + */ + private Object[] table; + + /** + * The number of key-value mappings contained in this identity hash map. + */ + private int size; + + /** + * The next size value at which to resize (capacity * load factor). + */ + private int threshold; + + /** + * Constructs a new, empty identity hash map with a default expected + * maximum size of 16. + * @ensures no this.elems' + */ + public IdentityHashSet() { + this(16); + } + + /** + * Constructs a new, empty set with the specified expected maximum size. + * Putting more than the expected number of elements into + * the set may cause the internal data structure to grow, which may be + * somewhat time-consuming. + * @ensures no this.elems' + * @throws IllegalArgumentException - expectedMaxSize < 0 + */ + public IdentityHashSet(int expectedMaxSize) { + if (expectedMaxSize < 0) + throw new IllegalArgumentException("expectedMaxSize < 0: " + expectedMaxSize); + + final int initCapacity = capacity(expectedMaxSize); + threshold = (initCapacity * 2)/3; + table = new Object[initCapacity]; + size = 0; + } + + /** + * Constructs a new identity hash set containing the elements + * in the specified collection. + * @ensures this.elems' = c.elems + * @throws NullPointerException - c = null + */ + public IdentityHashSet(Collection c) { + // Allow for a bit of growth + this((int) ((1 + c.size()) * 1.1)); + addAll(c); + } + + /** + * Returns the appropriate capacity for the specified expected maximum + * size. Returns the smallest power of two between MINIMUM_CAPACITY + * and MAXIMUM_CAPACITY, inclusive, that is greater than + * (3 * expectedMaxSize)/2, if such a number exists. Otherwise + * returns MAXIMUM_CAPACITY. If (3 * expectedMaxSize)/2 is negative, it + * is assumed that overflow has occurred, and MAXIMUM_CAPACITY is returned. + */ + private static int capacity(int expectedMaxSize) { + // Compute min capacity for expectedMaxSize given a load factor of 2/3 + final int minCapacity = (3 * expectedMaxSize)/2; + + // Compute the appropriate capacity + int result; + if (minCapacity > MAXIMUM_CAPACITY || minCapacity < 0) { + result = MAXIMUM_CAPACITY; + } else { + result = StrictMath.max(MINIMUM_CAPACITY, Integer.highestOneBit(minCapacity)); + if (result < minCapacity) result <<= 1; + } + return result; + } + + /** + * @inheritDoc + */ + @Override + public Iterator iterator() { + return new IdentityIterator(); + } + + /** + * @inheritDoc + */ + @Override + public int size() { + return size; + } + + /** + * @inheritDoc + */ + @Override + public boolean isEmpty() { + return size==0; + } + + /** + * Return index for Object x in a table of size length. + */ + private static int hash(Object x, int length) { + return System.identityHashCode(x) & (length - 1); + } + + /** + * Circularly traverse table of size length. + **/ + private static int nextKeyIndex(int i, int length) { + return (i + 3) & (length - 1); + } + + /** + * Tests whether the specified object reference is an element in this identity + * hash map. + * @return o in this.elems + */ + @Override + public boolean contains(Object o) { + o = maskNull(o); + + for (int i = hash(o, table.length); ; i = nextKeyIndex(i, table.length)) { + Object item = table[i]; + if (item == o) + return true; + if (item == null) + return false; + } + } + + /** + * @inheritDoc + */ + @Override + public boolean add(T element) { + final Object o = maskNull(element); + + int i = hash(o, table.length); + + for(Object item = table[i]; item != null; item = table[i]) { + if (item == o) return false; + i = nextKeyIndex(i, table.length); + } + + table[i] = o; + + if (++size >= threshold) + resize(table.length<<1); // newCapacity == 2 * current capacity. + + return true; + } + + /** + * Resize the table to hold given capacity. The new capacity must be a + * power of two. + */ + private void resize(int newCapacity) { + final int oldLength = table.length; + + if (oldLength == MAXIMUM_CAPACITY) {// can't expand any further + if (threshold == MAXIMUM_CAPACITY - 1) + throw new IllegalStateException("Capacity exhausted."); + threshold = MAXIMUM_CAPACITY - 1; + return; + } + + if (oldLength >= newCapacity) + return; + + final Object[] newTable = new Object[newCapacity]; + + for (int j = 0; j < oldLength; j++) { + Object o = table[j]; + if (o != null) { + table[j] = null; + int i = hash(o, newCapacity); + while (newTable[i] != null) + i = nextKeyIndex(i, newCapacity); + newTable[i] = o; + } + } + + table = newTable; + threshold = (newCapacity * 2) / 3; + } + + /** + * @inheritDoc + */ + @Override + public boolean remove(Object o) { + o = maskNull(o); + + for (int i = hash(o, table.length); ; i = nextKeyIndex(i, table.length)) { + Object item = table[i]; + if (item == o) { + size--; + table[i] = null; + closeDeletion(i); + return true; + } + if (item == null) + return false; + } + + } + + /** + * Rehash all possibly-colliding entries following a + * deletion. This preserves the linear-probe + * collision properties required by get, put, etc. + * + * @param d the index of a newly empty deleted slot + */ + private void closeDeletion(int d) { + // Adapted from Knuth Section 6.4 Algorithm R + + // Look for items to swap into newly vacated slot + // starting at index immediately following deletion, + // and continuing until a null slot is seen, indicating + // the end of a run of possibly-colliding keys. + Object item; + for (int i = nextKeyIndex(d, table.length); (item = table[i]) != null; + i = nextKeyIndex(i, table.length) ) { + // The following test triggers if the item at slot i (which + // hashes to be at slot r) should take the spot vacated by d. + // If so, we swap it in, and then continue with d now at the + // newly vacated i. This process will terminate when we hit + // the null slot at the end of this run. + // The test is messy because we are using a circular table. + int r = hash(item, table.length); + if ((i < r && (r <= d || d <= i)) || (r <= d && d <= i)) { + table[d] = item; + table[i] = null; + d = i; + } + } + } + + /** + * @inheritDoc + */ + @Override + public boolean addAll(Collection c) { + int n = c.size(); + if (n == 0) + return false; + if (n > threshold) // conservatively pre-expand + resize(capacity(n)); + + return super.addAll(c); + } + + /** + * @inheritDoc + */ + @Override + public boolean removeAll(Collection c) { + /* Must revert from AbstractSet's impl to AbstractCollection's, as + * the former contains an optimization that results in incorrect + * behavior when c is a smaller "normal" (non-identity-based) Set. + */ + boolean modified = false; + Iterator e = iterator(); + while (e.hasNext()) { + if (c.contains(e.next())) { + e.remove(); + modified = true; + } + } + return modified; + } + + /** + * @inheritDoc + */ + @Override + public void clear() { + for (int i = 0; i < table.length; i++) + table[i] = null; + size = 0; + } + + /** + * Compares the specified object with this set for equality. Returns + * true if the given object is also a set and the two sets + * contain identical object-references. + * + *

Owing to the reference-equality-based semantics of this set it is + * possible that the symmetry and transitivity requirements of the + * Object.equals contract may be violated if this set is compared + * to a normal set. However, the Object.equals contract is + * guaranteed to hold among IdentityHashSet instances. + * + * @return true if the specified object is equal to this set. + * @see Object#equals(Object) + */ + public boolean equals(Object o) { + if (o == this) { + return true; + } else if (o instanceof IdentityHashSet) { + final IdentityHashSet s = (IdentityHashSet) o; + if (s.size() != size) return false; + final Object[] tab = s.table; + for (int i = 0; i < tab.length; i++) { + Object k = tab[i]; + if (k != null && !contains(k)) + return false; + } + return true; + } else { + return super.equals(o); + } + } + + /** + * Returns the hash code value for this set. The hash code of a set + * is defined to be the sum of the hashcode of each entry in the set. + * This ensures that t1.equals(t2) implies + * that t1.hashCode()==t2.hashCode() for any two + * IdentityHashSet instances t1 and t2, as + * required by the general contract of {@link Object#hashCode()}. + * + *

Owing to the reference-equality-based semantics of the + * elements in this set, it is possible that the contractual + * requirement of Object.hashCode mentioned in the previous + * paragraph will be violated if one of the two objects being compared is + * an IdentityHashSet instance and the other is a normal set. + * + * @return the hash code value for this set. + * @see Object#hashCode() + * @see Object#equals(Object) + * @see #equals(Object) + */ + public int hashCode() { + int result = 0; + for (Object o : table) { + if (o != null) { + result += System.identityHashCode(unmaskNull(o)); + } + } + return result; + } + + + /** + * An iterator over the elements of an IdentityHashSet. + */ + private final class IdentityIterator implements Iterator { + int index = (size != 0 ? 0 : table.length); // current slot. + int lastReturnedIndex = -1; // to allow remove() + Object[] traversalTable = table; // reference to main table or copy + + public boolean hasNext() { + for (int i = index; i < traversalTable.length; i++) { + if (traversalTable[i] != null) { + index = i; + return true; + } + } + index = traversalTable.length; + return false; + } + + @SuppressWarnings("unchecked") + public T next() { + if (!hasNext()) + throw new NoSuchElementException(); + + lastReturnedIndex = index++; + return (T) unmaskNull(traversalTable[lastReturnedIndex]); + } + + public void remove() { + if (lastReturnedIndex == -1) + throw new IllegalStateException(); + + final int deletedSlot = lastReturnedIndex; + lastReturnedIndex = -1; + + // If traversing a copy, remove in real table. + // We can skip gap-closure on copy. + if (traversalTable != IdentityHashSet.this.table) { + IdentityHashSet.this.remove(traversalTable[deletedSlot]); + traversalTable[deletedSlot] = null; + } else { // we are working on the real table... + // back up index to revisit new contents after deletion + size--; + index = deletedSlot; + + // Removal code proceeds as in closeDeletion except that + // it must catch the rare case where an element already + // seen is swapped into a vacant slot that will be later + // traversed by this iterator. We cannot allow future + // next() calls to return it again. The likelihood of + // this occurring under 2/3 load factor is very slim, but + // when it does happen, we must make a copy of the rest of + // the table to use for the rest of the traversal. Since + // this can only happen when we are near the end of the table, + // even in these rare cases, this is not very expensive in + // time or space. + final Object[] tab = traversalTable; + final int length = tab.length; + + int d = deletedSlot; + tab[d] = null; // vacate the slot + + Object item; + for (int i = nextKeyIndex(d, length); (item = tab[i]) != null; i = nextKeyIndex(i, length)) { + int r = hash(item, length); + // See closeDeletion for explanation of this conditional + if ((i < r && (r <= d || d <= i)) || + (r <= d && d <= i)) { + + // If we are about to swap an already-seen element + // into a slot that may later be returned by next(), + // then clone the rest of table for use in future + // next() calls. It is OK that our copy will have + // a gap in the "wrong" place, since it will never + // be used for searching anyway. + if (i < deletedSlot && d >= deletedSlot && traversalTable == IdentityHashSet.this.table) { + int remaining = length - deletedSlot; + Object[] newTable = new Object[remaining]; + System.arraycopy(tab, deletedSlot, newTable, 0, remaining); + traversalTable = newTable; + index = 0; + } + tab[d] = item; + tab[i] = null; + d = i; + } + } + } + } + + } + +// public static void main(String[] args) { +// IdentityHashSet s = new IdentityHashSet(21); +// Integer[] elts = new Integer[21]; +// for(int i = 0; i < elts.length; i++) { +// elts[i] = new Integer(i); +// s.add(elts[i]); +// } +// System.out.println(s); +// System.out.println(s.size()); +// System.out.println(s.table.length); +// System.out.println(s.threshold); +// System.out.println(s.contains(2)); +// System.out.println(s.contains(elts[2])); +// System.out.println(s.remove(new Integer(0))); +// System.out.println(s.remove(elts[0])); +// System.out.println(s); +// +// for(Iterator iter = s.iterator(); iter.hasNext(); ) { +// System.out.println(iter.next()); +// iter.remove(); +// } +// System.out.println(s); +// +// } + + +} + + + diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/collections/Indexer.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/collections/Indexer.java new file mode 100644 index 00000000..9bb94c5a --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/collections/Indexer.java @@ -0,0 +1,56 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.util.collections; + + +/** + * An index generator for a set of keys. An indexer maps each key in its keyset + * to a unique integer in the range [0..#keys) + * @specfield keys: set K + * @specfield indices: keys lone->one [0..#keys) + * @author Emina Torlak + */ +public interface Indexer { + + + /** + * Returns the index of the given key, if it is in this.keys. + * Otherwise returns a negative number. + * @return key in this.keys => this.indices[key], {i: int | i < 0 } + */ + public abstract int indexOf(K key); + + /** + * Returns the key at the given index. + * @return this.indices.index + * @throws IndexOutOfBoundsException - index !in this.indices[this.keys] + */ + public abstract K keyAt(int index); + + /** + * Returns the number of keys in this.indexer. + * @return #this.keys + */ + public abstract int size(); + + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/collections/LinkedStack.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/collections/LinkedStack.java new file mode 100644 index 00000000..99ffb44e --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/collections/LinkedStack.java @@ -0,0 +1,167 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.util.collections; + +import java.util.EmptyStackException; +import java.util.Iterator; +import java.util.NoSuchElementException; + +/** + * A Stack implementation based on a singly linked list. + * @author Emina Torlak + */ +public final class LinkedStack extends Stack { + private StackEntry head; + private int size; + + /** + * Constructs an empty stack. + * @ensures no this.elems' + */ + public LinkedStack() { + head = null; + size = 0; + } + + /** + * @see kodkod.util.collections.Stack#size() + */ + public int size() { return size; } + + /** + * Pushes an item onto the top of this stack and returns it. + * @ensures this.size' = this.size + 1 && this.elems'[0] = item && + * all i: [0..this.size) | this.elems'[i+1] = this.elems[i] + * @return item + */ + public T push(T item) { + head = new StackEntry(item, head); + size++; + return item; + } + + /** + * Removes the object at the top of this stack and returns that object as the value of this function. + * @ensures this.size' = this.size - 1 && + * all i: [1..this.size) | this.elems'[i-1] = this.elems[i] + * @return this.elems[0] + * @throws EmptyStackException - no this.elems + */ + public T pop() { + if (head==null) throw new EmptyStackException(); + final T pop = head.data; + head = head.next; + size--; + return pop; + } + + /** + * Looks at the object at the top of this stack without removing it from the stack. + * @return this.elems[0] + * @throws EmptyStackException - no this.elems + */ + public T peek() { + if (head==null) throw new EmptyStackException(); + return head.data; + } + + /** + * Returns the 1-based position where an object is on this stack. + * If the object o occurs as an item in this stack, this method + * returns the distance from the top of the stack of the occurrence + * nearest the top of the stack; the topmost item on the stack is + * considered to be at distance 1. The equals method is used to + * compare o to the items in this stack. + * @return o in this.elems[int] => min(this.elems.o) + 1, -1 + */ + public int search(Object o) { + StackEntry e = head; + int position = 1; + while(e != null) { + if (equal(o, e.data)) + return position; + e = e.next; + position++; + } + return -1; + } + + /** + * Returns true if the stack is empty; otherwise returns false. + * @return no this.elems + */ + public boolean empty() { return head == null; } + + /** + * Iterates over the items in this LinkedStack, starting + * at the top of the stack and working its way down. + * @return iterator over the elements in this stack. + */ + public Iterator iterator() { + return new Iterator() { + private StackEntry cursor = head, prev = null, pprev = null; + public boolean hasNext() { + return cursor != null; + } + + public T next() { + if (cursor==null) throw new NoSuchElementException(); + pprev = prev; + prev = cursor; + cursor = cursor.next; + return prev.data; + } + + public void remove() { + if (prev==pprev) { + throw new UnsupportedOperationException(); + } else if (prev==head) { + head = cursor; + } else { + pprev.next = cursor; + prev.next = null; + } + prev = pprev; + size--; + } + + }; + } + + /** + * Represents a stack entry. + * @specfield data: T + * @specfield next: StackEntry + */ + @SuppressWarnings("hiding") + private static final class StackEntry { + T data; + StackEntry next; + + StackEntry(T data, StackEntry next) { + this.data = data; + this.next = next; + } + } + + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/collections/SingletonIdentitySet.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/collections/SingletonIdentitySet.java new file mode 100644 index 00000000..58d1fe8a --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/collections/SingletonIdentitySet.java @@ -0,0 +1,132 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.util.collections; + +import java.util.AbstractSet; +import java.util.Collection; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.Set; + +/** + *

This is an immutable singleton set implementation that tests for + * membership using reference-equality in place of object-equality when comparing elements.

+ * @specfield element: V + * @author Emina Torlak + */ +public final class SingletonIdentitySet extends AbstractSet { + private final V element; + + /** + * Constructs a SingletonIdentitySet that will hold the given element. + * @ensures this.element' = element + */ + public SingletonIdentitySet(V element) { + this.element = element; + } + + /** + * Constructs a SingletonIdentitySet that will hold the first element + * returned by the given collection's iterator. + * @ensures this.element' = collection.iterator().next() + * @throws NoSuchElementException - collection.isEmpty() + */ + public SingletonIdentitySet(Collection collection) { + this.element = collection.iterator().next(); + } + + + /** + * Returns true iff this.element and obj are the same object. + * @return this.element == obj + */ + public boolean contains(Object obj) { + return element == obj; + } + + /** + * @see java.util.Set#containsAll(java.util.Collection) + */ + public boolean containsAll(Collection collection) { + if (collection.isEmpty()) return true; + else if (collection.size()==1) return collection.iterator().next()==this.element; + else return false; + } + + /** + * Returns false. + * @return false. + */ + public boolean isEmpty() { + return false; + } + + /** + * @see java.util.Set#iterator() + */ + @SuppressWarnings("unchecked") + public Iterator iterator() { + return Containers.iterate(this.element); + } + + /** + * Returns 1. + * @return 1 + */ + public int size() { + return 1; + } + + /** + * Compares the specified object with this set for equality. Returns + * true if the given object is also a set and the two sets + * contain identical object-references. + * + *

Owing to the reference-equality-based semantics of this set it is + * possible that the symmetry and transitivity requirements of the + * Object.equals contract may be violated if this set is compared + * to a normal set. However, the Object.equals contract is + * guaranteed to hold among SingletonHashSet instances. + * + * @return true if the specified object is equal to this set. + * @see Object#equals(Object) + */ + public boolean equals(Object o) { + if (o == this) { + return true; + } else if (o instanceof Set) { + final Set s = (Set) o; + return s.size()==1 && s.iterator().next()==this.element; + } else { + return false; + } + } + + /** + * Returns 0 if this.element is null; otherwise returns this.element.hashCode(). + * @return this.element = null ? 0 : this.element.hashCode() + * @see java.util.AbstractSet#hashCode() + */ + public int hashCode() { + return this.element==null ? 0 : element.hashCode(); + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/collections/Stack.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/collections/Stack.java new file mode 100644 index 00000000..65ece38c --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/collections/Stack.java @@ -0,0 +1,157 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.util.collections; + +import java.util.EmptyStackException; +import java.util.Iterator; + + +/** + * Represents a last-in-first-out (LIFO) stack of objects. + * The usual push and pop operations are provided, as well as a method to peek at the top item on the stack, + * a method to test for whether the stack is empty, and an iterator over the elements in the stack + * (that does not support removal). When a stack is first created, it contains no items. + * + * @specfield size: int + * @specfield elems: [0..size)->one T + * @author Emina Torlak + */ +public abstract class Stack implements Iterable { + + /** + * Constructs a new stack. + */ + protected Stack() {} + + /** + * Returns the size of this stack. + * @return this.size + */ + public abstract int size(); + + /** + * Pushes an item onto the top of this stack and returns it. + * @ensures this.size' = this.size + 1 && this.elems'[0] = item && + * all i: [0..this.size) | this.elems'[i+1] = this.elems[i] + * @return item + */ + public abstract T push(T item); + + /** + * Removes the object at the top of this stack and returns that object as the value of this function. + * @ensures this.size' = this.size - 1 && + * all i: [1..this.size) | this.elems'[i-1] = this.elems[i] + * @return this.elems[0] + * @throws EmptyStackException - no this.elems + */ + public abstract T pop(); + + /** + * Looks at the object at the top of this stack without removing it from the stack. + * @return this.elems[0] + * @throws EmptyStackException - no this.elems + */ + public abstract T peek(); + + /** + * Returns the 1-based position where an object is on this stack. + * If the object o occurs as an item in this stack, this method + * returns the distance from the top of the stack of the occurrence + * nearest the top of the stack; the topmost item on the stack is + * considered to be at distance 1. The equals method is used to + * compare o to the items in this stack. + * @return o in this.elems[int] => min(this.elems.o) + 1, -1 + */ + public abstract int search(Object o); + + /** + * Returns true if the stack is empty; otherwise returns false. + * @return no this.elems + */ + public abstract boolean empty(); + + /** + * Iterates over the items in this Stack, starting + * at the top of the stack and working its way down. + * @return iterator over the elements in this stack. + */ + public abstract Iterator iterator(); + + /** + * Returns true if both o1 and o2 are null, or + * if they are non-null and 'equals' to each other. + * @return o1=null && o2=null || o1.equals(o2) + */ + static boolean equal(Object o1, Object o2) { + return (o1==null ? o2==null : o1.equals(o2)); + } + + /** + * Returns true if o is a stack containing the same elements + * as this stack, in the same order. + * @return o in Stack && this.elems = o.elems + */ + @SuppressWarnings("unchecked") + public boolean equals(Object o) { + if (this==o) return true; + else if (o instanceof Stack) { + final Stack s = (Stack) o; + if (size() != s.size()) return false; + final Iterator iter0 = iterator(), iter1 = s.iterator(); + while(iter0.hasNext()) { + if (!equal(iter0.next(), iter1.next())) + return false; + } + return true; + } + return false; + } + + /** + * Returns the hashcode for this stack. + * @return the hashcode for this stack. + */ + public int hashCode() { + int code = 0; + for(T item : this) { + if (item!=null) code += item.hashCode(); + } + return code; + } + + /** + * Returns a string represention of this stack. + */ + public String toString() { + final StringBuilder buffer = new StringBuilder("[ "); + final Iterator elems = iterator(); + if (elems.hasNext()) buffer.append(elems.next()); + while(elems.hasNext()) { + buffer.append(", "); + buffer.append(elems.next()); + } + buffer.append(" ]"); + return buffer.toString(); + } + + +} \ No newline at end of file diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/collections/package.html b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/collections/package.html new file mode 100644 index 00000000..0b4d679d --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/collections/package.html @@ -0,0 +1,34 @@ + + + + + + + +Contains specialized collections, such as a set that provides methods for +retrieving elements with a particular hashcode. + +

Package Specification

+ +

Contains specialized collections, such as a set that provides methods for +retrieving elements with a particular hashcode. It also provides a +utility class for working with arrays.

+ + + + diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/ints/AbstractIntCollection.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/ints/AbstractIntCollection.java new file mode 100644 index 00000000..163ead51 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/ints/AbstractIntCollection.java @@ -0,0 +1,182 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.util.ints; + +/** + * A skeletal implementation of the IntCollection interface. + * @author Emina Torlak + */ +public abstract class AbstractIntCollection implements IntCollection { + + /** + * Constructs an empty int collection. + * @ensures this.isEmpty() + */ + protected AbstractIntCollection() {} + + /** + * {@inheritDoc} + * @see kodkod.util.ints.IntSet#isEmpty() + */ + public boolean isEmpty() { return size()==0; } + + /** + * Iterates through this.ints and returns true if it + * finds i, otherwise returns false. + * @return true if i is in this collection, otherwise returns false. + */ + public boolean contains(int i) { + for(IntIterator iter = iterator(); iter.hasNext(); ) { + if (i==iter.next()) return true; + } + return false; + } + + /** + * Throws an UnsupportedOperationException. + * @throws UnsupportedOperationException + */ + public boolean add(int i) { + throw new UnsupportedOperationException(); + } + + /** + * Iterates through the elements of this, removes + * i if it finds it and returns true. Otherwise + * returns false. Throws an UnsupportedOperationException + * if this.iterator() does not support removal. + * @ensures iterates through the elements of this and removes i if it finds it + * @return true if this collection has changed as a result of the call + * @throws UnsupportedOperationException - this.iterator() does not support removal + */ + public boolean remove(int i) { + for(IntIterator iter = iterator(); iter.hasNext(); ) { + if (i==iter.next()) { + iter.remove(); + return true; + } + } + return false; + } + + /** + * {@inheritDoc} + * @see kodkod.util.ints.IntCollection#containsAll(kodkod.util.ints.IntCollection) + */ + public boolean containsAll(IntCollection c) { + if (size()>=c.size()) { + for(IntIterator itr = c.iterator(); itr.hasNext(); ) { + if (!contains(itr.next())) + return false; + } + return true; + } + return false; + } + + /** + * {@inheritDoc} + * @see kodkod.util.ints.IntCollection#addAll(kodkod.util.ints.IntCollection) + */ + public boolean addAll(IntCollection c) { + boolean modified = false; + for(IntIterator itr = c.iterator(); itr.hasNext(); ) { + if (add(itr.next())) + modified = true; + } + return modified; + } + + /** + * {@inheritDoc} + * @see kodkod.util.ints.IntCollection#retainAll(kodkod.util.ints.IntCollection) + */ + public boolean retainAll(IntCollection c) { + boolean modified = false; + for(IntIterator itr = iterator(); itr.hasNext(); ) { + if (!c.contains(itr.next())) { + modified = true; + itr.remove(); + } + } + return modified; + } + + /** + * {@inheritDoc} + * @see kodkod.util.ints.IntCollection#removeAll(kodkod.util.ints.IntCollection) + */ + public boolean removeAll(IntCollection c) { + boolean modified = false; + for(IntIterator itr = iterator(); itr.hasNext();) { + if (c.contains(itr.next())) { + modified = true; + itr.remove(); + } + } + return modified; + } + + /** + * This implementation iterates over this set, removing each + * element using the Iterator.remove operation. Most + * implementations will probably choose to override this method for + * efficiency.

+ * + * Note that this implementation will throw an + * UnsupportedOperationException if the iterator returned by this + * collection's iterator method does not implement the + * remove method and this collection is non-empty. + * + * @throws UnsupportedOperationException if the clear method is + * not supported by this collection. + */ + public void clear() { + for(IntIterator itr = iterator(); itr.hasNext();) { + itr.next(); + itr.remove(); + } + } + + /** + * Returns the result of calling {@linkplain #toArray(int[])} with + * a freshly constructed array of length this.size(). + * @return this.toArray(new int[size()]) + * @see kodkod.util.ints.IntCollection#toArray() + */ + public int[] toArray() { return toArray(new int[size()]); } + + /** + * {@inheritDoc} + * @see kodkod.util.ints.IntCollection#toArray(int[]) + */ + public int[] toArray(int[] array) { + if (array.length < size()) { + array = new int[size()]; + } + int i = 0; + for(IntIterator itr = iterator(); itr.hasNext(); ) { + array[i++] = itr.next(); + } + return array; + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/ints/AbstractIntSet.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/ints/AbstractIntSet.java new file mode 100644 index 00000000..139a2ef4 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/ints/AbstractIntSet.java @@ -0,0 +1,133 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.util.ints; + +import java.util.NoSuchElementException; + +/** + * A skeletal implementation of the IntSet interface. + * @author Emina Torlak + */ +public abstract class AbstractIntSet extends AbstractIntCollection implements IntSet { + + /** + * Constructs an empty int set. + * @ensures no this.ints' + */ + protected AbstractIntSet() {} + + /** + * Throws a NoSuchElementException if this is an empty set. + * @throws NoSuchElementException - this.isEmpty() + */ + final void checkNonEmpty() { + if (isEmpty()) throw new NoSuchElementException("no this.ints"); + } + + /** + * Returns an ascending iterator over all elements in this set. + * This method calls this.iterator(Integer.MIN_VALUE, Integer.MAX_VALUE). + * @return an ascending iterator over all elements in this set. + */ + public IntIterator iterator() { + return iterator(Integer.MIN_VALUE, Integer.MAX_VALUE); + } + + /** + * Returns the first element returned by this.iterator(). + * If this set is empty, throws a NoSuchElementException(). + * @return min(this.ints) + * @throws NoSuchElementException - no this.ints + */ + public int min() { + return iterator().next(); + } + + /** + * Returns the first element returned by this.iterator(Integer.MAX_VALUE, Integer.MIN_VALUE). + * If this set is empty, throws a NoSuchElementException(). + * @return max(this.ints) + * @throws NoSuchElementException - no this.ints + */ + public int max() { + return iterator(Integer.MAX_VALUE, Integer.MIN_VALUE).next(); + } + + /** + * Returns the result of calling super.clone(). + * @return the result of calling super.clone() + * @see java.lang.Object#clone() + */ + public IntSet clone() throws CloneNotSupportedException { + return (IntSet) super.clone(); + } + + /** + * Compares the specified object with this set for equality. + * Returns true if the specified object is also an IntSet, + * the two sets have the same size, and every member of the + * specified set is contained in this set (or equivalently, + * every member of this set is contained in the specified set). + * This definition ensures that the equals method works properly + * across different implementations of the IntSet interface. + * @return o instanceof IntSet and o.size() = this.size() and this.containsAll(o) + */ + public boolean equals(Object o) { + if (o==this) return true; + else if (o instanceof IntSet) { + final IntSet s = (IntSet) o; + return size()==s.size() && containsAll(s); + } else return false; + } + + /** + * Returns the hash code value for this set. The hash code of a set is + * defined to be the {@link Ints#superFastHash(int[])} of the elements in the set, + * taken in the ascending order of values. + * This ensures that s1.equals(s2) implies that s1.hashCode()==s2.hashCode() + * for any two IntSets s1 and s2, as required by the general contract of the Object.hashCode method. + * @return Ints.superFastHash(this.toArray()) + */ + public int hashCode() { + int hash = size(); + for(IntIterator iter = iterator(); iter.hasNext();) { + hash = Ints.superFastHashIncremental(iter.next(), hash); + } + return Ints.superFastHashAvalanche(hash); + } + + /** + * Returns a string representation of this int set. + * @return a string representation of this int set. + */ + public String toString() { + final StringBuilder buf = new StringBuilder("{"); + final IntIterator itr = iterator(); + if (itr.hasNext()) buf.append(itr.next()); + while(itr.hasNext()) { + buf.append(", "); + buf.append(itr.next()); + } + buf.append("}"); + return buf.toString(); + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/ints/AbstractIntVector.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/ints/AbstractIntVector.java new file mode 100644 index 00000000..7e47c310 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/ints/AbstractIntVector.java @@ -0,0 +1,247 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.util.ints; + +import java.util.NoSuchElementException; + + +/** + * A skeletal implementation of the IntVector interface. + * + * + * @specfield length: int + * @specfield elements: [0..size) ->one int + * + * @author Emina Torlak + */ +public abstract class AbstractIntVector extends AbstractIntCollection implements IntVector { + + /** + * Constructs an empty int vector. + * @ensures no this.elements' + */ + protected AbstractIntVector() {} + + /** + * {@inheritDoc} + * @see kodkod.util.ints.IntVector#contains(int) + */ + public boolean contains(int element) { + for(int i = 0, max = size(); i < max; i++) { + if (get(i)==element) return true; + } + return false; + } + + /** + * Throws {@link UnsupportedOperationException}. + * @see kodkod.util.ints.IntVector#set(int,int) + */ + public int set(int index, int element) { + throw new UnsupportedOperationException(); + } + + /** + * {@inheritDoc} + * @see kodkod.util.ints.IntVector#indexOf(int) + */ + public int indexOf(int element) { + for(int i = 0, length=size(); i < length; i++) { + if (get(i)==element) return i; + } + return -1; + } + + /** + * {@inheritDoc} + * @see kodkod.util.ints.IntVector#lastIndexOf(int) + */ + public int lastIndexOf(int element) { + for(int i = size()-1; i >= 0; i--) { + if (get(i)==element) return i; + } + return -1; + } + + /** + * Calls this.add(this.size(), element) + * @see kodkod.util.ints.IntVector#add(int) + * @see kodkod.util.ints.IntVector#add(int,int) + */ + public boolean add(int element) { + final int length = size(); + add(length,element); + return length != size(); + } + + /** + * Throws {@link UnsupportedOperationException} + * @see kodkod.util.ints.IntVector#add(int, int) + */ + public void add(int index, int element) { throw new UnsupportedOperationException(); } + + /** + * Returns the result of calling {@linkplain #addAll(int, IntCollection) this.addAll(size(), c)}. + * @return this.addAll(size(), c) + * @see kodkod.util.ints.AbstractIntCollection#addAll(kodkod.util.ints.IntCollection) + */ + public boolean addAll(IntCollection c) { + return addAll(size(), c); + } + + /** + * Throws an UnsupportedOperationException. + * @see kodkod.util.ints.IntVector#addAll(int, kodkod.util.ints.IntCollection) + */ + public boolean addAll(int index, IntCollection c) { throw new UnsupportedOperationException(); } + + /** + * Throws an UnsupportedOperationException. + * @see kodkod.util.ints.IntVector#removeAt(int) + */ + public int removeAt(int index) { + throw new UnsupportedOperationException(); + } + + /** + * Calls this.iterator(0, length()) + * @see kodkod.util.ints.IntVector#iterator() + * @see kodkod.util.ints.IntVector#iterator(int,int) + */ + public IntIterator iterator() { + return iterator(0, size()); + } + + /** + * {@inheritDoc} + * @see kodkod.util.ints.IntVector#iterator(int, int) + */ + public IntIterator iterator(int fromIndex, int toIndex) { + return fromIndex<=toIndex ? new AscendingIntVectorIterator(fromIndex,toIndex) : + new DescendingIntVectorIterator(fromIndex,toIndex); + } + + /** + * Returns the hash code value for this vector. + * + * @return the hash code value for this vector. + * @see Object#hashCode() + * @see Object#equals(Object) + * @see #equals(Object) + */ + public int hashCode() { + final int length = size(); + int hash = length; + for(int i = 0; i < length; i++) { + hash = Ints.superFastHashIncremental(get(i), hash); + } + return Ints.superFastHashAvalanche(hash); + } + + /** + * Compares the specified object with this vector for equality. Returns + * true if and only if the specified object is also an int vector, both + * vectors have the same size, and all corresponding pairs of elements in + * the two vectors are equal. + * + * @return true if the specified object is equal to this vector. + */ + public boolean equals(Object o) { + if (o==this) return true; + if (o instanceof IntVector) { + final IntVector l = (IntVector) o; + final int length = size(); + if (l.size()==length) { + for (int i = 0; i < length; i++) { + if (get(i) != l.get(i)) return false; + } + return true; + } + } + return false; + } + + /** + * {@inheritDoc} + * @see java.lang.Object#toString() + */ + public String toString() { + final StringBuilder buf = new StringBuilder(); + buf.append("["); + IntIterator itr = iterator(); + if (itr.hasNext()) buf.append(itr.next()); + while(itr.hasNext()) { + buf.append(", "); + buf.append(itr.next()); + } + buf.append("]"); + return buf.toString(); + } + + + private abstract class IntVectorIterator implements IntIterator { + int next, end, last; + IntVectorIterator(int fromIndex, int toIndex) { + next = fromIndex; + end = toIndex; + last = -1; + } + public final void remove() { + if (last < 0) throw new IllegalStateException(); + AbstractIntVector.this.removeAt(last); + next = last; + last = -1; + } + } + + private final class AscendingIntVectorIterator extends IntVectorIterator { + /** + * Constructs a new AscendingIntArrayIterator. + * @requires fromIndex <= toIndex + */ + AscendingIntVectorIterator(int fromIndex, int toIndex) { + super(fromIndex, toIndex); + } + public boolean hasNext() { return last= toIndex + */ + DescendingIntVectorIterator(int fromIndex, int toIndex) { + super(fromIndex, toIndex); + } + public boolean hasNext() { return next > end; } + public int next() { + if (!hasNext()) throw new NoSuchElementException(); + last = next--; + return get(last); + } + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/ints/AbstractSparseSequence.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/ints/AbstractSparseSequence.java new file mode 100644 index 00000000..d9c62248 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/ints/AbstractSparseSequence.java @@ -0,0 +1,445 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.util.ints; + +import java.util.AbstractCollection; +import java.util.Collection; +import java.util.Iterator; +import java.util.NoSuchElementException; + +/** + * A skeletal implementation of the SparseSequence interface. + * The class provides an implementation for the isEmpty, + * putAll, contains, indices, equals, hashCode, + * and toString methods. All other methods must be + * implemented by the subclasses. + * + * @specfield entries: int -> lone V + * + * @author Emina Torlak + */ +public abstract class AbstractSparseSequence implements SparseSequence { + + /** + * Constructs a sparse sequence + * @ensures no this.entries' + */ + protected AbstractSparseSequence() {} + + /** + * Returns true if the size of this sequence is 0. + * @return this.size()==0 + */ + public boolean isEmpty() { + return size()==0; + } + + /** + * Returns an iterator over the entries in this sequence + * in the ascending order of indeces, starting at this.first(). + * This method calls this.iterator(Integer.MIN_VALUE, Integer.MAX_VALUE). + * @return an iterator over this.entries starting at the entry + * with the smallest index + */ + public Iterator> iterator() { + return iterator(Integer.MIN_VALUE, Integer.MAX_VALUE); + } + + /** + * Returns the first element in this sequence, if any. This + * method first checks that the sequence is not empty, and + * if not, returns this.iterator().next(); + * {@inheritDoc} + * @see kodkod.util.ints.SparseSequence#first() + */ + public IndexedEntry first() { + return isEmpty() ? null : iterator().next(); + } + + /** + * Returns the last element in this sequence, if any. This + * method first checks that the sequence is not empty, and + * if not, returns this.iterator(Integer.MAX_VALUE, Integer.MIN_VALUE).next(); + * {@inheritDoc} + * @see kodkod.util.ints.SparseSequence#last() + */ + public IndexedEntry last() { + return isEmpty() ? null : iterator(Integer.MAX_VALUE, Integer.MIN_VALUE).next(); + } + + /** + * Returns the entry whose index is the ceiling of the given index in this sequence. + * This method calls this.iterator(index, Integer.MAX_VALUE), and if the resulting iterator has + * a next element returns it. + * {@inheritDoc} + * @see kodkod.util.ints.SparseSequence#ceil(int) + */ + public IndexedEntry ceil(int index) { + final Iterator> itr = iterator(index, Integer.MAX_VALUE); + return itr.hasNext() ? itr.next() : null; + } + + /** + * Returns the entry whose index is the floor of the given index in this sequence. + * This method calls this.iterator(index, Integer.MIN_VALUE), and if the resulting iterator has + * a next element returns it. + * {@inheritDoc} + * @see kodkod.util.ints.SparseSequence#floor(int) + */ + public IndexedEntry floor(int index) { + final Iterator> itr = iterator(index, Integer.MIN_VALUE); + return itr.hasNext() ? itr.next() : null; + } + + /** + * Returns the set of all indices mapped by this sparse sequence. + * The returned set supports removal iff this is not an unmodifiable + * sparse sequence. + * @return {s: IntSet | s.ints = this.entries.V} + */ + public IntSet indices() { + return new AbstractIntSet() { + public IntIterator iterator(final int from, final int to) { + return new IntIterator() { + Iterator> iter = AbstractSparseSequence.this.iterator(from, to); + public boolean hasNext() { + return iter.hasNext(); + } + public int next() { + return iter.next().index(); + } + public void remove() { + iter.remove(); + } + }; + } + public int size() { + return AbstractSparseSequence.this.size(); + } + public boolean contains(int i) { + return containsIndex(i); + } + public int min() { + final IndexedEntry first = AbstractSparseSequence.this.first(); + if (first==null) + throw new NoSuchElementException(); + return first.index(); + } + public int max() { + final IndexedEntry last = AbstractSparseSequence.this.last(); + if (last==null) + throw new NoSuchElementException(); + return last.index(); + } + public boolean remove(int i) { + final boolean isMapped = containsIndex(i); + AbstractSparseSequence.this.remove(i); + return isMapped; + } + public int floor(int i) { + final IndexedEntry floor = AbstractSparseSequence.this.floor(i); + if (floor==null) + throw new NoSuchElementException(); + return floor.index(); + } + public int ceil(int i) { + final IndexedEntry ceil = AbstractSparseSequence.this.ceil(i); + if (ceil==null) + throw new NoSuchElementException(); + return ceil.index(); + } + public void clear() { + AbstractSparseSequence.this.clear(); + } + public IntSet clone() throws CloneNotSupportedException { + final IntSet s; + if (size()==0) + s = Ints.bestSet(Integer.MIN_VALUE, Integer.MAX_VALUE); + else + s = Ints.bestSet(min(), max()); + s.addAll(this); + return s; + } + }; + } + + /** + * {@inheritDoc} + * @see kodkod.util.ints.SparseSequence#values() + */ + public Collection values() { + return new AbstractCollection() { + + public int size() { + return AbstractSparseSequence.this.size(); + } + + public boolean isEmpty() { + return AbstractSparseSequence.this.isEmpty(); + } + + public boolean contains(Object arg0) { + return AbstractSparseSequence.this.contains(arg0); + } + + public Iterator iterator() { + return new Iterator() { + Iterator> iter = AbstractSparseSequence.this.iterator(); + public boolean hasNext() { + return iter.hasNext(); + } + public V next() { + return iter.next().value(); + } + public void remove() { + iter.remove(); + } + }; + } + + public void clear() { + AbstractSparseSequence.this.clear(); + } + }; + } + + /** + * Returns true if this sparse sequence has an entry for the + * given index; otherwise returns false. This method returns + * the value of this.iterator(index,index).hasNext(); + * {@inheritDoc} + * @see kodkod.util.ints.SparseSequence#containsIndex(int) + */ + public boolean containsIndex(int index) { + return iterator(index,index).hasNext(); + } + + /** + * Iterates through all the entries in this sequence and returns + * true if one of the encountered entries has the given object as + * its value. + * @return {@inheritDoc} + * @see kodkod.util.ints.SparseSequence#contains(java.lang.Object) + */ + public boolean contains(Object value) { + for(IndexedEntry v: this) { + if (equal(value, v.value())) + return true; + } + return false; + } + + /** + * Returns the result of calling super.clone(). + * @see java.lang.Object#clone() + */ + @SuppressWarnings("unchecked") + public SparseSequence clone() throws CloneNotSupportedException { + return (SparseSequence) super.clone(); + } + + /*---------- adapted from java.util.AbstractMap -----------*/ + + /** + * Removes the entry with the given index, if it exists, and + * returns the value previously stored at the index. If the + * sequence had no previous mapping for the index, null is returned. + * This method obtains an iterator from index to index and removes + * its sole element, if any. + * {@inheritDoc} + * @see kodkod.util.ints.SparseSequence#remove(int) + */ + public V remove(int index) { + final Iterator> itr = iterator(index,index); + if (itr.hasNext()) { + final V ret = itr.next().value(); + itr.remove(); + return ret; + } + return null; + } + + /** + * Removes all entries from this sequences. This method + * obtains an iterator over the sequences and calls remove() + * after each call to next(). + * {@inheritDoc} + * @see kodkod.util.ints.SparseSequence#clear() + */ + public void clear() { + final Iterator> itr = iterator(); + while(itr.hasNext()) { + itr.next(); + itr.remove(); + } + } + + + /** + * Throws an UnsupportedOperationException. + * @throws UnsupportedOperationException + */ + public V put(int index, V value) { + throw new UnsupportedOperationException(); + } + + /** + * Copies all of the entries from the specified sparse sequence to + * this sequence. This implementation calls put(e.index, e.value) on + * this sequence once for each entry e in the specified sequence. + * @ensures this.entries' = this.entries ++ s.entries + */ + public void putAll(SparseSequence s) { + Iterator> i = s.iterator(); + while (i.hasNext()) { + IndexedEntry e = i.next(); + put(e.index(), e.value()); + } + } + + /** + * Returns true if both o1 and o2 are null, or + * o1.equals(o2) + * @return o1 and o2 are equal + */ + static boolean equal(Object o1, Object o2) { + return o1==null ? o2==null : o1.equals(o2); + } + + /** + * Returns true if the indexed entries e0 and e1 are equal to each other. + * @requires e0 != null && e1 != null + * @return e0.index = e1.index && e0.value = e1.value + */ + static boolean equal(IndexedEntry e0, IndexedEntry e1) { + return e0.index()==e1.index() && equal(e0.value(), e1.value()); + } + + /** + * Compares the specified object with this sequence for equality. Returns + * true if the given object is also a sequence and the two sequences + * represent the same function from integers to E.

+ * + * This implementation first checks if the specified object is this sequence; + * if so it returns true. Then, it checks if the specified + * object is a sequence whose size is identical to the size of this set; if + * not, it returns false. If so, it iterates over this sequences's + * entries, and checks that the specified sequence + * contains each entry that this sequence contains. If the specified sequence + * fails to contain such an entry, false is returned. If the + * iteration completes, true is returned. + * @return o in SparseSequence && o.entries = this.entries + */ + @SuppressWarnings("unchecked") + public boolean equals(Object o) { + if (o == this) return true; + + if (!(o instanceof SparseSequence)) return false; + + SparseSequence s = (SparseSequence) o; + if (s.size() != size()) return false; + + try { + final Iterator> i1 = iterator(), i2 = s.iterator(); + while (i1.hasNext()) { + if (!equal(i1.next(), i2.next())) + return false; + } + } catch(ClassCastException unused) { + return false; + } catch(NullPointerException unused) { + return false; + } + + return true; + } + + /** + * Returns the hashcode for an indexed entry. + * @requires e != null + * @return e.index ^ e.value.hashCode() + */ + static int hashCode(IndexedEntry e) { + return e.index() ^ (e.value()==null ? 0 : e.value().hashCode()); + } + + /** + * Returns the hash code value for this sparse sequence. + * The hash code of a sparse sequence is defined to be the sum of the + * hashCodes of each entry of its entries. This ensures that t1.equals(t2) + * implies that t1.hashCode()==t2.hashCode() for any two sequences t1 and t2, + * as required by the general contract of Object.hashCode. + * This implementation iterates over this.entries, calling + * hashCode on each IndexedEntry in the sequence, and adding + * up the results. + * @return sum(this.entries.hashCode()) + */ + public int hashCode() { + int h = 0; + for (IndexedEntry e : this) + h += hashCode(e); + return h; + } + + /** + * Returns a string representation of this sequence. The string representation + * consists of a list of index-value mappings in the order returned by the + * sequences iterator, enclosed in brackets + * ("[]"). Adjacent entries are separated by the characters + * ", " (comma and space). Each index-value mapping is rendered as + * the index followed by an equals sign ("=") followed by the + * associated value. Elements are converted to strings as by + * String.valueOf(Object).

+ * + * This implementation creates an empty string buffer, appends a left + * bracket, and iterates over the map's entries, appending + * the string representation of each IndexedEntry in turn. After + * appending each entry except the last, the string ", " is + * appended. Finally a right bracket is appended. A string is obtained + * from the stringbuffer, and returned. + * + * @return a String representation of this map. + */ + public String toString() { + final StringBuilder buf = new StringBuilder(); + buf.append("["); + + final Iterator> i = iterator(); + boolean hasNext = i.hasNext(); + while (hasNext) { + IndexedEntry e = i.next(); + buf.append(e.index()); + buf.append("="); + if (e.value() == this) + buf.append("(this sequence)"); + else + buf.append(e.value()); + hasNext = i.hasNext(); + if (hasNext) buf.append(", "); + } + + buf.append("]"); + return buf.toString(); + } + + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/ints/ArrayIntSet.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/ints/ArrayIntSet.java new file mode 100644 index 00000000..219f4d56 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/ints/ArrayIntSet.java @@ -0,0 +1,219 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.util.ints; + +import java.util.Arrays; +import java.util.NoSuchElementException; + +/** + * An immutable set of integers, stored in a sorted array. + * + * @specfield ints: set int + * @author Emina Torlak + */ +public final class ArrayIntSet extends AbstractIntSet { + private final int[] ints; + private final int hashcode; + /** + * Constructs a set view for the given array. The array must contain no duplicates, + * its elements must be sorted + * in the ascending order, and its contents + * must not be changed while it is in use by this set + * @requires all i, j: [0..ints.length) | i < j => array[i] <= array[j] + * @ensures this.ints' = ints + */ + public ArrayIntSet(int[] ints) { + this.ints = ints; + this.hashcode = Ints.superFastHash(ints); + } + + /** + * Constructs an ArrayIntSet that is equal to the + * given set. + * @ensures this.ints' = s.ints + */ + public ArrayIntSet(IntSet s) { + this(s.toArray()); + } + + /** + * {@inheritDoc} + * @see kodkod.util.ints.IntSet#iterator(int, int) + */ + public IntIterator iterator(final int from, final int to) { + return from <= to ? new AscendingIntArrayIterator(from,to) : new DescendingIntArrayIterator(from,to); + } + + /** + * {@inheritDoc} + * @see kodkod.util.ints.IntSet#size() + */ + public int size() { + return ints.length; + } + + /** + * @see kodkod.util.ints.IntSet#ceil(int) + */ + public int ceil(int i) { + final int index = Arrays.binarySearch(ints, i); + if (index==-ints.length-1) throw new NoSuchElementException(); + else return index >= 0 ? ints[index] : ints[-index-1]; + } + + /** + * @see kodkod.util.ints.IntSet#floor(int) + */ + public int floor(int i) { + final int index = Arrays.binarySearch(ints, i); + if (index==-1) throw new NoSuchElementException(); + else return index >= 0 ? ints[index] : ints[-index-2]; + } + + /** + * Returns true if i is in this set. + * @return i in this.ints + * @see kodkod.util.ints.IntSet#contains(int) + */ + public boolean contains(int i) { + return Arrays.binarySearch(ints, i) >= 0; + } + + /** + * Returns the smallest element in this set. + * Throws a NoSuchElementException if this set is empty. + * @return min(this.ints) + * @throws java.util.NoSuchElementException - no this.ints + * @see kodkod.util.ints.IntSet#max() + */ + @Override + public int max() { + if (ints.length==0) throw new NoSuchElementException(); + return ints[ints.length-1]; + } + + /** + * Returns the largest element in this set. + * Throws a NoSuchElementException if this set is empty. + * @return max(this.ints) + * @throws java.util.NoSuchElementException - no this.ints + * @see kodkod.util.ints.IntSet#min() + */ + @Override + public int min() { + if (ints.length==0) throw new NoSuchElementException(); + return ints[0]; + } + + /** + * {@inheritDoc} + * @see kodkod.util.ints.IntCollection#toArray() + */ + @Override + public int[] toArray() { + final int[] ret = new int[ints.length]; + System.arraycopy(ints, 0, ret, 0, ints.length); + return ret; + } + + /** + * {@inheritDoc} + * @see kodkod.util.ints.IntCollection#toArray(int[]) + */ + @Override + public int[] toArray(int[] array) { + if (array.length < size()) { + array = new int[ints.length]; + } + System.arraycopy(ints, 0, array, 0, ints.length); + return array; + } + + /** + * {@inheritDoc} + * @see kodkod.util.ints.IntSet#hashCode() + */ + @Override + public int hashCode() { return hashcode; } + + /** + * {@inheritDoc} + * @see kodkod.util.ints.IntSet#equals(java.lang.Object) + */ + @Override + public boolean equals(Object o) { + return (o instanceof ArrayIntSet) ? + java.util.Arrays.equals(ints, ((ArrayIntSet)o).ints) : + super.equals(o); + } + + /** + * An iterator that returns the elements of this int set in the + * ascending order. + * @author Emina Torlak + */ + private final class AscendingIntArrayIterator implements IntIterator { + private int next, end; + /** + * Constructs a new AscendingIntArrayIterator. + * @requires from <= to + */ + AscendingIntArrayIterator(int from, int to) { + final int fromIndex = Arrays.binarySearch(ints, from); + final int toIndex = Arrays.binarySearch(ints, to); + next = fromIndex >= 0 ? fromIndex : -fromIndex-1; + end = toIndex >=0 ? toIndex : -toIndex-2; + } + public boolean hasNext() { return next>=0 && next <= end; } + public int next() { + if (!hasNext()) throw new NoSuchElementException(); + return ints[next++]; + } + public final void remove() { throw new UnsupportedOperationException(); } + } + + /** + * An iterator that returns the elements of this int set in the + * descending order. + * @author Emina Torlak + */ + private final class DescendingIntArrayIterator implements IntIterator { + private int next, end; + /** + * Constructs a new AscendingIntArrayIterator. + * @requires from >= to + */ + DescendingIntArrayIterator(int from, int to) { + final int fromIndex = Arrays.binarySearch(ints, from); + final int toIndex = Arrays.binarySearch(ints, to); + next = fromIndex >= 0 ? fromIndex : -fromIndex-2; + end = toIndex >=0 ? toIndex : -toIndex-1; + } + public boolean hasNext() { return next >= end; } + public int next() { + if (!hasNext()) throw new NoSuchElementException(); + return ints[next--]; + } + public final void remove() { throw new UnsupportedOperationException(); } + } + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/ints/ArrayIntVector.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/ints/ArrayIntVector.java new file mode 100644 index 00000000..946ae275 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/ints/ArrayIntVector.java @@ -0,0 +1,241 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.util.ints; + +/** + * A mutable implementation of the IntVector interface. Implements + * all optional IntVector operations. In addition to implementing the IntVector interface, + * this class provides methods to manipulate the size of the array that is + * used internally to store the elements of the IntVector.

+ * + * The size, isEmpty, get, set, + * and iterator operations run in constant + * time. The insert operations run in amortized constant time, + * that is, adding n elements requires O(n) time. All of the other operations + * run in linear time (roughly speaking).

+ * + * Each MutableIntVector instance has a capacity. The capacity is + * the size of the array used to store the elements in the list. It is always + * at least as large as the list size. As elements are added to a MutableIntVector, + * its capacity grows automatically. The details of the growth policy are not + * specified beyond the fact that adding an element has constant amortized + * time cost.

+ * + * An application can increase the capacity of an MutableIntVector instance + * before adding a large number of elements using the ensureCapacity + * operation. This may reduce the amount of incremental reallocation.

+ * + * This impementation is not synchronized and its iterators are not fail-fast. + * + * @author Emina Torlak + */ +public final class ArrayIntVector extends AbstractIntVector { + + private int[] elements; + private int size; + + /** + * Constructs an empty MutableIntVector with the specified initial capacity. + * + * @exception IllegalArgumentException if the specified initial capacity + * is negative + */ + public ArrayIntVector(int initialCapacity) { + if (initialCapacity < 0) + throw new IllegalArgumentException("Illegal Capacity: "+ + initialCapacity); + this.elements = new int[initialCapacity]; + } + + /** + * Constructs an empty MutableIntVector with an initial capacity of ten. + */ + public ArrayIntVector() { + this(10); + } + + /** + * Constructs a MutableIntVector containing the elements of the specified + * array, in proper sequence. The MutableIntVector instance has an initial capacity of + * 110% the size of the specified collection. + * + * @throws NullPointerException if the specified array is null. + */ + public ArrayIntVector(int[] array) { + size = array.length; + // Allow 10% room for growth + int capacity = (int) Math.min((size*110L)/100, Integer.MAX_VALUE); + elements = new int[capacity]; + System.arraycopy(array, 0, elements, 0, size); + } + + /** + * Trims the capacity of this MutableIntVector instance to be the + * list's current size. An application can use this operation to minimize + * the storage of an MutableIntVector instance. + */ + public void trimToSize() { + + int oldCapacity = elements.length; + if (size < oldCapacity) { + int[] oldData = elements; + elements = new int[size]; + System.arraycopy(oldData, 0, elements, 0, size); + } + } + + /** + * Increases the capacity of this MutableIntVector instance, if + * necessary, to ensure that it can hold at least the number of elements + * specified by the minimum capacity argument. + */ + public void ensureCapacity(int minCapacity) { + + int oldCapacity = elements.length; + if (minCapacity > oldCapacity) { + int[] oldData = elements; + int newCapacity = (oldCapacity * 3)/2 + 1; + if (newCapacity < minCapacity) + newCapacity = minCapacity; + elements = new int[newCapacity]; + System.arraycopy(oldData, 0, elements, 0, size); + } + } + + + + /** + * @throws IndexOutOfBoundsException - index < 0 or index >= size + */ + private void checkExcludeLength(int index) { + if (index < 0 || index >= size) + throw new IndexOutOfBoundsException(); + } + + /** + * @throws IndexOutOfBoundsException - index < 0 or index > size + */ + private void checkIncludeLength(int index) { + if (index < 0 || index > size) + throw new IndexOutOfBoundsException(); + } + + /** + * {@inheritDoc} + * @see kodkod.util.ints.IntVector#get(int) + */ + public int get(int index) { + checkExcludeLength(index); + return elements[index]; + } + + /** + * {@inheritDoc} + * @see kodkod.util.ints.IntVector#size() + */ + public int size() { + return size; + } + + /** + * {@inheritDoc} + * @see kodkod.util.ints.IntVector#set(int, int) + */ + @Override + public int set(int index, int element) { + final int oldValue = get(index); + elements[index] = element; + return oldValue; + } + + /** + * {@inheritDoc} + * @see kodkod.util.ints.IntVector#add(int) + */ + @Override + public boolean add(int element) { + ensureCapacity(size+1); + elements[size++] = element; + return true; + } + + /** + * {@inheritDoc} + * @see kodkod.util.ints.IntVector#add(int, int) + */ + @Override + public void add(int index, int element) { + checkIncludeLength(index); + + ensureCapacity(size+1); + System.arraycopy(elements, index, elements, index + 1, size - index); + elements[index] = element; + size++; + } + + /** + * {@inheritDoc} + * @see kodkod.util.ints.IntVector#addAll(int, kodkod.util.ints.IntCollection) + */ + @Override + public boolean addAll(int index, IntCollection c) { + checkIncludeLength(index); + + final int csize = c.size(); + if (csize==0) return false; + + ensureCapacity(size+csize); + System.arraycopy(elements, index, elements, index + csize, size - index); + + for(IntIterator iter = c.iterator(); iter.hasNext(); ) { + elements[index++] = iter.next(); + } + + return true; + } + + /** + * {@inheritDoc} + * @see kodkod.util.ints.AbstractIntVector#removeAt(int) + */ + @Override + public int removeAt(int index) { + checkExcludeLength(index); + final int old = elements[index]; + System.arraycopy(elements, index+1, elements, index, size - index - 1); + size--; + return old; + } + + /** + * {@inheritDoc} + * @see kodkod.util.ints.AbstractIntCollection#toArray(int[]) + */ + @Override + public int[] toArray(int[] array) { + if (array.length < size) { + array = new int[size]; + } + System.arraycopy(elements, 0, array, 0, size); + return array; + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/ints/ArraySequence.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/ints/ArraySequence.java new file mode 100644 index 00000000..5a4dbc81 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/ints/ArraySequence.java @@ -0,0 +1,387 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.util.ints; + +import java.util.Iterator; +import java.util.NoSuchElementException; + + +/** + * An implementation of a sparse sequence based on an array. + * This implementation can be used only when the indices + * of the sequence are known in advance. The indices with + * which an ArraySequence is construct remain fixed throughout. + * The put operation fails whenever its index argument is not one + * of the sequence's pre-set indices, and iteration time is proportional + * to the number of pre-set indices. This sequence does not + * allow null values. The lookup and put operations are logarithmic in + * the number of pre-set indices. + * + * @specfield indeces: set int + * @specfield entries: indeces -> lone (V - null) + * @author Emina Torlak + */ +public final class ArraySequence extends AbstractSparseSequence implements Cloneable { + private final EntryView[] entries; + private int size; + + /** + * Constructs an array sequence that contains + * the given indeces. + * @ensures this.indeces' = indeces && no this.entries' + * @throws NullPointerException - indeces = null + */ + @SuppressWarnings("unchecked") + public ArraySequence(IntSet indices) { + this.entries = new EntryView[indices.size()]; + this.size = indices.size(); + final IntIterator indexIter = indices.iterator(); + for(int i = 0; indexIter.hasNext(); i++) { + entries[i] = new EntryView(indexIter.next(), null); + } + } + + /** + * Constructs a new array sequence with the same index/value mappings + * as the given sequence. + * @ensures this.entries' = s.entries + * @throws NullPointerException - s = null || null in s + */ + @SuppressWarnings("unchecked") + public ArraySequence(SparseSequence s) { + this.entries = new EntryView[s.size()]; + this.size = s.size(); + int i = 0; + for(IndexedEntry entry : s) { + if (entry.value()==null) + throw new NullPointerException(); + entries[i++] = new EntryView(entry.index(), (V)entry.value()); + } + } + + /** + * Copy constructor. + * @ensures constructs a deep copy of the original array sequence. + */ + @SuppressWarnings("unchecked") + private ArraySequence(ArraySequence original) { + this.size = original.size; + this.entries = new EntryView[original.entries.length]; + int i = 0; + for(EntryView e : original.entries) + this.entries[i++] = new EntryView(e.index(), e.value()); + } + + /** + * Returns the number of entries in this sequence. + * @return #this.entries + * @see kodkod.util.ints.SparseSequence#size() + */ + public int size() { + return size; + } + + /** + * Returns true if this sequence is empty; otherwise returns false. + * @return no this.entries + * @see kodkod.util.ints.SparseSequence#isEmpty() + */ + public boolean isEmpty() { + return size==0; + } + + /** + * {@inheritDoc} + * @see kodkod.util.ints.SparseSequence#clear() + */ + public void clear() { + for(EntryView e : entries) { + e.setValue(null); + } + } + + /** + * Searches this.entries for the specified index using the + * binary search algorithm. If the index is not found, then + * -insertionPoint - 1 is returned, where insertionPoint is + * the point at which the given index would be inserted into + * this.entries. + * @return the position in this.entries where the entry with + * the given index is located, or -insertionPoint - 1 if + * the index is not in this.indeces + */ + private final int search(int index) { + int low = 0; + int high = entries.length-1; + + while (low <= high) { + int mid = (low + high) >>> 1; + int midIndex = entries[mid].index(); + if (midIndex < index) + low = mid + 1; + else if (midIndex > index) + high = mid - 1; + else + return mid; // key found + } + + return -(low + 1); // key not found. + } + + /** + * Puts the given value at the specified index. If the + * sequence already mapped the index to a value, the + * previous value is replaced with the new one and returned. + * + * @ensures this.entries' = this.entries + index->value + * @return this.entries[index] + * @throws IndexOutOfBoundsException - index !in this.indeces + * @throws NullPointerException - value = null + * @see kodkod.util.ints.SparseSequence#put(int, Object) + */ + public V put(int index, V value) { + if (value==null) + throw new NullPointerException(); + final int position = search(index); + if (position < 0) + throw new IndexOutOfBoundsException(""+index); + if (entries[position]==null) size++; + return entries[position].setValue(value); + } + + /** + * Returns the value to which this sequence maps the given + * index. If the index is not mapped, null is returned. + * @return this.entries[index] + * @see kodkod.util.ints.SparseSequence#get(int) + */ + public V get(int index) { + final int position = search(index); + return position < 0 ? null : entries[position].value(); + } + + /** + * Removes the entry with the given index, if it exists, and + * returns the value previously stored at the index. If the + * sequence had no previous mapping for the index, null is returned. + * @ensures this.entries' = this.entries - index->E + * @return this.entries[index] + * @see kodkod.util.ints.SparseSequence#remove(int) + */ + public V remove(int index) { + final int position = search(index); + if (position < 0) + return null; + else { + if (entries[position].value()!=null) size--; + return entries[position].setValue(null); + } + } + + /** + * Returns true if this sparse sequence has an entry for the + * given index; otherwise returns false. + * @return index in this.indeces + * @see kodkod.util.ints.SparseSequence#containsIndex(int) + */ + public boolean containsIndex(int index) { + final int position = search(index); + return position >= 0 && entries[position].value()!=null; + } + + /** + * Returns an iterator over the entries in this sequence, + * whose indeces are between from and to. If from < to, + * the entries are returned in the ascending order of + * indeces. Otherwise, they are returned in the descending + * order of indeces. + * @return an iterator over the entries in this sequence + * whose indeces are between from and to. Formally, if + * from < to, then the first and last entries returned + * by the iterator are this.ceil(from) and this.floor(to). + * Otherwise, they are this.floor(from) and this.ceil(to). + * @see kodkod.util.ints.SparseSequence#iterator(int, int) + */ + public Iterator> iterator(int from, int to) { + return from <= to ? new AscendingIterator(from, to) : new DescendingIterator(from, to); + } + + /** + * Returns the entry with the smallest index. If the sequence + * is empty, returns null. + * @return {e: IndexedEntry | e.index = min(this.entries.E) && + * e.value = this.entries[e.index] } + * @see kodkod.util.ints.SparseSequence#first() + */ + public IndexedEntry first() { + if (size==0) + return null; + for(EntryView e : entries) { + if (e.value()!=null) + return e; + } + throw new InternalError(); // unreachable code + } + + /** + * Returns the entry with the largest index. If the sequence + * is empty, returns null. + * @return {e: IndexedEntry | e.index = max(this.entries.E) && + * e.value = this.entries[e.index] } + * @see kodkod.util.ints.SparseSequence#last() + */ + public IndexedEntry last() { + if (size==0) + return null; + for(int i = entries.length-1; i>=0; i--) { + if (entries[i].value()!=null) + return entries[i]; + } + throw new InternalError(); // unreachable code + } + + /** + * If an entry for the given index exists, it is returned. Otherwise, + * successor(index) is returned. + * @return this.containsIndex(index) => + * {e: IndexedEntry | e.index = index && e.value = this.entries[index] }, + * successor(index) + * @see kodkod.util.ints.SparseSequence#ceil(int) + */ + public IndexedEntry ceil(int index) { + final int position = search(index); + for(int i = position < 0 ? -position-1 : position; i < entries.length; i++) { + if (entries[i].value()!=null) + return entries[i]; + } + return null; + } + + /** + * If an entry for the given index exists, it is returned. Otherwise, + * predecessor(index) is returned. + * @return this.containsIndex(index) => + * {e: IndexedEntry | e.index = index && e.value = this.entries[index] }, + * predecessor(index) + * @see kodkod.util.ints.SparseSequence#floor(int) + */ + public IndexedEntry floor(int index) { + final int position = search(index); + for(int i = position < -1 ? -position-2 : position; i >=0 ; i--) { + if (entries[i].value()!=null) + return entries[i]; + } + return null; + } + + /** + * Returns a copy of this sparse sequence. The copy is independent of this + * sequence. + * @return a copy of this sparse sequence. + * @see kodkod.util.ints.SparseSequence#clone() + */ + public ArraySequence clone() { + return new ArraySequence(this); + } + + /** + * An iterator that traverses this sequence in the ascending order. + * + * @author Emina Torlak + */ + private final class AscendingIterator implements Iterator> { + final int endIndex; + IndexedEntry lastReturned = null; + int cursor; + + /** + * @requires from <= to + */ + AscendingIterator(int from, int to) { + final int fromPos = search(from); + final int toPos = search(to); + cursor = fromPos < 0 ? -fromPos-1 : fromPos; + endIndex = toPos < -1 ? -toPos-2 : toPos; + } + + public boolean hasNext() { + while (cursor < entries.length && entries[cursor].value()==null) + cursor++; + return cursor<=endIndex; + } + + public IndexedEntry next() { + if (!hasNext()) + throw new NoSuchElementException(); + return lastReturned=entries[cursor++]; + } + + public void remove() { + if (lastReturned==null) + throw new IllegalStateException(); + entries[lastReturned.index()].setValue(null); + lastReturned = null; + } + } + + /** + * An iterator that traverses this sequence in the descending order. + * + * @author Emina Torlak + */ + private final class DescendingIterator implements Iterator> { + final int endIndex; + IndexedEntry lastReturned = null; + int cursor; + + /** + * @requires from >= to + */ + DescendingIterator(int from , int to) { + final int fromPos = search(from); + final int toPos = search(to); + cursor = fromPos < -1 ? -fromPos-2 : fromPos; + endIndex = toPos < 0 ? -toPos-1 : toPos; + } + + public boolean hasNext() { + while (cursor >= 0 && entries[cursor].value()==null) + cursor--; + return cursor>=endIndex; + } + + public IndexedEntry next() { + if (!hasNext()) + throw new NoSuchElementException(); + return lastReturned=entries[cursor--]; + } + + public void remove() { + if (lastReturned==null) + throw new IllegalStateException(); + entries[lastReturned.index()].setValue(null); + lastReturned = null; + } + + } + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/ints/EntryView.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/ints/EntryView.java new file mode 100644 index 00000000..abc64364 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/ints/EntryView.java @@ -0,0 +1,137 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.util.ints; + +/** + * A mutable IndexedEntry. This class provides + * various convience method for changing the entry state. + * @author Emina Torlak + */ +class EntryView implements IndexedEntry { + private int index; + private V value; + + /** + * Constructs a new entry view with the given index and value. + * @ensures this.index' = index and this.value' = value + */ + EntryView(int index, V value) { + this.index = index; + this.value = value; + } + + + /** + * Sets this.index to the given index, + * and returns the old index. + * @ensures this.index' = newIndex + * @return this.index + */ + int setIndex(int newIndex) { + final int oldIndex = this.index; + this.index = newIndex; + return oldIndex; + } + + /** + * Sets this.value to the given value, + * and returns the old value. + * @ensures this.value' = newValue + * @return this.value + */ + V setValue(V newValue) { + final V oldValue = this.value; + this.value = newValue; + return oldValue; + } + + /** + * Sets this.index and this.value to the given + * index and value, and returns this. + * @ensures this.index' = newIndex && this.value' = newValue + * @return this + */ + IndexedEntry setView(int newIndex, V newValue) { + this.index = newIndex; + this.value = newValue; + return this; + } + + /** + * Sets this.index to the given + * index, and returns this. + * @ensures this.index' = newIndex + * @return this + */ + IndexedEntry setIndexView(int newIndex) { + this.index = newIndex; + return this; + } + + /** + * Sets this.value to the given + * value, and returns this. + * @ensures this.value' = newValue + * @return this + */ + IndexedEntry setValueView(V newValue) { + this.value = newValue; + return this; + } + + /** + * @see kodkod.util.ints.IndexedEntry#index() + */ + public int index() { + return index; + } + + /** + * @see kodkod.util.ints.IndexedEntry#value() + */ + public V value() { + return value; + } + + /** + * @see java.lang.Object#toString() + */ + public final String toString() { + return index + "=" + value; + } + + /** + * @see java.lang.Object#equals(java.lang.Object) + */ + public final boolean equals(Object o) { + if (o==this) return true; + if (!(o instanceof IndexedEntry)) return false; + return AbstractSparseSequence.equal(this, (IndexedEntry)o); + } + + /** + * @see java.lang.Object#hashCode() + */ + public final int hashCode() { + return AbstractSparseSequence.hashCode(this); + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/ints/HomogenousSequence.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/ints/HomogenousSequence.java new file mode 100644 index 00000000..7e71c8f5 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/ints/HomogenousSequence.java @@ -0,0 +1,327 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.util.ints; + +import java.util.Iterator; + +/** + * A sparse sequence implementation based on an {@link kodkod.util.ints.IntSet IntSet}. + * This implementation can be used only when all entries in the sequence map to the same value. + * + *

Important Implementation Note: As this implementation does not actually store any + * {@link kodkod.util.ints.IndexedEntry indexed entries}, + * the methods {@link #first()}, {@link #last()}, {@link #ceil(int)}, and {@link #floor(int)} + * may re-use the same IndexedEntry object. For example, suppose that an entry e with e.index = 1 + * is returned by a call to predecessor(2) on a homogenous sequence h = {<0,v>, <1,v>, <2,v>}. + * A subsequent call to h.predecessor(1) may return the same object e, with its index set to 0. Hence, + * the following assertion may fail:

+ *
+ * IndexedEntry e1 = h.predecessor(2);
+ * assert e1.index()==1; // this will work
+ * IndexedEntry e2 = h.predecessor(1);
+ * assert e1.index()==1; // this may fail, as e1 may be == to e2
+ * 
+ *

The entries returned by this implementation's {@link #iterator()} are unique + * to that iterator (but not necessarily independent of each other). For example,

+ *
+ * // let s be a range sequence abstractly represented as { 0->v, 1->v, 2->v }
+ * Iterator> iter1 = s.iterator();
+ * IndexedEntry e1 = iter1.next();
+ * assert e1.index()==0; // this will work
+ * iter1.next();
+ * assert e1.index()==0; // this may fail, as the previous call may have changed the state of e1
+ * Iterator> iter2 = s.iterator();
+ * IndexedEntry e2 = iter2.next();
+ * iter1.next();
+ * assert e2.index()==0; // this will work
+ * 
+ * @specfield indeces: set int + * @specfield entries: indeces -> one V + * @author Emina Torlak + */ +public final class HomogenousSequence extends AbstractSparseSequence { + private final IntSet indices; + private final V value; + private final EntryView view; + + /** + * Constructs a new homogenous sequence for the given value, backed + * by a {@link IntTreeSet IntTreeSet} instance. + * @ensures this.value' = value && no this.indices' + */ + public HomogenousSequence(V value) { + this.value = value; + this.indices = new IntTreeSet(); + this.view = new EntryView(Integer.MIN_VALUE, value); + } + + /** + * Constructs a new homogenous sequence for the given value, backed + * by the specified intset. Any changes to the provided set will + * be reflected in this sequence, and vice versa. This sequence will + * be unmodifiable if the given index set is unmodifiable. + * @requires indices is cloneable + * @ensures this.value' = value && this.indices' = indices + */ + public HomogenousSequence(V value, IntSet indices) { + this.value = value; + this.indices = indices; + this.view = new EntryView(Integer.MIN_VALUE, value); + } + + /** + * Copy constructor + * @ensures constructs a deep copy of the original + */ + private HomogenousSequence(HomogenousSequence original) { + this.value = original.value; + this.view = new EntryView(Integer.MIN_VALUE, value); + try { + this.indices = original.indices.clone(); + } catch (CloneNotSupportedException e) { + throw new InternalError(); // unreachable code. + } + } + + /** + * Constructs a new homogeneous sequence from the provided sequence. The + * returned sequence is backed by a {@link IntTreeSet IntTreeSet} instance. + * @requires one seq.entries[int] + * @ensures this.value' = seq.entries[int] && this.indices' = seq.indices() + * @throws NullPointerException - seq = null + * @throws IllegalArgumentException - seq.isEmpty() + * @throws IllegalArgumentException - #seq.entries[int] > 1 + */ + public HomogenousSequence(SparseSequence seq) { + if (seq.isEmpty()) + throw new IllegalArgumentException(); + this.indices = new IntTreeSet(); + this.value = seq.first().value(); + this.view = new EntryView(Integer.MIN_VALUE, value); + for(IndexedEntry e : seq) { + if (!value.equals(e.value())) + throw new IllegalArgumentException(); + indices.add(e.index()); + } + } + + /** + * Returns the set of all indices mapped by this sparse sequence. + * The returned set supports removal and addition iff this is not + * backed by an unmodifiable intset. + * @return {s: IntSet | s.ints = this.entries.V} + */ + public IntSet indices() { + return indices; + } + + /** + * Returns an iterator over the entries in this sequence, + * whose indeces are between from and to. If from < to, + * the entries are returned in the ascending order of + * indeces. Otherwise, they are returned in the descending + * order of indeces. + *

While the returned iterator i re-uses the same IndexedEntry + * object, it is not shared with other iterators or other method calls. + * In particular, no other call except i.next() can change the + * value of the re-used object.

+ * @return an iterator over the entries in this sequence + * whose indeces are between from and to. Formally, if + * from < to, then the first and last entries returned + * by the iterator are this.ceil(from) and this.floor(to). + * Otherwise, they are this.floor(from) and this.ceil(to). + * @see kodkod.util.ints.AbstractSparseSequence#iterator(int, int) + */ + public Iterator> iterator(int from, int to) { + return new HomogenousIterator(indices.iterator(from, to), value); + } + + /** + * Returns the number of entries in this sequence. + * @return #this.entries + * @see kodkod.util.ints.SparseSequence#size() + */ + public int size() { + return indices.size(); + } + + /** + * Removes all entries from this sequences. + * @ensures no this.entries' + * @see kodkod.util.ints.SparseSequence#clear() + */ + public void clear() { + indices.clear(); + } + + /** + * Puts the given value at the specified index. If the + * sequence already mapped the index to a value, the + * previous value is replaced with the new one and returned. + * + * @requires this.value = value + * @ensures this.indices' = this.indices + index + * @return this.entries[index] + * @throws IllegalArgumentException - this.value != value + * @see kodkod.util.ints.SparseSequence#put(int, Object) + */ + public V put(int index, V value) { + if (!equal(this.value, value)) + throw new IllegalArgumentException(); + return indices.add(index) ? null : value; + } + + /** + * Returns the value to which this sequence maps the given + * index. If the index is not mapped, null is returned. + * @return this.entries[index] + * @see kodkod.util.ints.SparseSequence#get(int) + */ + public V get(int index) { + return indices.contains(index) ? value : null; + } + + /** + * Removes the entry with the given index, if it exists, and + * returns the value previously stored at the index. If the + * sequence had no previous mapping for the index, null is returned. + * @ensures this.entries' = this.entries - index->E + * @return this.entries[index] + * @see kodkod.util.ints.SparseSequence#remove(int) + */ + public V remove(int index) { + return indices.remove(index) ? value : null; + } + + /** + * Returns true if this sparse sequence has an entry for the + * given index; otherwise returns false. + * @return some this.entries[index] + * @see kodkod.util.ints.SparseSequence#containsIndex(int) + */ + public boolean containsIndex(int index) { + return indices.contains(index); + } + + /** + * Returns true if this sequence has an entry with the given value; + * otherwise returns false. + * @return some this.indices && this.value = value + * @see kodkod.util.ints.SparseSequence#contains(java.lang.Object) + */ + public boolean contains(Object value) { + return !indices.isEmpty() && equal(this.value, value); + } + + /** + * Returns the entry with the smallest index. If the sequence + * is empty, returns null. + * @return {e: IndexedEntry | e.index = min(this.entries.E) && + * e.value = this.entries[e.index] } + * @see kodkod.util.ints.SparseSequence#first() + */ + public IndexedEntry first() { + return indices.isEmpty() ? null : view.setIndexView(indices.min()); + } + + /** + * Returns the entry with the largest index. If the sequence + * is empty, returns null. + * @return {e: IndexedEntry | e.index = max(this.entries.E) && + * e.value = this.entries[e.index] } + * @see kodkod.util.ints.SparseSequence#last() + */ + public IndexedEntry last() { + return indices.isEmpty() ? null : view.setIndexView(indices.max()); + } + + /** + * If an entry for the given index exists, it is returned. Otherwise, + * successor(index) is returned. + * @return this.containsIndex(index) => + * {e: IndexedEntry | e.index = index && e.value = this.entries[index] }, + * successor(index) + * @see kodkod.util.ints.SparseSequence#ceil(int) + */ + public IndexedEntry ceil(int index) { + if (indices.isEmpty() || index > indices.max()) + return null; + else + return view.setIndexView(indices.ceil(index)); + } + + /** + * If an entry for the given index exists, it is returned. Otherwise, + * predecessor(index) is returned. + * @return this.containsIndex(index) => + * {e: IndexedEntry | e.index = index && e.value = this.entries[index] }, + * predecessor(index) + * @see kodkod.util.ints.SparseSequence#floor(int) + */ + public IndexedEntry floor(int index) { + if (indices.isEmpty() || index < indices.min()) + return null; + else + return view.setIndexView(indices.floor(index)); + } + + /** + * Returns a copy of this sparse sequence. The copy is independent of this + * sequence. + * @return a copy of this sparse sequence. + * @see kodkod.util.ints.SparseSequence#clone() + */ + public HomogenousSequence clone() { + // ok to clone a final class using a copy constructor + return new HomogenousSequence(this); + } + + /** + * An iterator over a homogenous sequence. + * @author Emina Torlak + */ + private static final class HomogenousIterator extends EntryView implements Iterator> { + private final IntIterator iter; + + /** + * Constructs a wrapper for the given IntIterator and value + */ + HomogenousIterator(IntIterator iter, V value) { + super(Integer.MIN_VALUE, value); + this.iter = iter; + } + + public boolean hasNext() { + return iter.hasNext(); + } + + public IndexedEntry next() { + return setIndexView(iter.next()); + } + + public void remove() { + iter.remove(); + } + + } + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/ints/IndexedEntry.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/ints/IndexedEntry.java new file mode 100644 index 00000000..6da96761 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/ints/IndexedEntry.java @@ -0,0 +1,66 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.util.ints; + +/** + * An entry in a {@link SparseSequence sparse sequence}. + * + * @specfield index: int + * @specfield value: E + * + * @author Emina Torlak + */ +public interface IndexedEntry { + + /** + * Returns the index of this entry. + * @return this.index + */ + public abstract int index(); + + /** + * Returns the value stored in this entry. + * @return this.value + */ + public abstract E value(); + + /** + * Compares the specified object with this entry for equality. Returns true if + * the given object is also an indexed entry and the two entries + * have the same indeces and values. More formally, two entries e1 and e2 + * are equal if e1.index = e2.index && e1.value = e2.value. This ensures + * that the equals method works properly across different implementations of + * the IndexedEntry interface. + * @return o in IndexedEntry && o.index = this.index && o.value = this.value + */ + public abstract boolean equals(Object o); + + /** + * Returns the hash code value for this indexed entry. The hash code of an + * indexed entry e is defined to be: + * e.index ^ (e.value=null ? 0 : e.value.hashCode()). + * This ensures that e1.equals(e2) implies that e1.hashCode()==e2.hashCode() + * for any two IndexedEntries e1 and e2, as required by the general contract of + * Object.hashCode. + */ + public abstract int hashCode(); +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/ints/IntBitSet.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/ints/IntBitSet.java new file mode 100644 index 00000000..5bd5402b --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/ints/IntBitSet.java @@ -0,0 +1,527 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.util.ints; + +import java.util.Arrays; +import java.util.NoSuchElementException; + +/** + * An implementation of the IntSet interface based on a bit map. + * An IntBitSet can store only numbers in the half-open range + * [0..capacity) where capacity is a user-specified value. + * The implementation will allocated enough bits to explicitly represent all allowed + * integers; it performs better than a tree set when the stored integers + * are not clustered. + * @specfield capacity: [0..Integer.MAX_VALUE] + * @invariant all i: this.ints | 0 <= i < capacity + * @author Emina Torlak + */ +public final class IntBitSet extends AbstractIntSet implements Cloneable { + // implementation adapted from java.util.JumboEnumSet + private final int capacity; + /* + * Bit vector representation of this set. The ith bit of the jth + * element of this array represents the presence of universe[64*j +i] + * in this set. + */ + private long elements[]; + + // Redundant - maintained for performance + private int size; + + /** + * Constructs an empty IntBitSet that can store up + * to capacity elements. + * @ensures no this.ints' && this.capacity' = capacity + * @throws IllegalArgumentException - capacity < 0 + */ + public IntBitSet(int capacity) { + if (capacity < 0) throw new IllegalArgumentException("capacity < 0"); + this.capacity = capacity; + elements = new long[(capacity >>> 6) + 1]; + size = 0; + } + + /** + * Constructs an IntBitSet that can store up to capacity elements. + * The set is initialized to contain all integers i such that + * data[i>>>6] & (1L<>>6] & (1L<>>6)+1 > data.length + * @throws IllegalArgumentException - capacity is out of range + */ + public IntBitSet(int capacity, long[] data) { + if (capacity > (data.length<<6)) throw new IllegalArgumentException("capacity too large: " + capacity + ", max: " + (data.length<<6)); + this.capacity = capacity; + this.elements = data; + recalculateSize(); + +// System.out.println("capacity: " + capacity + ", max: " + max() + ", data.length: " + data.length); +// System.out.println(Arrays.toString(data)); + if (size > 0 && capacity <= max()) throw new IllegalArgumentException("capacity too small"); + } + + /** + * Returns the smallest element in this set. + * Throws a NoSuchElementException if this set is empty. + * @return min(this.ints) + * @throws java.util.NoSuchElementException - no this.ints + * @see kodkod.util.ints.IntSet#min() + */ + @Override + public int min() { + checkNonEmpty(); + int minWordIndex = 0; + while(elements[minWordIndex]==0) { minWordIndex++; } + return (minWordIndex << 6) + Long.numberOfTrailingZeros(elements[minWordIndex]); + } + + /** + * Returns the largest element in this set. + * Throws a NoSuchElementException if this set is empty. + * @return max(this.ints) + * @throws java.util.NoSuchElementException - no this.ints + * @see kodkod.util.ints.IntSet#max() + */ + @Override + public int max() { + checkNonEmpty(); + int maxWordIndex = elements.length-1; + while(elements[maxWordIndex]==0) { maxWordIndex--; } + return (maxWordIndex << 6) + 63 - Long.numberOfLeadingZeros(elements[maxWordIndex]); + } + + /** + * {@inheritDoc} + * @see kodkod.util.ints.IntSet#ceil(int) + */ + public int ceil(int i) { + if (i <= 0) + return min(); + int wordIndex = wordIndex(i); + long word = 0; + if (wordIndex < elements.length) { + word = (extendedMask(i) & elements[wordIndex]); + } + while(word==0 && wordIndex < elements.length-1) { + word = elements[++wordIndex]; + } + if (word==0) + throw new NoSuchElementException(); + else + return (wordIndex << 6) + Long.numberOfTrailingZeros(word); + } + + /** + * {@inheritDoc} + * @see kodkod.util.ints.IntSet#floor(int) + */ + public int floor(int i) { + if (i < 0) + throw new NoSuchElementException(); + int wordIndex = wordIndex(i); + long word = 0; + if (wordIndex < elements.length) { + word = ((~extendedMask(i+1)) & elements[wordIndex]); + } else { + wordIndex = elements.length-1; + word = elements[wordIndex]; + } + while(word==0 && wordIndex > 0) { + word = elements[--wordIndex]; + } + if (word==0) + throw new NoSuchElementException(); + else + return (wordIndex << 6) + 63 - Long.numberOfLeadingZeros(word); + } + + /** + * {@inheritDoc} + * @see kodkod.util.ints.IntSet#iterator() + */ + @Override + public IntIterator iterator() { + return new AscendingIterator(0,capacity); + } + + /** + * {@inheritDoc} + * @see kodkod.util.ints.IntSet#iterator(int, int) + */ + public IntIterator iterator(int from, int to) { + return from > to ? new DescendingIterator(from,to) : new AscendingIterator(from,to); + } + + /** + * {@inheritDoc} + * @see kodkod.util.ints.IntSet#size() + */ + public int size() { + return size; + } + + /** + * Returns the capacity of this int bit set + * @return this.capacity + */ + public int capacity() { return capacity; } + + /** + * Returns the index of the word that contains + * the bit that represents the integer i. + * @requires 0 <= i < this.capacity + */ + private final int wordIndex(int i) { + return i >>> 6; + } + + /** + * Returns a bit mask that has 1 in the position representing the + * given integer within its word (obtained by wordIndex(i)) + * @requires 0 <= i < this.capacity + */ + private final long bitMask(int i) { + return 1L << i; + } + + /** + * Returns a bit mask that has 1 at every index greater than + * or equal to the position representing the + * given integer within its word. + * @requires 0 <= i < this.capacity + */ + private final long extendedMask(int i) { + return -1L << i; + } + + /** + * @return i in [0..this.capacity) + */ + private final boolean allows(int i) { + return 0 <= i && i < capacity; + } + + /** + * Returns true if i is in this set. + * @return i in this.ints + * @see kodkod.util.ints.IntSet#contains(int) + */ + @Override + public boolean contains(int i) { + return allows(i) && (elements[wordIndex(i)] & (bitMask(i))) != 0; + } + + /** + * Adds the given integer to this set if not already present + * and returns true. Otherwise does nothing and returns false. + * @ensures this.ints' = this.ints + i + * @return i in this.ints' + * @throws IllegalArgumentException - i !in [0..this.capacity) + * @see kodkod.util.ints.IntSet#add(int) + */ + @Override + public boolean add(int i) { + if (!allows(i)) throw new IllegalArgumentException(i + " !in [0.." + capacity + ")"); + + final int wordIndex = wordIndex(i); + final long oldElements = elements[wordIndex]; + elements[wordIndex] |= bitMask(i); + if (elements[wordIndex] != oldElements) { + size++; + return true; + } + return false; + } + + /** + * Removes the given integer from this set if already present and + * returns true. Otherwise does nothing and returns false. + * @ensures this.ints' = this.ints - i + * @return i !in this.ints' + * @see kodkod.util.ints.IntSet#remove(int) + */ + @Override + public boolean remove(int i) { + if (allows(i)) { + final int wordIndex = wordIndex(i); + final long oldElements = elements[wordIndex]; + elements[wordIndex] &= ~bitMask(i); + if (elements[wordIndex] != oldElements) { + size--; + return true; + } + } + + return false; + } + + /** + * {@inheritDoc} + * @see kodkod.util.ints.IntCollection#isEmpty() + */ + @Override + public boolean isEmpty() { + return size==0; + } + + /** + * {@inheritDoc} + * @see kodkod.util.ints.IntSet#containsAll(kodkod.util.ints.IntCollection) + */ + @Override + public boolean containsAll(IntCollection other) { + if (other instanceof IntBitSet) { + final IntBitSet s = (IntBitSet) other; + if (isEmpty() || s.isEmpty()) return isEmpty() ? s.isEmpty():true; + if (size < s.size || max() < s.max()) return false; + final int minLength = StrictMath.min(elements.length, s.elements.length); + for(int wordIndex = 0; wordIndex < minLength; wordIndex++) { + if ((s.elements[wordIndex] & ~elements[wordIndex]) != 0) + return false; + } + return true; + } + return super.containsAll(other); + } + + /** + * Recalculates the size and returns true if the size has changed. + */ + private boolean recalculateSize() { + final int oldSize = size; + size = 0; + for(long elt: elements) { + size += Long.bitCount(elt); + } + return size!=oldSize; + } + + /** + * {@inheritDoc} + * @see kodkod.util.ints.IntSet#addAll(kodkod.util.ints.IntCollection) + */ + @Override + public boolean addAll(IntCollection other) { + if (other instanceof IntBitSet) { + final IntBitSet s = (IntBitSet) other; + if (s.isEmpty()) return false; + if (s.max() >= capacity) + throw new IllegalArgumentException(s.max()+" !in [0.." + capacity + ")"); + final int minLength = StrictMath.min(elements.length, s.elements.length); + for(int wordIndex = 0; wordIndex < minLength; wordIndex++) { + elements[wordIndex] |= s.elements[wordIndex]; + } + return recalculateSize(); + } + return super.addAll(other); + } + + /** + * {@inheritDoc} + * @see kodkod.util.ints.IntSet#retainAll(kodkod.util.ints.IntCollection) + */ + @Override + public boolean retainAll(IntCollection other) { + if (other instanceof IntBitSet) { + final IntBitSet s = (IntBitSet) other; + final int minLength = StrictMath.min(elements.length, s.elements.length); + int wordIndex = 0; + for(; wordIndex < minLength; wordIndex++) { + elements[wordIndex] &= s.elements[wordIndex]; + } + for(; wordIndex < elements.length; wordIndex++) { + elements[wordIndex] = 0; + } + return recalculateSize(); + } + return super.retainAll(other); + } + + /** + * {@inheritDoc} + * @see kodkod.util.ints.IntSet#removeAll(kodkod.util.ints.IntCollection) + */ + @Override + public boolean removeAll(IntCollection other) { + if (other instanceof IntBitSet) { + final IntBitSet s = (IntBitSet) other; + final int minLength = StrictMath.min(elements.length, s.elements.length); + for(int wordIndex = 0; wordIndex < minLength; wordIndex++) { + elements[wordIndex] &= ~s.elements[wordIndex]; + } + return recalculateSize(); + } + return super.removeAll(other); + } + + /** + * Removes all elements from this set. + * @ensures no this.ints' + * @see kodkod.util.ints.IntCollection#clear() + */ + @Override + public void clear() { + Arrays.fill(elements, 0); + size = 0; + } + + /** + * Returns a copy of this int bit set. The copy is independent of this + * IntSet. + * @return a copy of this IntSet. + * @see kodkod.util.ints.IntSet#clone() + */ + @Override + public IntBitSet clone() { + try { + final IntBitSet ret = (IntBitSet) super.clone(); + ret.elements = (long[]) this.elements.clone(); + return ret; + } catch (CloneNotSupportedException e) { + throw new InternalError(); // unreachable code + } + + } + + /** + * Stores common fields and methods for the ascending and descending iterators. + */ + private abstract class AbstractIterator implements IntIterator { + long unseen; + int unseenIndex, lastReturned; + + public void remove() { + if (lastReturned < 0) + throw new IllegalStateException(); + elements[wordIndex(lastReturned)] -= bitMask(lastReturned); + size--; + lastReturned = -1; + } + } + + /** + * Implementation of an ascending iterator over (a subset of) this set. + */ + private final class AscendingIterator extends AbstractIterator { + private final long maxMask; + private final int maxIndex; + + /** + * Constructs an ascending iterator that returns elements between + * from and to. + * @requires from <= to + */ + AscendingIterator(int from, int to) { + if (from >= capacity || to < 0) { + unseenIndex = maxIndex = elements.length; + unseen = maxMask = 0L; + } else { + if (to >= capacity) { + maxIndex = elements.length - 1; + maxMask = -1L; + } else { + maxIndex = wordIndex(to); + maxMask = (bitMask(to)==Long.MIN_VALUE ? -1L : ~extendedMask(to+1)); + } + if (from < 0) { + unseenIndex = 0; + unseen = elements[0]; + } else { + unseenIndex = wordIndex(from); + unseen = elements[unseenIndex] & extendedMask(from); + } + + } + lastReturned = -1; + } + + public boolean hasNext() { + while (unseen == 0 && unseenIndex < elements.length - 1) + unseen = elements[++unseenIndex]; + return (unseenIndex < maxIndex && unseen != 0) || + (unseenIndex == maxIndex && (unseen & maxMask) != 0); + } + + public int next() { + if (!hasNext()) throw new NoSuchElementException(); + final long lastReturnedMask = Long.lowestOneBit(unseen); + unseen -= lastReturnedMask; + lastReturned = (unseenIndex << 6) + Long.numberOfTrailingZeros(lastReturnedMask); + return lastReturned; + } + + } + + /** + * Implementation of a descending iterator over (a subset of) this set. + */ + private final class DescendingIterator extends AbstractIterator { + private final long minMask; + private final int minIndex; + + /** + * Constructs a descending iterator that returns elements between + * from and to. + * @requires from >= to + */ + DescendingIterator(int from, int to) { + if (to >= capacity || from < 0) { + unseenIndex = minIndex = 0; + unseen = minMask = 0L; + } else { + if (from < capacity) { + unseenIndex = wordIndex(from); + unseen = elements[unseenIndex] & + (bitMask(from)==Long.MIN_VALUE ? -1L : ~extendedMask(from+1)); + } else { + unseenIndex = elements.length-1; + unseen = elements[unseenIndex]; + } + if (to < 0) { + minIndex = 0 ; + minMask = -1L; + } else { + minIndex = wordIndex(to); + minMask = extendedMask(to); + } + } + lastReturned = -1; + } + + public boolean hasNext() { + while (unseen == 0 && unseenIndex > 0) + unseen = elements[--unseenIndex]; + return (unseenIndex > minIndex && unseen != 0) || + (unseenIndex == minIndex && (unseen & minMask) != 0); + } + + public int next() { + if (!hasNext()) throw new NoSuchElementException(); + final long lastReturnedMask = Long.highestOneBit(unseen); + unseen -= lastReturnedMask; + lastReturned = (unseenIndex << 6) + 63 - Long.numberOfLeadingZeros(lastReturnedMask); + return lastReturned; + } + } + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/ints/IntCollection.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/ints/IntCollection.java new file mode 100644 index 00000000..8e6a90ad --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/ints/IntCollection.java @@ -0,0 +1,140 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.util.ints; + +/** + * The root interface in the int collection hierarchy. A collection + * represents a group of integers, known as its elements. Some collections + * allow duplicate elements and others do not. Some are ordered and others unordered. + * Some allow all integers and others only integers in a certain range. + * + * @author Emina Torlak + */ +public interface IntCollection { + + /** + * Returns the number of elements in this collection. + * @return number of elements in this collection. + */ + public abstract int size(); + + /** + * Returns true if this collection has no elements; + * otherwise returns false. + * @return true if this collection has no elements, + * false otherwise. + */ + public abstract boolean isEmpty(); + + /** + * Returns an iterator over the elements in this collection. + * There are no guarantees concerning the order in which the elements + * are returned (unless this collection is an instance of some class that provides a guarantee). + * @return an iterator over the elements in this collection + */ + public abstract IntIterator iterator(); + + /** + * Returns true if i is an element in this collection. + * @return true if i is an element in this collection. + */ + public abstract boolean contains(int i); + + /** + * Ensures that this collection contains the given integer, and returns true + * if this collection has changed as a result of the call. + * @return true if this collection has changed as a result of the call + * @throws UnsupportedOperationException - this is an unmodifiable collection + * @throws IllegalArgumentException - some aspect of the element prevents it + * from being added to this collection. + */ + public abstract boolean add(int i); + + /** + * Removes a single instance of the given integer from this collection, + * and returns true if this collection has changed as a result of the call. + * @return true if this collection has changed as a result of the call + * @throws UnsupportedOperationException - this is an unmodifiable collection + */ + public abstract boolean remove(int i); + + /** + * Returns true if this collection contains all of the elements in the specified collection. + * @return true if this collection contains all of the elements in the specified collection. + * @throws NullPointerException - c = null + */ + public abstract boolean containsAll(IntCollection c); + + /** + * Adds all of the elements in the specified collection to this collection. + * Returns true if this collection has changed as a result of the call. + * @return true if this collection has changed as a result of the call + * @throws NullPointerException - c = null + * @throws UnsupportedOperationException - this is an unmodifiable collection + * @throws IllegalArgumentException - some aspect of an element of the specified + * collection prevents it from being added to this collection. + */ + public abstract boolean addAll(IntCollection c); + + /** + * Removes all of this collection's elements that are also contained in the specified + * collection. After this call returns, this collection will contain no elements in + * common with the specified collection. Returns true if this collection has changed as a result of the call. + * @return true if this collection has changed as a result of the call + * @throws NullPointerException - c = null + * @throws UnsupportedOperationException - this is an unmodifiable collection + */ + public abstract boolean removeAll(IntCollection c); + + /** + * Retains only the elements in this collection that are contained in the specified + * collection. In other words, removes from this collection all of its elements that + * are not contained in the specified collection. Returns true if this collection has changed as a result of the call. + * @return rue if this collection has changed as a result of the call + * @throws NullPointerException - c = null + * @throws UnsupportedOperationException - this is an unmodifiable collection + */ + public abstract boolean retainAll(IntCollection c); + + /** + * Removes all elements from this collection. + * @throws UnsupportedOperationException - this is an unmodifiable collection + */ + public abstract void clear(); + + /** + * Returns an array containing all of the elements in this collection. + * @return an array containing all of the elements in this collection. + */ + public abstract int[] toArray(); + + /** + * Copies the elements of this collection into the specified array, provided + * that it is large enough, and returns it. If the array is not large enough, + * the effect of this method is the same as calling {@linkplain #toArray()}. + * @return the given array, filled with the elements from this collection, if + * the it is large enough; otherwise a new array containing all of the elements + * in this collection. + * @throws NullPointerException - array = null + */ + public abstract int[] toArray(int[] array); +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/ints/IntIterator.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/ints/IntIterator.java new file mode 100644 index 00000000..49895e3b --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/ints/IntIterator.java @@ -0,0 +1,55 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.util.ints; + + +/** + * An iterator over integer primitives. + * + * @author Emina Torlak + */ +public interface IntIterator { + + /** + * Returns true if this iteration has more elements. + * @return true if this iteration has more elements. + */ + public abstract boolean hasNext(); + + /** + * Returns the next int in the iteration, if any. + * Otherwise throws a NoSuchElementException. + * @return the next element in the iteration + * @throws java.util.NoSuchElementException - the iteration is empty. + */ + public abstract int next(); + + /** + * Removes the last returned element from the underlying collection. + * @ensures removes the last returned element from the underlying collection. + * @throws UnsupportedOperationException - removal is not supported + * @throws IllegalStateException - next() has not been called yet or remove() + * has already been called since the last call to next(). + */ + public abstract void remove(); + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/ints/IntRange.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/ints/IntRange.java new file mode 100644 index 00000000..57fa4401 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/ints/IntRange.java @@ -0,0 +1,161 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.util.ints; + + +/** + * Represents a range of integers, [min..max]. + * + * @specfield min: int + * @specfield max: int + * @invariant min <= max + * @author Emina Torlak + */ +public abstract class IntRange { + + private IntRange() { } + + /** + * Returns the left endpoint of this range. + * @return this.min + */ + public abstract int min(); + + /** + * Returns the right endpoint of this range. + * @return this.max + */ + public abstract int max(); + + /** + * Returns the number of element in this range. + * @return this.max - this.min + 1 + */ + public int size() { + return max() - min() + 1; + } + + /** + * Returns true if the given integer is within + * this range; otherwise returns false. + * @return i in [min..max] + */ + public boolean contains(int i) { + return i >= min() && i <= max(); + } + + /** + * Returns true if this range contains the + * given range. + * @return this.min <= range.min <= range.max <= this.max + * @throws NullPointerException - range = null + */ + public boolean contains(IntRange range) { + return min() <= range.min() && range.max() <= max(); + } + + /** + * Returns true if this and the given range intersect. + * @return some i: int | this.contains(i) && range.contains(i) + * @throws NullPointerException - range = null + */ + public boolean intersects(IntRange range) { + return contains(range.min()) || contains(range.max()); + } + + + /** + * Returns true if o is an int range with the same endpoints as this. + * @return o in IntRange && o.min==this.min && o.max==this.max + */ + public boolean equals(Object o) { + if (o instanceof IntRange) { + final IntRange r = (IntRange) o; + return min()==r.min() && max()==r.max(); + } + return false; + } + + /** + * Returns the hash code for this int range. The implementation + * is guaranteed to obey the Object contract. + * @return the hashcode for this intrange + */ + public int hashCode() { + return min()==max() ? min() : min() ^ max(); + } + + public String toString() { + return "[" + min() + ".." + max() + "]"; + } + + /** + * Represents an int range that consists of a single point. + * + * @invariant min==max + * @author Emina Torlak + */ + static final class OnePointRange extends IntRange { + private final int min; + + /** + * Constructs a new one point range. + */ + OnePointRange(int min) { + this.min = min; + } + + @Override + public final int min() { return min; } + + @Override + public final int max() { return min; } + } + + /** + * Represents an int range with two distinct end points. + * + * @invariant min < max + * @author Emina Torlak + */ + static final class TwoPointRange extends IntRange { + private final int min, max; + + /** + * Constructs a new two point range. + * @requires min < max + */ + TwoPointRange(int min, int max) { + assert min < max; + this.min = min; + this.max = max; + } + + @Override + public final int min() { return min; } + + @Override + public final int max() { return max; } + } + + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/ints/IntSet.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/ints/IntSet.java new file mode 100644 index 00000000..2b735221 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/ints/IntSet.java @@ -0,0 +1,217 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.util.ints; + +import java.util.NoSuchElementException; + +/** + * An ordered set of integers. + * + * @specfield ints: set int + * @author Emina Torlak + */ +public interface IntSet extends IntCollection, Cloneable { + + /** + * Returns the cardinality of this set. + * @return #this.ints + */ + public abstract int size(); + + /** + * Returns true if this set has no elements; + * otherwise returns false. + * @return no this.ints + */ + public abstract boolean isEmpty(); + + /** + * Returns true if i is in this set. + * @return i in this.ints + */ + public abstract boolean contains(int i); + + /** + * Returns the smallest element in this set. + * Throws a NoSuchElementException if this set is empty. + * @return min(this.ints) + * @throws java.util.NoSuchElementException - no this.ints + */ + public abstract int min(); + + /** + * Returns the largest element in this set. + * Throws a NoSuchElementException if this set is empty. + * @return max(this.ints) + * @throws java.util.NoSuchElementException - no this.ints + */ + public abstract int max(); + + /** + * Returns the largest element in this set that + * is smaller than or equal to i. If this is emtpy or i is less than this.min(), + * NoSuchElementException is thrown. + * @return {j: this.ints | j <= i && no k: this.ints - j | k > j && k <= i} + * @throws NoSuchElementException - no this.ints || i < this.min() + */ + public abstract int floor(int i); + + /** + * Returns the smallest element in this set that + * is greater than or equal to i. If this is emtpy or i is greater than this.max(), + * NoSuchElementException is thrown. + * @return {j: this.ints | j >= i && no k: this.ints - j | k < j && k >= i} + * @throws NoSuchElementException - no this.ints || i > this.max() + */ + public abstract int ceil(int i); + + /** + * Returns an iterator over the integers in this set, + * in the ascending element order. + * @return an IntIterator over the integers in this set. + */ + public abstract IntIterator iterator(); + + /** + * Returns an iterator over the elements of this set that + * are in the closed range [from..to]. If from < to, + * the elements are returned in the ascending order. + * Otherwise, they are returned in the descending order. + * @return an iterator over the elements in this set + * that are in the closed range [from..to]. + */ + public abstract IntIterator iterator(int from, int to); + + /** + * Adds the given integer to this set if not already present + * and returns true. Otherwise does nothing and returns false. + * @ensures this.ints' = this.ints + i + * @return i in this.ints' + * @throws IllegalArgumentException - this is a bounded set + * and i is out of bounds + */ + public abstract boolean add(int i); + + /** + * Removes the given integer from this set if already present and + * returns true. Otherwise does nothing and returns false. + * @ensures this.ints' = this.ints - i + * @return i !in this.ints' + */ + public abstract boolean remove(int i); + + /** + * Returns true if the elements of c are a subset of this set. + * @return { i: int | c.contains(i) } in this.ints + * @throws NullPointerException - c = null + */ + public abstract boolean containsAll(IntCollection c); + + /** + * Adds all of the elements in the specified collection to this set + * if they're not already present. + * @ensures this.ints' = this.ints + { i: int | c.contains(i) } + * @return this.ints' != this.ints + * @throws NullPointerException - c = null + * @throws UnsupportedOperationException - this is an unmodifiable set + * @throws IllegalArgumentException - some aspect of an element of the specified + * collection prevents it from being added to this collection. + */ + public abstract boolean addAll(IntCollection c); + + /** + * Removes from this set all of its elements that are contained in the + * specified set. + * @ensures this.ints' = this.ints - { i: int | c.contains(i) } + * @return this.ints' != this.ints + * @throws NullPointerException - s = null + * @throws UnsupportedOperationException - this is an unmodifiable set + */ + public abstract boolean removeAll(IntCollection c); + + /** + * Retains only the elements in this set that are contained in the + * specified set. + * @ensures this.ints' = this.ints & { i: int | c.contains(i) } + * @return this.ints' != this.ints + * @throws NullPointerException - s = null + * @throws UnsupportedOperationException - this is an unmodifiable set + */ + public abstract boolean retainAll(IntCollection c); + + /** + * Removes all elements from this set. + * @ensures no this.ints' + */ + public abstract void clear(); + + /** + * Returns a copy of this IntSet. The copy is independent of this + * IntSet unless this is a singleton or an immutable set, in which case + * clone() may return this. An implementing class that does not support + * cloning may throw a CloneNotSupportedException. + * @return a copy of this IntSet. + * @throws CloneNotSupportedException - this is not cloneable + */ + public abstract IntSet clone() throws CloneNotSupportedException; + + /** + * Returns an array containing all of the elements in this set in the + * ascending order. + * + * @return an array containing all of the elements in this set in the + * ascending order. + */ + public abstract int[] toArray(); + + /** + * Copies the elements of this set into the specified array, in the ascending + * order, provided that the array is large enough. If the array is not large enough, + * the effect of this method is the same as calling {@linkplain #toArray()}. + * @ensures array.length>=this.size() => all i: [0..this.size()) | array'[i] in this.ints and #{e: this.ints | e < array'[i]} = i + * @return array.length>=this.size() => array' else this.toArray() + * @throws NullPointerException - array = null + */ + public abstract int[] toArray(int[] array); + + /** + * Compares the specified object with this set for equality. + * Returns true if the specified object is also an IntSet, + * the two sets have the same size, and every member of the + * specified set is contained in this set (or equivalently, + * every member of this set is contained in the specified set). + * This definition ensures that the equals method works properly + * across different implementations of the IntSet interface. + * @return o instanceof IntSet and o.size() = this.size() and this.containsAll(o) + */ + public abstract boolean equals(Object o); + + /** + * Returns the hash code value for this set. The hash code of a set is + * defined to be the {@link Ints#superFastHash(int[])} of the elements in the set, + * taken in the ascending order of values. + * This ensures that s1.equals(s2) implies that s1.hashCode()==s2.hashCode() + * for any two IntSets s1 and s2, as required by the general contract of the Object.hashCode method. + * @return Ints.superFastHash(this.toArray()) + */ + public abstract int hashCode(); +} \ No newline at end of file diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/ints/IntTree.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/ints/IntTree.java new file mode 100644 index 00000000..0472ba99 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/ints/IntTree.java @@ -0,0 +1,632 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package kodkod.util.ints; + +/** + * A tree with integer keys. + * + * @specfield root: lone N + * @specfield nodes: root.*(left + right) + * @author Emina Torlak + */ +final class IntTree> implements Cloneable { + private static final boolean BLACK = true, RED = false; + + private N root; + + /** + * Creates an empty IntTree. + * + * @ensures no this.root' + */ + IntTree() { + root = null; + } + + /** + * Discards all elements from this tree. + * + * @ensures no this.root' + **/ + final void clear() { + root = null; + } + + /** + * Returns the node with the given key, or null no such node exists. + * + * @return this.nodes & key.index + */ + final N search(int k) { + N node = root; + while (node != null) { + if (node.key == k) + break; + else if (node.key > k) + node = node.left; + else + node = node.right; + } + return node; + } + + /** + * Returns the node whose key is the ceiling of k in this tree, or null if no such node + * exists. + * + * @return {n: this.nodes | n.key >= k && no n': this.nodes - n | n'.key >= k && n'.key < n.key } + */ + final N searchGTE(int k) { + if (root == null) + return null; + N c = root; + while (true) { + if (c.key == k) { + return c; + } else if (c.key > k) { + if (c.left != null) + c = c.left; + else + return c; + } else { + if (c.right != null) + c = c.right; + else + return successor(c); + } + } + } + + /** + * Returns the node whose key is the floor of k in this tree, or null if no such node + * exists. + * + * @return {n: this.nodes | n.key <= k && no n': this.nodes - n | n'.key <= k && n'.key > n.key } + */ + final N searchLTE(int k) { + if (root == null) + return null; + N f = root; + while (true) { + if (f.key == k) + return f; + else if (f.key > k) { + if (f.left != null) + f = f.left; + else + return predecessor(f); + } else { + if (f.right != null) + f = f.right; + else + return f; + } + } + } + + /** + * Implementation of the tree-predecessor algorithm from CLR. Returns the given node's + * predecessor, if it exists. Otherwise returns null. + * + * @return the given node's predecessor + * @throws NullPointerException - node = null + */ + final N predecessor(N node) { + if (node.left != null) { + return max(node.left); + } else { + N n = node; + N ancestor = n.parent; + while (ancestor != null && n == ancestor.left) { + n = ancestor; + ancestor = ancestor.parent; + } + return ancestor; + } + } + + /** + * Implementation of the tree-successor algorithm from CLR. Returns the given node's successor, if + * it exists. Otherwise returns null. + * + * @return the given node's successor + * @throws NullPointerException - node = null + */ + final N successor(N node) { + if (node.right != null) { + return min(node.right); + } else { + N n = node; + N ancestor = n.parent; + while (ancestor != null && n == ancestor.right) { + n = ancestor; + ancestor = ancestor.parent; + } + return ancestor; + } + } + + /** + * Returns the node with the smallest key. + * + * @return key.(min(this.nodes.key)) + */ + final N min() { + return min(root); + } + + /** + * Returns the node with the largest key. + * + * @return key.(max(this.nodes.key)) + */ + final N max() { + return max(root); + } + + /** + * Returns the leftmost node in the subtree rooted at start. The behavior of this method is + * unspecified if the given node is not in this tree. + * + * @requires node in this.nodes + * @return {n: start.*left | no n.left } + */ + private final N min(N start) { + if (start != null) { + while (start.left != null) { + start = start.left; + } + } + return start; + } + + /** + * Returns the rightmost in the subtree rooted at start. The behavior of this method is + * unspecified if the given node is not in this tree. + * + * @requires node in this.nodes + * @return {n: start.*left | no n.right } + */ + private final N max(N start) { + if (start != null) { + while (start.right != null) { + start = start.right; + } + } + return start; + } + + /** + * Replaces the old node, o, with the given new node, n, in this tree. + * + * @requires no n.(left + right + parent) + * @requires o = o.parent.left => n.key < o.parent.key + * @requires o = o.parent.right => n.key > o.parent.key + * @requires some o.left => n.key > o.left.key + * @requires some o.right => n.key < o.right.key + * @ensures this.nodes' = this.nodes - o + n + * @ensures o.parent' = o.left' = o.right' = null + */ + @SuppressWarnings("unchecked") + final void replace(N o, N n) { + n.color = o.color; + n.parent = o.parent; + n.left = o.left; + n.right = o.right; + if (o.left != null) { + o.left.parent = n; + } + if (o.right != null) { + o.right.parent = n; + } + if (o.parent == null) { + root = n; + } else if (o == o.parent.left) { + o.parent.left = n; + } else { + o.parent.right = n; + } + o.parent = o.left = o.right = null; + } + + private final N parentOf(N n) { + return n == null ? null : n.parent; + } + + private final N leftOf(N n) { + return n == null ? null : n.left; + } + + private final N rightOf(N n) { + return n == null ? null : n.right; + } + + private final boolean colorOf(N n) { + return n == null ? BLACK : n.color; + } + + private final void setColor(N n, boolean color) { + if (n != null) + n.color = color; + } + + /** + * Implementation of the CLR insertion algorithm. + * + * @requires no z.key & this.nodes.key + * @ensures this.nodes' = this.nodes + z + */ + @SuppressWarnings("unchecked") + final void insert(N z) { + N y = null; + for (N x = root; x != null;) { + y = x; + if (x.key > z.key) + x = x.left; + else + x = x.right; + } + + z.parent = y; + z.left = z.right = null; + if (y == null) { + root = z; + } else { + z.color = RED; + if (y.key > z.key) { + y.left = z; + } else { + y.right = z; + } + + insertFixUp(z); + } + } + + /** + * A slightly modified implementation of the CLR deletion algorithm. + * + * @requires z in this.nodes + * @ensures this.nodes' = this.nodes - z + */ + @SuppressWarnings("unchecked") + final void delete(N z) { + N y = (z.left == null || z.right == null ? z : successor(z)); + N x = (y.left != null ? y.left : y.right); + + N yparent = y.parent; + final boolean yleft = (y == leftOf(y.parent)); + final boolean ycolor = y.color; + + if (x != null) { + x.parent = yparent; + } + + if (yparent == null) { + root = x; + } else if (yleft) { + yparent.left = x; + } else { + yparent.right = x; + } + + if (y != z) { + replace(z, y); + } + + if (ycolor == BLACK) { + if (x != null) { + deleteFixUp(x); + } else if (yparent != null) { // z is not the only node + + if (z == yparent) + yparent = y; // y, z's successor, is z's right child + z.color = BLACK; + z.left = z.right = null; + z.parent = yparent; + if (yleft) { + yparent.left = z; + } else { + yparent.right = z; + } + + deleteFixUp(z); + if (z == z.parent.left) { + z.parent.left = null; + } else { + z.parent.right = null; + } + } + } + + z.left = z.right = z.parent = null; // cut z out of the tree by nulling out its pointers + } + + /** + * {@inheritDoc} + * + * @see java.lang.Object#clone() + * @throws CloneNotSupportedException - nodes contained in this tree are not cloneable + */ + @SuppressWarnings("unchecked") + protected IntTree clone() throws CloneNotSupportedException { + final IntTree ret = (IntTree) super.clone(); + ret.root = clone(root, null); + return ret; + } + + /** + * Recursively clones the given node. + */ + @SuppressWarnings("unchecked") + private N clone(N n, N parent) throws CloneNotSupportedException { + if (n == null) + return null; + N clone = (N) n.clone(); + clone.parent = parent; + clone.left = clone(n.left, clone); + clone.right = clone(n.right, clone); + return clone; + } + + /*---------balancing operations (CLR, pp.278-289)---------*/ + /** + * From CLR. + */ + @SuppressWarnings("unchecked") + private void insertFixUp(N z) { + while (z != null && z != root && z.parent.color == RED) { + if (parentOf(z) == leftOf(parentOf(parentOf(z)))) { + N y = rightOf(parentOf(parentOf(z))); + if (colorOf(y) == RED) { + setColor(parentOf(z), BLACK); + setColor(y, BLACK); + setColor(parentOf(parentOf(z)), RED); + z = parentOf(parentOf(z)); + } else { + if (z == rightOf(parentOf(z))) { + z = parentOf(z); + rotateLeft(z); + } + setColor(parentOf(z), BLACK); + setColor(parentOf(parentOf(z)), RED); + if (parentOf(parentOf(z)) != null) + rotateRight(parentOf(parentOf(z))); + } + } else { + N y = leftOf(parentOf(parentOf(z))); + if (colorOf(y) == RED) { + setColor(parentOf(z), BLACK); + setColor(y, BLACK); + setColor(parentOf(parentOf(z)), RED); + z = parentOf(parentOf(z)); + } else { + if (z == leftOf(parentOf(z))) { + z = parentOf(z); + rotateRight(z); + } + setColor(parentOf(z), BLACK); + setColor(parentOf(parentOf(z)), RED); + if (parentOf(parentOf(z)) != null) + rotateLeft(parentOf(parentOf(z))); + } + } + } + root.color = BLACK; + } + + /** + * From CLR. + */ + @SuppressWarnings("unchecked") + private void deleteFixUp(N x) { + while (x != root && colorOf(x) == BLACK) { + if (x == leftOf(parentOf(x))) { + N sib = rightOf(parentOf(x)); + + if (colorOf(sib) == RED) { + setColor(sib, BLACK); + setColor(parentOf(x), RED); + rotateLeft(parentOf(x)); + sib = rightOf(parentOf(x)); + } + + if (colorOf(leftOf(sib)) == BLACK && colorOf(rightOf(sib)) == BLACK) { + setColor(sib, RED); + x = parentOf(x); + } else { + if (colorOf(rightOf(sib)) == BLACK) { + setColor(leftOf(sib), BLACK); + setColor(sib, RED); + rotateRight(sib); + sib = rightOf(parentOf(x)); + } + setColor(sib, colorOf(parentOf(x))); + setColor(parentOf(x), BLACK); + setColor(rightOf(sib), BLACK); + rotateLeft(parentOf(x)); + x = root; + } + } else { // symmetric + N sib = leftOf(parentOf(x)); + + if (colorOf(sib) == RED) { + setColor(sib, BLACK); + setColor(parentOf(x), RED); + rotateRight(parentOf(x)); + sib = leftOf(parentOf(x)); + } + + if (colorOf(rightOf(sib)) == BLACK && colorOf(leftOf(sib)) == BLACK) { + setColor(sib, RED); + x = parentOf(x); + } else { + if (colorOf(leftOf(sib)) == BLACK) { + setColor(rightOf(sib), BLACK); + setColor(sib, RED); + rotateLeft(sib); + sib = leftOf(parentOf(x)); + } + setColor(sib, colorOf(parentOf(x))); + setColor(parentOf(x), BLACK); + setColor(leftOf(sib), BLACK); + rotateRight(parentOf(x)); + x = root; + } + } + } + + setColor(x, BLACK); + + } + + /** + * From CLR. + */ + @SuppressWarnings("unchecked") + private void rotateLeft(N x) { + N y = x.right; + x.right = y.left; + if (y.left != null) + y.left.parent = x; + y.parent = x.parent; + if (x.parent == null) + root = y; + else if (x.parent.left == x) + x.parent.left = y; + else + x.parent.right = y; + y.left = x; + x.parent = y; + } + + /** + * From CLR. + */ + @SuppressWarnings("unchecked") + private void rotateRight(N x) { + N y = x.left; + x.left = y.right; + if (y.right != null) + y.right.parent = x; + y.parent = x.parent; + if (x.parent == null) + root = y; + else if (x.parent.right == x) + x.parent.right = y; + else + x.parent.left = y; + y.right = x; + x.parent = y; + } + + /** + * @see java.lang.Object#toString() + */ + public String toString() { + return root.toString(); + } + + /** + * A node in an int tree. Subclasses need to implement the clone method iff IntTree.clone will be + * called on the tree containing the nodes. + * + * @specfield key: int + * @specfield parent: lone N + * @specfield left: lone N + * @specfield right: lone N + * @author Emina Torlak + */ + abstract static class Node> implements Cloneable { + N parent; + N left; + N right; + boolean color; + /** + * Subclasses are required to maintain the following invariant: + * + * @invariant this = this.parent.left => this.key < this.parent.key && this = this.parent.right + * => this.key > this.parent.key && some this.left => this.key > this.left.key && + * some this.right => this.key < this.right.key + */ + protected int key; + + /** + * Constructs an empty node with the given key. + * + * @ensures no this.(parent' + left' + right') && this.key' = key + */ + Node(int key) { + this.parent = this.left = this.right = null; + this.color = BLACK; + this.key = key; + } + + /** + * Returns the left child of this node. + * + * @return this.left + */ + final N left() { + return left; + } + + /** + * Returns the right child of this node. + * + * @return this.right + */ + final N right() { + return right; + } + + /** + * Return the parent of this node. + * + * @return this.parent + */ + final N parent() { + return parent; + } + + /** + * Clones this node. Subclasses must override this method (and call super.clone()) in order for + * IntTree.clone() to function properly. + * + * @throws CloneNotSupportedException + * @see java.lang.Object#clone() + */ + @SuppressWarnings("unchecked") + protected Node clone() throws CloneNotSupportedException { + Node ret = (Node) super.clone(); + ret.parent = ret.left = ret.right = null; + return ret; + } + + /** + * @see java.lang.Object#toString() + */ + public String toString() { + return "[" + key + " " + (color ? "b" : "r") + " " + (left == this ? key : left) + " " + + (right == this ? key : right) + "]"; + + } + } + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/ints/IntTreeSet.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/ints/IntTreeSet.java new file mode 100644 index 00000000..b6e7af99 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/ints/IntTreeSet.java @@ -0,0 +1,410 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.util.ints; + +import java.util.NoSuchElementException; + +/** + * An implementation of the IntTreeSet interface based + * on a balanced binary search tree. + * + * @specfield ints: set int + * @author Emina Torlak + */ +public final class IntTreeSet extends AbstractIntSet implements Cloneable { + /* The endpoints of the ranges in the tree do not touch, and they are + * sorted by their right endpoints. + * @invariant all n: tree.nodes | n.max = n.key && n.min <= n.max && + * all n': tree.nodes - n | n'.max < n.min - 1 || n'.min > n.max + 1 + */ + private final IntTree tree; + private int size; + + /** + * Constructs an empty int set. + * @ensures no this.ints' + */ + public IntTreeSet() { + tree = new IntTree(); + size = 0; + } + + /** + * Constructs a new int set containing the elements + * in the specified set. + * @ensures this.ints' = s.ints + * @throws NullPointerException - s = null + */ + public IntTreeSet(IntSet s) { + this(); + addAll(s); + } + + /** + * Copy constructor. + * @ensures constructs a deep copy of the original set. + */ + @SuppressWarnings("unchecked") + private IntTreeSet(IntTreeSet original) { + this.size = original.size; + try { + this.tree = original.tree.clone(); + } catch (CloneNotSupportedException e) { + throw new InternalError(); // unreachable code + } + } + +// public String toString() { +// for(Range next = tree.min(); next != null; next = tree.successor(next) ) { +// System.out.print("[" + next.min + " .. " + next.key+ "] "); +// } +// System.out.println(""); +// return ""; +// } + + /** + * {@inheritDoc} + * @see kodkod.util.ints.IntSet#iterator(int,int) + */ + public IntIterator iterator(int from, int to) { + return from <= to ? new AscendingIterator(from, to) : new DescendingIterator(from, to); + } + + /** + * {@inheritDoc} + * @see kodkod.util.ints.IntSet#size() + */ + public int size() { + return size; + } + + /** + * Returns true if i is in this set. + * @return i in this.ints + * @see kodkod.util.ints.IntSet#contains(int) + */ + @Override + public boolean contains(int i) { + final Range r = tree.searchGTE(i); + return r != null && r.min <= i; + } + + /** + * Returns the smallest element in this set. + * Throws a NoSuchElementException if this set is empty. + * @return min(this.ints) + * @throws java.util.NoSuchElementException - no this.ints + * @see kodkod.util.ints.IntSet#min() + */ + @Override + public int min() { + checkNonEmpty(); + return tree.min().min; + } + + /** + * Returns the largest element in this set. + * Throws a NoSuchElementException if this set is empty. + * @return max(this.ints) + * @throws java.util.NoSuchElementException - no this.ints + * @see kodkod.util.ints.IntSet#max() + */ + @Override + public int max() { + checkNonEmpty(); + return tree.max().key; + } + + /** + * {@inheritDoc} + * @see kodkod.util.ints.IntSet#floor(int) + */ + public int floor(int i) { + checkNonEmpty(); + Range r = tree.searchGTE(i); + if (r==null || r.min > i) { + r = tree.searchLTE(i); + return r == null ? null : r.key; + } else + return i; + } + + /** + * {@inheritDoc} + * @see kodkod.util.ints.IntSet#ceil(int) + */ + public int ceil(int i) { + checkNonEmpty(); + final Range r = tree.searchGTE(i); + return r == null ? null : StrictMath.max(i, r.min); + } + + /** + * Adds the given integer to this set if not already present + * and returns true. Otherwise does nothing and returns false. + * @ensures this.ints' = this.ints + i + * @return i in this.ints' + * @see kodkod.util.ints.IntSet#add(int) + */ + @Override + public boolean add(int i) { + final Range ceil = tree.searchGTE(i); + if (ceil==null || ceil.min > i) { + + final Range floor = tree.searchLTE(i); + + if (floor != null && floor.key==i-1) { + if (ceil != null && ceil.min==i+1) { + tree.delete(ceil); + floor.key = ceil.key; + } else { + floor.key = i; + } + } else if (ceil != null && ceil.min==i+1) { + ceil.min = i; + } else { + tree.insert(new Range(i,i)); + } + + size++; + return true; + } + + return false; + } + + /** + * Removes the given integer from this set if already present and + * returns true. Otherwise does nothing and returns false. + * @ensures this.ints' = this.ints - i + * @return i !in this.ints' + * @see kodkod.util.ints.IntSet#remove(int) + */ + @Override + public boolean remove(int i) { + final Range ceil = tree.searchGTE(i); + + if (ceil != null && i >= ceil.min) { + if (ceil.min==ceil.key) { + tree.delete(ceil); + } else if (i==ceil.min) { + ceil.min++; + } else if (i==ceil.key) { + ceil.key = i-1; + } else { // split the range in two + tree.insert(new Range(ceil.min, i-1)); + ceil.min = i+1; + } + size--; + assert size >= 0; + return true; + } + + return false; + } + + /** + * {@inheritDoc} + * @see kodkod.util.ints.IntSet#containsAll(kodkod.util.ints.IntCollection) + */ + @Override + public boolean containsAll(IntCollection other) { + if (other instanceof IntTreeSet) { + IntTreeSet s = (IntTreeSet) other; + if (size>=s.size) { + for(Range r1 = s.tree.min(); r1 != null; r1 = s.tree.successor(r1)) { + Range r0 = tree.searchGTE(r1.key); + if (r0==null || r1.min < r0.min) + return false; + } + return true; + } + return false; + } + return super.containsAll(other); + } + + /** + * Removes all elements from this set. + * @ensures no this.ints' + * @see kodkod.util.ints.IntCollection#clear() + */ + @Override + public void clear() { + tree.clear(); + size = 0; + } + + /** + * Returns a copy of this int tree set. The copy is independent of this + * IntSet. + * @return a copy of this IntSet. + * @see kodkod.util.ints.IntSet#clone() + */ + @SuppressWarnings("unchecked") + @Override + public IntTreeSet clone() { + // ok to use copy constructor to clone a final class + return new IntTreeSet(this); + } + + /** + * A range of integers in an int set. + * @specfield min: int + * @specfield max: int + * @invariant min <= max + * @invariant max = key + * @author Emina Torlak + */ + private static final class Range extends IntTree.Node implements Cloneable { + private int min; + + Range(int min, int max) { + super(max); + this.min = min; + } + + protected Range clone() throws CloneNotSupportedException { + return (Range)super.clone(); + } + + } + + /** + * An iterator that traverses the ints in this set in the + * ascending order. + * @author Emina Torlak + */ + private final class AscendingIterator implements IntIterator { + private Range next; + private final int endpoint; + private int currentMax, cursor, lastReturned; + private boolean canRemove; + + /** + * @requires from <= to + */ + AscendingIterator(int from, int to) { + endpoint = to; + lastReturned = Integer.MIN_VALUE; + canRemove = false; + next = tree.searchGTE(from); + if (next==null) { + cursor = 0; + currentMax = -1; + } else { + cursor = StrictMath.max(next.min, from); + currentMax = next.key; + next = tree.successor(next); + } + } + + public boolean hasNext() { + if (cursor > currentMax) { + if (next==null) return false; + this.cursor = next.min; + this.currentMax = next.key; + next = tree.successor(next); + } + return lastReturned= to + */ + DescendingIterator(int from, int to) { + endpoint = to; + lastReturned = Integer.MAX_VALUE; + canRemove = false; + next = tree.searchGTE(from); + if (next==null || next.min > from) { + next = tree.searchLTE(from); + if (next==null) { + cursor = -1; + currentMin = 0; + } else { + cursor = StrictMath.min(next.key, from); + currentMin = next.min; + } + } else { + cursor = StrictMath.min(next.key, from); + currentMin = next.min; + } + } + + public boolean hasNext() { + if (cursor < currentMin) { + if (next==null) return false; + this.cursor = next.key; + this.currentMin = next.min; + next = tree.predecessor(next); + } + return lastReturned>Integer.MIN_VALUE && cursor >= endpoint; + } + + public int next() { + if (!hasNext()) + throw new NoSuchElementException(); + canRemove = true; + return lastReturned = cursor--; + } + + public void remove() { + if (!canRemove) + throw new IllegalStateException(); + IntTreeSet.this.remove(lastReturned); + next = tree.searchLTE(cursor); + canRemove = false; + } + + } + + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/ints/IntVector.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/ints/IntVector.java new file mode 100644 index 00000000..0f2bc21c --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/ints/IntVector.java @@ -0,0 +1,271 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.util.ints; + + +/** + * A resizable array of integers. + * + * @specfield length: int + * @specfield elements: [0..size) ->one int + * + * @author Emina Torlak + */ +public interface IntVector extends IntCollection { + /** + * Returns the number of elements in this vector. + * @return this.length + */ + public int size(); + + /** + * Returns true if this vector contains no elements. + * + * @return no this.elements + */ + public boolean isEmpty(); + + /** + * Returns true if this vector contains the specified element. + * @return element in this.elements[int] + */ + public boolean contains(int element); + + /** + * Returns the element at the specified position in this vector. + * + * @return this.elements[index] + * + * @throws IndexOutOfBoundsException if the index is out of range (index + * < 0 || index >= length()). + */ + public int get(int index); + + /** + * Returns an iterator over the elements in this vector in proper sequence. + * + * @return an iterator over the elements in this vector in proper sequence. + */ + public IntIterator iterator(); + + /** + * Returns an iterator over the elements in this vector in proper sequence, + * starting fromIndex<\tt>, inclusive, and ending at toIndex<\tt>, exclusive. + * If fromIndex<\tt> is less than toIndex<\tt>, then the iterator will return + * the elements in the descending order. + * @return an iterator over the elements in this vector in proper sequence, + * starting at fromIndex<\tt>, inclusive, and ending at toIndex<\tt>. + * @throws IndexOutOfBoundsException - fromIndex !in [0..this.length) || toIndex !in [-1..this.length] + */ + public IntIterator iterator(int fromIndex, int toIndex); + + /** + * Replaces the element at the specified position in this vector with the + * specified element, and returns the previous element (optional operation). + * + * @ensures this.elements' = this.elements' ++ index -> element + * @return this.elements[index] + * + * @throws UnsupportedOperationException if the set method is not + * supported by this vector. + * @throws IllegalArgumentException if some aspect of the specified + * element prevents it from being added to this vector. + * @throws IndexOutOfBoundsException if the index is out of range + * (index < 0 || index >= length()). + */ + public int set(int index, int element); + + /** + * Removes all of the elements from this vector (optional operation). This + * vector will be empty after this call returns (unless it throws an + * exception). + * @ensures this.length' = 0 && no this.elements' + * @throws UnsupportedOperationException if the clear method is + * not supported by this vector. + */ + public void clear(); + + /** + * Returns the index in this vector of the first occurrence of the specified + * element, or -1 if this vector does not contain this element. + * + * @return element in this.elements[int] => min(this.elements.element), -1 + */ + public int indexOf(int element); + + /** + * Returns the index in this vector of the last occurrence of the specified + * element, or -1 if this vector does not contain this element. + * + * @return element in this.elements[int] => max(this.elements.element), -1 + */ + public int lastIndexOf(int element); + + /** + * Adds the specified element to the end of this vector (optional + * operation), and returns true if this vector has changed as a result + * of the call. + * @ensures this.length' = this.length + 1 && this.elements' = this.elements + this.length -> element + * @return this.elements != this.elements' + * @throws UnsupportedOperationException if the add method is not supported by this vector. + * @throws IllegalArgumentException if some aspect of this element prevents it from being added to this vector. + */ + public boolean add(int element); + + /** + * Inserts the specified element at the specified position in this vector + * (optional operation), and returns true if this vector has changed as a result of the call. + * Shifts the element currently at that position + * (if any) and any subsequent elements to the right (adds one to their + * indices). + * + * @ensures this.length' = this.length + 1 && + * this.elements' = { i: [0..this.length'), e: int | i < index => e = this.elements[i], + * i = index => e = element, e = this.elements[i-1] } + * @throws UnsupportedOperationException if the add method is not + * supported by this vector. + * @throws IllegalArgumentException if some aspect of the specified + * element prevents it from being added to this vector. + * @throws IndexOutOfBoundsException if the index is out of range + * (index < 0 || index > length()). + */ + public void add(int index, int element); + + /** + * Appends the specified elements to the end of this vector (optional + * operation), and returns true if this vector has changed as a result of the call. + * @ensures appends the specified elements to the end of this vector + * @return this.elements != this.elements' + * @throws UnsupportedOperationException if the add method is not + * supported by this vector. + * @throws IllegalArgumentException if some aspect of an element in the given vector + * prevents it from being added to this vector. + */ + public boolean addAll(IntCollection c); + + /** + * Inserts the specified elements at the specified position in this vector + * (optional operation), and returns true if this vector has changed as a result of the call. + * Shifts the element currently at that position + * (if any) and any subsequent elements to the right. + * + * @ensures inserts the specified elements at the specified position in this vector + * @return this.elements != this.elements + * @throws UnsupportedOperationException if the add method is not + * supported by this vector. + * @throws IllegalArgumentException if some aspect of an element in the specified + * collection prevents it from being added to this vector. + * @throws IndexOutOfBoundsException if the index is out of range + * (index < 0 || index > length()). + */ + public boolean addAll(int index, IntCollection c); + + /** + * Removes the first occurrence of the given integer from this vector, + * and returns true if this vector has changed as a result of the call. + * @ensures removes the first occurrence of the given integer from this vector + * @return this.elements != this.elements' + * @throws UnsupportedOperationException - this is an unmodifiable collection + */ + public abstract boolean remove(int i); + + /** + * Removes the element at the specified position in this vector (optional + * operation). Shifts any subsequent elements to the left (subtracts one + * from their indices). Returns the element that was removed from the + * vector. + * @return this.elements[index] + * @ensures this.length' = this.length - 1 && + * this.elements' = { i: [0..this.length'), e: int | i < index => e = this.elements[i], + * e = this.elements[i+1] } + * @throws UnsupportedOperationException if the remove method is + * not supported by this vector. + * @throws IndexOutOfBoundsException if the index is out of range (index + * < 0 || index >= length()). + */ + public int removeAt(int index); + + /** + * Removes all of this vector's elements that are also contained in the specified + * collection. After this call returns, this collection will contain no elements in + * common with the specified collection. Returns true if this collection has changed as a result of the call. + * @ensures removes all of this vector's elements that are also contained in the specified + * collection + * @return this.elements != this.elements' + * @throws NullPointerException - c = null + * @throws UnsupportedOperationException - this is an unmodifiable collection + */ + public abstract boolean removeAll(IntCollection c); + + /** + * Retains only the elements in this vector that are contained in the specified + * collection. In other words, removes from this collection all of its elements that + * are not contained in the specified collection. Returns true if this collection has changed as a result of the call. + * @ensures retains only the elements in this vector that are contained in the specified + * collection + * @return this.elements != this.elements' + * @throws NullPointerException - c = null + * @throws UnsupportedOperationException - this is an unmodifiable collection + */ + public abstract boolean retainAll(IntCollection c); + + /** + * Compares the specified object with this vector for equality. Returns + * true if and only if the specified object is also an int vector, both + * vectors have the same size, and all corresponding pairs of elements in + * the two vectors are equal. + * + * @return true if the specified object is equal to this vector. + */ + public boolean equals(Object o); + + /** + * Returns the hash code value for this vector. The hash code of an int vector is + * defined to be the {@link Ints#superFastHash(int[])} of the elements in the vector, + * taken in the ascending order of indices. + * This ensures that v1.equals(v2) implies that v1.hashCode()==v2.hashCode() + * for any two IntVectors v1 and v2, as required by the general contract of the Object.hashCode method. + * @return Ints.superFastHash(this.toArray()) + */ + public int hashCode(); + + /** + * Returns an array containing all of the elements in this vector in proper + * sequence. + * + * @return an array containing all of the elements in this vector in proper + * sequence. + */ + public int[] toArray(); + + /** + * Copies the components of this vector into the specified array, provided that + * it is large enough, and returns it. The item at index + * k in this vector is copied into component k of the given array. If the + * given array is not large enough, the effect of this method is the same as + * calling {@linkplain #toArray()}. + * @ensures array.length>=this.length => all i: [0..this.length) | array'[i] = this.elements[i] + * @return array.length>=this.length => array' else this.toArray() + * @throws NullPointerException - array = null + */ + public int[] toArray(int[] array); +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/ints/Ints.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/ints/Ints.java new file mode 100644 index 00000000..6038825c --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/ints/Ints.java @@ -0,0 +1,510 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.util.ints; + +import java.util.Iterator; +import java.util.NoSuchElementException; + +import kodkod.util.ints.IntRange.OnePointRange; +import kodkod.util.ints.IntRange.TwoPointRange; + + +/** + * Contains various utility methods for working with + * integers, {@link kodkod.util.ints.IntRange IntRanges}, + * {@link kodkod.util.ints.IntSet IntSets}, and {@link kodkod.util.ints.SparseSequence SparseSequences}. + * + *

The methods in this class all throw a NullPointerException if + * given a null reference unless otherwise specified.

+ * + * @author Emina Torlak + */ +public final class Ints { + /** An immutable empty int set. The clone method returns the empty set itself. */ + public static final IntSet EMPTY_SET = + new AbstractIntSet() { + public boolean contains(int i) { return false; } + public int min() { throw new NoSuchElementException(); } + public int max() { throw new NoSuchElementException(); } + public IntIterator iterator(int from, int to) { + return new IntIterator() { + public boolean hasNext() { return false; } + public int next() { throw new NoSuchElementException(); } + public void remove() { throw new UnsupportedOperationException(); } + }; + } + public int size() { return 0; } + public int floor(int i) { throw new NoSuchElementException(); } + public int ceil(int i) { throw new NoSuchElementException(); } + public IntSet clone() { return EMPTY_SET;} + }; + + private Ints() {} + + /*-----------SETS AND RANGES-----------*/ + + /** + * Returns an integer range from min, inclusive, to max, inclusive. + * @return { r: IntRange | r.min = min && r.max = max } + * @throws IllegalArgumentException - min > max + */ + public static IntRange range(int min, int max) { + if (min < max) return new TwoPointRange(min,max); + else if (min==max) return new OnePointRange(min); + else throw new IllegalArgumentException("min > max"); + } + + /** + * Returns the smallest IntRange r that contains + * both r1 and r2. + * @return { r: IntRange | r.contains(r1) && r.contains(r2) && + * no r' : IntRange - r | r'.contains(r1) && r'.contains(r2) && r'.size() < r.size() } + * @throws NullPointerException - range = null + */ + public static IntRange merge(IntRange r1, IntRange r2) { + if (r1.contains(r2)) return r1; + else if (r2.contains(r1)) return r2; + else return range(StrictMath.min(r1.min(),r2.min()), StrictMath.max(r1.max(), r2.max())); + } + + /** + * Returns an unmodifiable view of the specified set. This method + * allows modules to provide users with "read-only" access to internal int sets. + * Query operations on the returned set "read through" to the specified set, and + * attempts to modify the returned set, whether direct or via its iterator, result + * in an UnsupportedOperationException. The clone() method of the returned set + * returns the result of calling s.clone(). + * @return an unmodifiable view of s + * @throws NullPointerException - s = null + */ + public static IntSet unmodifiableIntSet(final IntSet s) { + if (s==null) + throw new NullPointerException("s = null"); + else if (s instanceof UnmodifiableIntSet || s instanceof SingletonIntSet || s instanceof RangeIntSet) + return s; + else + return new UnmodifiableIntSet(s); + } + + /** + * Returns an unmodifiable IntSet whose sole + * element is the given integer. The clone method + * of the returned set returns the set itself. + * @return {s: IntSet | s.ints = i} + */ + public static IntSet singleton(final int i) { + return new SingletonIntSet(i); + } + + /** + * Returns an unmodifiable IntSet that contains + * all the elements in the given range. The clone + * method of the returned set returns the set itself. + * @return {s: IntSet | s.ints = [range.min()..range.max()] } + */ + public static IntSet rangeSet(IntRange range) { + if (range==null) + throw new NullPointerException(); + return new RangeIntSet(range); + } + + /** + * Returns an implementation of the int set interface + * that offers the best time/space trade-off for a + * set that can store all elements in the half open + * range [0..max). The returned instance may or may + * not admit elements out of the range [0..max). + * @return an int set that can store at least the + * elements in [0..max). + */ + public static IntSet bestSet(int max) { + // cut-off for using a bit map is 512 + return max > 512 ? new IntTreeSet() : new IntBitSet(max); + } + + /** + * Returns an implementation of the int set interface + * that offers the best time/space trade-off for a + * set that can store all elements in the closed range [min..max]. + * The returned instance may or may not admit elements + * out of the specified range. + * @return an int set that can store at least the + * elements in the given range. + * @throws IllegalArgumentException - min > max + */ + public static IntSet bestSet(int min, int max) { + if (min > max) throw new IllegalArgumentException("min > max"); + return min < 0 ? new IntTreeSet() : bestSet(max+1); + } + + /** + * Returns an IntSet that is backed by the given array of integers. + * The array must contain no duplicates, its elements must be sorted + * in the ascending order, and its contents + * must not be changed while it is in use by the returned set. + * @requires all i, j: [0..ints.length) | i < j => array[i] <= Sarray[j] + * @return an unmodifiable IntSet view of the given array + */ + public static IntSet asSet(int[] ints) { + return ints.length==0 ? EMPTY_SET : new ArrayIntSet(ints); + } + + /** + * Returns an unmodifiable IntArray backed by the given array of integers. + * @return an unmodifiable IntArray backed by the given array of integers. + */ + public static IntVector asIntVector(final int[] ints) { + return new AbstractIntVector() { + public int get(int index) { return ints[index]; } + public int size() { return ints.length; } + public int[] toArray(int[] array) { + if (array.length < ints.length) { + array = new int[ints.length]; + } + System.arraycopy(ints, 0, array, 0, ints.length); + return array; + } + }; + } + + /** + * Returns an unmodifiable IntArray of length n which contains the given + * element at each position. + * @return an unmodifiable IntArray of length n which contains the given + * element at each position + */ + public static IntVector nCopies(final int n, final int elt) { + return new AbstractIntVector() { + public int get(int index) { + if (index < 0 || index >= n) throw new IndexOutOfBoundsException(); + return elt; + } + public int size() { return n; } + public int[] toArray(int[] array) { + if (array.length < n) { + array = new int[n]; + } + for(int i = 0; i < n; i++) { array[i] = elt; } + return array; + } + }; + } + + /*-----------SEQUENCES-----------*/ + /** + * Returns an unmodifiable view of the specified sparse sequence. This method + * allows modules to provide users with "read-only" access to internal sparse sequences. + * Query operations on the returned sequence "read through" to the specified sequence, and + * attempts to modify the returned sequence, whether direct or via its iterator, result + * in an UnsupportedOperationException. The clone() method of the returned sequence + * returns the result of calling s.clone(). + * @return an unmodifiable view of s + * @throws NullPointerException - s = null + */ + public static SparseSequence unmodifiableSequence(SparseSequence s) { + if (s==null) + throw new NullPointerException(); + if (s instanceof UnmodifiableSparseSequence) + return s; + else return new UnmodifiableSparseSequence(s); + } + + /*-----------INTEGER MANIPULATION-----------*/ + + /** + * Returns an integer whose value is the 16 low order bits of the given key. + * @return key & 0x0000ffff + */ + private static int low16(int key) { + return key & 0x0000ffff; + } + + /** + * Returns an integer whose value is the 16 high order bits of the given key. + * @return (key >>> 16) & 0x0000ffff + */ + private static int high16(int key) { + return low16(key>>>16); + } + + /** + * Performs the bit avalanching step of Paul Hsieh's + * hashing function (http://www.azillionmonkeys.com/qed/hash.html) + * @return the bit avalanched version of the given hash value + */ + public static int superFastHashAvalanche(int hash) { + hash ^= hash << 3; + hash += hash >> 5; + hash ^= hash << 4; + hash += hash >> 17; + hash ^= hash << 25; + hash += hash >> 6; + return hash; + } + + /** + * Performs the hashing step of Paul Hsieh's hashing function, + * described at http://www.azillionmonkeys.com/qed/hash.html. + * The method returns a 32 bit hash of the given integer, starting + * with the given initial hash. This method does not perform + * bit avalanching. To get the full hash, call {@linkplain #superFastHashAvalanche(int)} + * on the value returned by this method. + * @return a 32 bit hash of the given integer, based on the given hash + */ + public static int superFastHashIncremental(int key, int hash) { + + hash += low16(key); + final int tmp = (high16(key) << 11) ^ hash; + hash = (hash << 16) ^ tmp; + hash += hash >> 11; + + // no end cases since the key has exactly 4 bytes + return hash; + } + + /** + * An implementation of Paul Hsieh's hashing function, + * described at http://www.azillionmonkeys.com/qed/hash.html. + * The method returns a 32 bit hash of the given integer. + * This function is very fast and collision resistent; e.g. + * it hashes the four million integers in the range + * [-2000000,...-1, 1,..., 2000000] to distinct values. + * The initial hash is taken to be 11. + * @return a 32 bit hash of the given integer + */ + public static int superFastHash(int key) { + return superFastHashAvalanche(superFastHashIncremental(key, 11)); + } + + /** + * An implementation of Paul Hsieh's hashing function, + * described at http://www.azillionmonkeys.com/qed/hash.html. + * The method returns a 32 bit hash of the given integers, + * or 0 if the array is empty. The initial hash is taken to be + * the number of keys. + * @return a 32 bit hash of the given integers + */ + public static int superFastHash(int... key) { + if (key.length==0) return 0; + int hash = key.length; + + for(int word : key) { + hash = superFastHashIncremental(word, hash); + } + // no end cases since key parts are ints + return superFastHashAvalanche(hash); + } + + /** + * An implementation of Paul Hsieh's hashing function, + * described at http://www.azillionmonkeys.com/qed/hash.html. + * The method returns a 32 bit hash of the given objects' hash codes, + * or zero if the array is empty. Any null references in the array + * are taken to have 0 as their hash code value. + * @return a 32 bit hash of the given objects' hashCodes + */ + public static int superFastHash(Object... key) { + if (key.length==0) return 0; + int hash = key.length; + + for(Object o : key) { + hash = superFastHashIncremental(o == null ? 0 : o.hashCode(), hash); + } + // no end cases since the hashcodes of key parts are ints + return superFastHashAvalanche(hash); + } + + /** + * An implementation of an IntSet wrapper for an IntRange. + */ + private static final class RangeIntSet extends AbstractIntSet { + private final IntRange range; + /** + * Constructs an unmodifiable IntSet wrapper for a range. + */ + RangeIntSet(IntRange range) { + this.range = range; + } + public boolean contains(int i) { return range.contains(i); } + public int min() { return range.min(); } + public int max() { return range.max(); } + public IntIterator iterator(final int from, final int to) { + return new IntIterator() { + final boolean ascending = (from <= to); + long cursor = ascending ? StrictMath.max(range.min(), from) : StrictMath.min(range.max(), from); + final int end = ascending ? StrictMath.min(range.max(), to) : StrictMath.max(range.min(), to); + public boolean hasNext() { + return ascending && cursor<=end || !ascending && cursor >= end; + } + public int next() { + if (!hasNext()) throw new NoSuchElementException(); + return ascending ? (int)cursor++ : (int)cursor--; + } + public void remove() { throw new UnsupportedOperationException(); } + + }; + } + public int size() { return range.size(); } + public IntSet copy() { return this; } + public int floor(int i) { + if (irange.max()) + throw new NoSuchElementException(); + return StrictMath.max(i, range.min()); + } + public IntSet clone() { return this; } + } + + /** + * An implementation of an IntSet wrapper for a single integer. + */ + private static final class SingletonIntSet extends AbstractIntSet { + private final int i; + /** + * Constructs an unmodifiable intset wrapper for the given integer. + */ + SingletonIntSet(int i) { + this.i = i; + } + public boolean contains(int j) { return i==j; } + public int min() { return i; } + public int max() { return i; } + public IntIterator iterator(final int from, final int to) { + return new IntIterator() { + boolean cursor = (from<=i && i<=to) || (to<=i && i<=from); + public boolean hasNext() { return cursor; } + public int next() { + if (!hasNext()) throw new NoSuchElementException(); + cursor = false; + return i; + } + public void remove() { throw new UnsupportedOperationException(); } + }; + } + public int size() { return 1; } + public IntSet copy() { return this; } + public boolean equals(Object o) { + if (this==o) return true; + else if (o instanceof IntSet) { + final IntSet s = (IntSet) o; + return s.size()==1 && s.min()==i; + } else + return super.equals(o); + } + public int hashCode() { return i; } + public int floor(int j) { + if (i<=j) return i; + else throw new NoSuchElementException(); + } + public int ceil(int j) { + if (i>=j) return i; + else throw new NoSuchElementException(); + } + public IntSet clone() { return this; } + } + + /** + * An implementation of an unmodifiable IntSet view. + * @author Emina Torlak + */ + private static final class UnmodifiableIntSet extends AbstractIntSet { + private final IntSet s; + + /** + * Constructs an unmodifiable wrapper for the given intset. + * @requires set != null + */ + UnmodifiableIntSet(IntSet set) { + this.s = set; + } + public int size() { return s.size(); } + public boolean contains(int i) { return s.contains(i); } + public int min() { return s.min(); } + public int max() { return s.max(); } + public boolean containsAll(IntSet other) { return s.containsAll(other ); } + public IntIterator iterator(final int from, final int to) { + return new IntIterator() { + IntIterator iter = s.iterator(from,to); + public boolean hasNext() { return iter.hasNext(); } + public int next() { return iter.next(); } + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + public int floor(int i) { return s.floor(i); } + public int ceil(int i) { return s.ceil(i); } + public IntSet clone() throws CloneNotSupportedException { return s.clone(); } + } + + /** + * An implementation of an unmodifiable SparseSequence view. + * @author Emina Torlak + */ + private static final class UnmodifiableSparseSequence extends AbstractSparseSequence { + private final SparseSequence s; + + UnmodifiableSparseSequence(SparseSequence s) { + this.s = s; + } + + public Iterator> iterator(final int from, final int to) { + return new Iterator>() { + Iterator> iter = s.iterator(from, to); + public boolean hasNext() { + return iter.hasNext(); + } + + public IndexedEntry next() { + return iter.next(); + } + + public void remove() { + throw new UnsupportedOperationException(); + } + + }; + } + + public int size() { return s.size(); } + public void clear() { throw new UnsupportedOperationException(); } + public V put(int index, V value) { throw new UnsupportedOperationException(); } + public V get(int index) { return s.get(index); } + public V remove(int index) { throw new UnsupportedOperationException(); } + public IndexedEntry first() { return s.first(); } + public IndexedEntry last() { return s.last(); } + + public IndexedEntry ceil(int index) { return s.ceil(index); } + public IndexedEntry floor(int index) { return s.floor(index); } + public boolean containsIndex(int index) { return s.containsIndex(index); } + public boolean contains(Object value) { return s.contains(value); } + + public SparseSequence clone() throws CloneNotSupportedException { + return s.clone(); + } + + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/ints/RangeSequence.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/ints/RangeSequence.java new file mode 100644 index 00000000..e7f5580b --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/ints/RangeSequence.java @@ -0,0 +1,614 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.util.ints; + +import java.util.Iterator; +import java.util.NoSuchElementException; + +/** + *

A tree-based sparse sequence implementation. Unlike {@link kodkod.util.ints.TreeSequence}, + * this is not a general-purpose sparse sequence implementation. In particular, + * the entries with consecutive indices and the same value are not stored explicitly. As a result, + * methods + * that return an {@link kodkod.util.ints.IndexedEntry} may re-use the same object. + * Specifically, the last assertion in the following code snippet may fail.

+ *
+ * // let s be a range sequence abstractly represented as { 0->v, 1->v, 2->v }
+ * IndexedEntry e1 = s.predecessor(2);
+ * assert e1.index()==1; // this will work
+ * IndexedEntry e2 = s.predecessor(1);
+ * assert e1.index()==1; // this may fail, as e1 may be == to e2
+ * 
+ *

The entries returned by this implementation's {@link #iterator()} are unique + * to that iterator (but not necessarily independent of each other). For example,

+ *
+ * // let s be a range sequence abstractly represented as { 0->v, 1->v, 2->v }
+ * Iterator> iter1 = s.iterator();
+ * IndexedEntry e1 = iter1.next();
+ * assert e1.index()==0; // this will work
+ * iter1.next();
+ * assert e1.index()==0; // this may fail, as the previous call may have changed the state of e1
+ * Iterator> iter2 = s.iterator();
+ * IndexedEntry e2 = iter2.next();
+ * iter1.next();
+ * assert e2.index()==0; // this will work
+ * 
+ *

This implementation is a good choice when the client expects the usage pattern with many consecutive + * indices mapped to the same value, and when there is no need for entry uniqueness.

+ * @author Emina Torlak + */ +public final class RangeSequence extends AbstractSparseSequence implements Cloneable { + /* The ranges are sorted by their right endpoints. All consecutive indices + * that map to the same value are represented by a single range node. + * @invariant (all n: tree.nodes | n.max = n.key && n.min <= n.max) && + * (no disj n, n': tree.nodes | n.value=n'.value && n.max = n'.min-1) + */ + private final IntTree> tree; + private final EntryView view; + private int size; + + /** + * Constructs an empty RangeSequence. + * @ensures no this.entries' + */ + public RangeSequence() { + view = new EntryView(Integer.MIN_VALUE,null); + tree = new IntTree>(); + size = 0; + } + + /** + * Copy constructor. + * @ensures creatres a deep copy of the original + */ + @SuppressWarnings("unchecked") + private RangeSequence(RangeSequence original) { + this.size = original.size; + try { + this.tree = original.tree.clone(); + } catch (CloneNotSupportedException e) { + throw new InternalError(); // unreachable code; + } + view = new EntryView(Integer.MIN_VALUE,null); + } + + /** + * {@inheritDoc} + * @see kodkod.util.ints.SparseSequence#iterator(int, int) + */ + public Iterator> iterator(int from, int to) { + return from <= to ? new AscendingIterator(from, to) : new DescendingIterator(from, to); + } + + /** + * {@inheritDoc} + * @see kodkod.util.ints.SparseSequence#size() + */ + public int size() { + return size; + } + + /** + * Removes all entries from this sequences. + * @ensures no this.entries' + * @see kodkod.util.ints.SparseSequence#clear() + */ + public void clear() { + tree.clear(); + size = 0; + } + + /** + * Returns true if e is the head of a contiguous homogenous + * sequence starting with the mapping e.min->e.value and + * ending with the mapping index->value. + * @return e!=null index-1 = e.key && equal(value, e.value) + */ + private boolean isHeadOf(Entry e, int index, V value){ + return e!=null && e.key==index-1 && equal(e.value, value); + } + + /** + * Returns true if e is the tail of a contiguous homogenous + * sequence starting with the mapping index->value and + * ending with the mapping e.max->e.value. + * @return e!=null && index+1 = e.min && equal(value, e.value) + */ + private boolean isTailOf(Entry e, int index, V value) { + return e!=null && e.min()==index+1 && equal(e.value, value); + } + + + /** + * Merges the mapping index->value into its floor or ceiling, if + * possible. Otherwise creates a new node for index->value and + * inserts into the tree. + * @requires index !in this.nodes.key + * @requires f = searchLTE(index) && c = searchGTE(index) + * @ensures this.entries' = this.entries + index->value + */ + private void merge(int index, V value, Entry f, Entry c) { + if (isHeadOf(f, index, value)) { + if (f.isPoint()) { + if (isTailOf(c, index, value)) { + if (c.isPoint()) { // [f: 0->a][i: 1->a][c: 2->a] ---> [{0,1,2}->a] + tree.delete(c); + tree.replace(f, new Range(f.key, c.key, value)); + } else { // [f: 0->a][i: 1->a][c: {2,3}->a] ---> [c: {0,1,2,3}->a] + tree.delete(f); + ((Range)c).min = f.key; + } + } else { // [f: 0->a][i: 1->a] ---> [{0,1}->a] + tree.replace(f, new Range(f.key, index, value)); + } + } else { + if (isTailOf(c, index, value)) { // [f: {-1,0}->a][i: 1->a][c: 2->a] ---> [f: {-1,0,1,2}->a] + tree.delete(c); + f.key = c.key; + } else { // [f: {-1,0}->a][i: 1->a] ---> [f: {0,1,2}->a] + f.key = index; + } + } + } else if (isTailOf(c, index, value)) { + if (c.isPoint()) { // [i: 1->a][c: 2->a] ---> [{1,2}->a] + tree.replace(c, new Range(index, c.key, value)); + } else { // [i: 1->a][c: {2,3}->a] ---> [c: {1,2,3}->a] + ((Range)c).min = index; + } + } else { // can't merge anything + tree.insert(new Point(index,value)); + } + } + + /** + * {@inheritDoc} + * @see kodkod.util.ints.SparseSequence#put(int, Object) + */ + public V put(int index, V value) { + Entry c = tree.searchGTE(index); + if (c==null || c.min() > index) { // we are adding a new index + size++; + merge(index, value, tree.searchLTE(index), c); + return null; + } else { // the index already exists + if (equal(value, c.value)) { + return value; // nothing to do + } else if (c.isPoint()) { + if (isHeadOf(tree.predecessor(c), index, value) || + isTailOf(tree.successor(c), index, value)) { + final V oldVal = remove(index); + put(index, value); + return oldVal; + } + return c.setValue(value); + } else { // split the range + final V oldVal = split(index, c); + tree.insert(new Point(index,value)); + return oldVal; + } + } + + } + + /** + * {@inheritDoc} + * @see kodkod.util.ints.SparseSequence#get(int) + */ + public V get(int index) { + final Entry e = tree.searchGTE(index); + return e==null || e.min() > index ? null : e.value; + } + + /** + * Splits the z entry into the least number of entries + * that do not contain the given index. + * @requires z.min() <= index <= z.max() + * @requires z != NIL + * @ensures this.entries' = this.entries' - index->V + * @return z.value + */ + private V split(int index, Entry z) { + final V val = z.value; + if (z.isPoint()) + tree.delete(z); + else { // z contains a range of keys + final Range r = (Range) z; + final int min = r.min, max = r.key; + if (min==index) { + if (min+1(max, val)); + } else if (max==index) { + if (max-1>min) + r.key--; + else + tree.replace(r, new Point(min, val)); + } else { // index is between min and max + if (min==index-1) { + tree.replace(r, new Point(index-1, val)); + if (max==index+1) { + tree.insert(new Point(max, val)); + } else { + r.min = index+1; + tree.insert(r); + } + } else { + r.key = index-1; + if (max==index+1) { + tree.insert(new Point(max, val)); + } else { + tree.insert(new Range(index+1, max, val)); + } + } + } + } + return val; + } + + /** + * Removes the entry with the given index, if it exists, and + * returns the value previously stored at the index. If the + * sequence had no previous mapping for the index, null is returned. + * @ensures this.entries' = this.entries - index->E + * @return this.entries[index] + * @see kodkod.util.ints.SparseSequence#remove(int) + */ + public V remove(int index) { + final Entry z = tree.searchGTE(index); + if (z==null || index < z.min()) return null; + size--; + return split(index,z); + } + + /** + * {@inheritDoc} + * @see kodkod.util.ints.SparseSequence#containsIndex(int) + */ + public boolean containsIndex(int index) { + final Entry e = tree.searchGTE(index); + return e != null && e.min() <= index; + } + + /** + * {@inheritDoc} + * @see kodkod.util.ints.SparseSequence#first() + */ + public IndexedEntry first() { + final Entry e = tree.min(); + return e==null ? null : view.setView(e.min(), e.value); + } + + /** + * {@inheritDoc} + * @see kodkod.util.ints.SparseSequence#last() + */ + public IndexedEntry last() { + final Entry e = tree.max(); + return e==null ? null : view.setView(e.max(), e.value); + } + + /** + * {@inheritDoc} + * @see kodkod.util.ints.SparseSequence#ceil(int) + */ + public IndexedEntry ceil(int index) { + final Entry e = tree.searchGTE(index); + return e == null ? null : view.setView(StrictMath.max(index, e.min()), e.value); + } + + /** + * {@inheritDoc} + * @see kodkod.util.ints.SparseSequence#floor(int) + */ + public IndexedEntry floor(int index) { + Entry e = tree.searchGTE(index); + if (e==null || e.min() > index) { + e = tree.searchLTE(index); + return e == null ? null : view.setView(e.max(), e.value); + } else + return view.setView(index, e.value); + } + + /** + * Returns a copy of this sparse sequence. The copy is independent of this + * sequence. + * @return a copy of this sparse sequence. + * @see kodkod.util.ints.SparseSequence#clone() + */ + @SuppressWarnings("unchecked") + public RangeSequence clone() { + // ok to use copy constructor to clone a final class + return new RangeSequence(this); + + } + + /** + * A mapping from range of integers in to a value + * @specfield min: int + * @specfield max: int + * @specfield value: V + * @invariant min <= max + * @invariant max = key + * @author Emina Torlak + */ + private static abstract class Entry extends IntTree.Node> implements Cloneable { + V value; + + Entry(int max, V value) { + super(max); + this.value = value; + } + + V setValue(V newValue) { + final V oldValue = value; + value = newValue; + return oldValue; + } + + /** + * Returns the minimum of this. + * @return this.min + */ + abstract int min(); + + /** + * Returns the maximum of this. + * @return this.max + */ + final int max() { return key; } + + /** + * Return true if this.min=this.max + * @return this.min = this.max + */ + abstract boolean isPoint(); + + /** + * {@inheritDoc} + * @throws CloneNotSupportedException + * @see java.lang.Object#clone() + */ + @SuppressWarnings("unchecked") + protected Entry clone() throws CloneNotSupportedException { + return (Entry) super.clone(); + } + } + + /** + * A point entry in a range sequence. + * @specfield min: int + * @specfield max: int + * @specfield value: V + * @specfield index: [min..max] + * @invariant min = max + */ + private static final class Point extends Entry { + /** + * Constructs an entry with the given index and value. + * @ensures this.index' = index && this.value' = value + * @ensures this.min' = this.max' = index + */ + Point(int index, V value) { + super(index, value); + } + + @Override + int min() { return key; } + + @Override + boolean isPoint() { return true; } + + @SuppressWarnings("unchecked") + protected Point clone() throws CloneNotSupportedException { + return (Point) super.clone(); + } + } + + /** + * A range entry in a range sequence. + * @specfield min: int + * @specfield max: int + * @specfield value: V + * @invariant min < max + */ + private static final class Range extends Entry { + int min; + + /** + * Constructs an entry with the given min/max and value. + * @ensures this.index' = min && this.value' = value + * @ensures this.min' = min && this.max' = max + */ + Range(int min, int max, V value) { + super(max, value); + this.min = min; + } + + @Override + int min() { return min; } + + @Override + boolean isPoint() { return false; } + + @SuppressWarnings("unchecked") + protected Range clone() throws CloneNotSupportedException { + return (Range) super.clone(); + } + } + + /** + * An iterator over the entries in this sequence. + */ + private abstract class EntryIterator implements Iterator>, IndexedEntry { + final int endIndex; + int endpoint, cursor, index; + boolean canRemove; + Entry next; + V value; + + /** + * @ensures this.endIndex' = endIndex && canRemove = false; + */ + EntryIterator(int endIndex) { + this.endIndex = endIndex; + this.canRemove = false; + } + + public final int index() { return index; } + public final V value() { return value; } + + public final boolean equals(Object o) { + if (o==this) return true; + if (!(o instanceof IndexedEntry)) return false; + return AbstractSparseSequence.equal(this, (IndexedEntry)o); + } + + public final int hashCode() { + return AbstractSparseSequence.hashCode(this); + } + + public final String toString() { + return index + "=" + value; + } + } + + /** + * An iterator that returns the entries in this sequence + * in the ascending order of indices. + * + * @author Emina Torlak + */ + private final class AscendingIterator extends EntryIterator { + + /** + * Constructs an ascending iterator over the entries with + * indeces between from and to. + * @requires from <= to + */ + @SuppressWarnings("unchecked") + AscendingIterator(int from, int to) { + super(to); + next = tree.searchGTE(from); + index = Integer.MIN_VALUE; + if (next==null) { + cursor = 0; + endpoint = -1; + value = null; + } else { + cursor = StrictMath.max(next.min(), from); + endpoint = next.key; + value = next.value; + next = tree.successor(next); + } + } + + public boolean hasNext() { + if (cursor > endpoint) { + if (next==null) return false; + this.cursor = next.min(); + this.endpoint = next.key; + this.value = next.value; + next = tree.successor(next); + } + return index next() { + if (!hasNext()) + throw new NoSuchElementException(); + index = cursor++; + canRemove = true; + return this; + } + + public void remove() { + if (!canRemove) + throw new IllegalStateException(); + RangeSequence.this.remove(index); + next = tree.searchGTE(cursor); + canRemove = false; + } + } + + /** + * An iterator that returns the entries in this sequence + * in the descending order of indices. + * + * @author Emina Torlak + */ + private final class DescendingIterator extends EntryIterator { + + /** + * Constructs a descending iterator over the entries with + * indeces between from and to. + * @requires from >= to + */ + DescendingIterator(int from, int to) { + super(to); + next = tree.searchGTE(from); + index = Integer.MAX_VALUE; + if (next==null || next.min() > from) { + next = tree.searchLTE(from); + if (next==null) { + cursor = -1; + endpoint = 0; + value = null; + } else { + cursor = next.key; + endpoint = next.min(); + value = next.value; + } + } else { + cursor = StrictMath.min(next.key, from); + endpoint = next.min(); + value = next.value; + next = tree.predecessor(next); + } + } + + public boolean hasNext() { + if (cursor < endpoint) { + if (next==null) return false; + this.cursor = next.key; + this.endpoint = next.min(); + this.value = next.value; + next = tree.predecessor(next); + } + return index>Integer.MIN_VALUE && cursor >= endIndex; + } + public IndexedEntry next() { + if (!hasNext()) + throw new NoSuchElementException(); + index = cursor--; + canRemove = true; + return this; + } + public void remove() { + if (!canRemove) + throw new IllegalStateException(); + RangeSequence.this.remove((int) index); + next = tree.searchLTE((int)cursor); + canRemove = false; + } + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/ints/SparseSequence.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/ints/SparseSequence.java new file mode 100644 index 00000000..57766e35 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/ints/SparseSequence.java @@ -0,0 +1,224 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.util.ints; + +import java.util.Collection; +import java.util.Iterator; + +/** + *

Represents a sparse sequence -- a sequence whose indices are not + * necessarily contiguous. For example, a sparse sequence can have + * elements at indices 10, 2121, and 3000, without having any elements + * in between. This specification of sparse sequences also supports + * negative indeces. Formally, the following methods specify a partial + * function from integers to values of type V.

+ * + *

Sequence implementations are not required to support mutation. + * All mutating operations are optional and may + * throw an UnsupportedOperationException.

+ * + * @specfield entries: int -> lone V + * + * @author Emina Torlak + */ +public interface SparseSequence extends Iterable> { + + /** + * Returns the number of entries in this sequence. + * @return #this.entries + */ + public abstract int size(); + + /** + * Returns true if this sequence is empty; otherwise returns false. + * @return no this.entries + */ + public abstract boolean isEmpty(); + + /** + * Removes all entries from this sequences. + * @ensures no this.entries' + */ + public abstract void clear(); + + /** + * Puts the given value at the specified index. If the + * sequence already mapped the index to a value, the + * previous value is replaced with the new one and returned. + * + * @ensures this.entries' = this.entries + index->value + * @return this.entries[index] + * @throws IndexOutOfBoundsException - the given index + * is not valid for this sequence. + * @throws IllegalArgumentException - the given value cannot + * be stored in this sequence. + */ + public abstract V put(int index, V value); + + /** + * Copies all of the entries from the specified sparse sequence to + * this sequence. The effect of this call is equivalent to that of + * calling put(e.index, e.value) on this sequence once for each entry + * e in the specified sequence. + * @ensures this.entries' = this.entries ++ s.entries + * @throws IndexOutOfBoundsException - s contains indeces that are + * not valid for this sequence. + * @throws IllegalArgumentException - s contains a value that cannot + * be stored in this sequence. + */ + public abstract void putAll(SparseSequence s); + + + /** + * Returns the value to which this sequence maps the given + * index. If the index is not mapped, null is returned. + * @return this.entries[index] + */ + public abstract V get(int index); + + /** + * Removes the entry with the given index, if it exists, and + * returns the value previously stored at the index. If the + * sequence had no previous mapping for the index, null is returned. + * @ensures this.entries' = this.entries - index->E + * @return this.entries[index] + */ + public abstract V remove(int index); + + /** + * Returns true if this sparse sequence has an entry for the + * given index; otherwise returns false. + * @return some this.entries[index] + */ + public abstract boolean containsIndex(int index); + + /** + * Returns the set of all indices mapped by this sparse sequence. + * The returned set supports removal iff this is not an unmodifiable + * sparse sequence. The returned set may be uncloneable. + * @return {s: IntSet | s.ints = this.entries.V} + */ + public abstract IntSet indices(); + + /** + * Returns true if this sequence has an entry with the given value; + * otherwise returns false. + * @return some this.entries.value + */ + public abstract boolean contains(Object value); + + /** + * Returns a Collection view of the values stored in this sequence. + * The returned collection supports removal iff this is not an + * unmodifiable sparse sequence. + * @return {c: Collection | c.size()=this.size() && + * all v: V | c.contains(v) <=> this.contains(v) } + */ + public abstract Collection values(); + + /** + * Returns an iterator over the entries in this sequence + * in the ascending order of indeces, starting at this.first(). + * @return an iterator over this.entries starting at the entry + * with the smallest index + */ + public abstract Iterator> iterator(); + + /** + * Returns an iterator over the entries in this sequence, + * whose indeces are between from and to. If from < to, + * the entries are returned in the ascending order of + * indeces. Otherwise, they are returned in the descending + * order of indeces. + * @return an iterator over the entries in this sequence + * whose indeces are between from and to. Formally, if + * from < to, then the first and last entries returned + * by the iterator are this.ceil(from) and this.floor(to). + * Otherwise, they are this.floor(from) and this.ceil(to). + */ + public abstract Iterator> iterator(int from, int to); + + /** + * Returns the entry with the smallest index. If the sequence + * is empty, returns null. + * @return {e: IndexedEntry | e.index = min(this.entries.E) && + * e.value = this.entries[e.index] } + */ + public abstract IndexedEntry first(); + + /** + * Returns the entry with the largest index. If the sequence + * is empty, returns null. + * @return {e: IndexedEntry | e.index = max(this.entries.E) && + * e.value = this.entries[e.index] } + */ + public abstract IndexedEntry last(); + + /** + * If an entry for the given index exists, it is returned. Otherwise, + * successor(index) is returned. + * @return this.containsIndex(index) => + * {e: IndexedEntry | e.index = index && e.value = this.entries[index] }, + * successor(index) + */ + public abstract IndexedEntry ceil(int index); + + /** + * If an entry for the given index exists, it is returned. Otherwise, + * predecessor(index) is returned. + * @return this.containsIndex(index) => + * {e: IndexedEntry | e.index = index && e.value = this.entries[index] }, + * predecessor(index) + */ + public abstract IndexedEntry floor(int index); + + /** + * Compares the specified object with this sequence for equality. + * Returns true if the given object is also a sparse sequence and the two + * sequences have the same entries. More formally, two sequences t1 and t2 have the + * the same entries if they represent the same function from integers to values: i.e. + * t1.entries = t2.entries. This ensures that the equals method works properly across + * different implementations of the SparseSequence interface. + * @return o in SparseSequence && o.entries = this.entries + */ + public abstract boolean equals(Object o); + + /** + * Returns the hash code value for this sparse sequence. + * A hash function for a sparse sequence must ensure that t1.equals(t2) + * implies that t1.hashCode()==t2.hashCode() for any two sequences t1 and t2, + * as required by the general contract of Object.hashCode. + * @return hash code for this sparse sequence + */ + public abstract int hashCode(); + + /** + * Returns a copy of this sparse sequence. The copy is independent of this + * sequence unless this is a singleton or immutable, in which case + * clone() may return this. An implementing class that does not support + * cloning may throw a CloneNotSupportedException. + * @return a copy of this sparse sequence. + * @throws CloneNotSupportedException - this is not cloneable + */ + public abstract SparseSequence clone() throws CloneNotSupportedException; + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/ints/TreeSequence.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/ints/TreeSequence.java new file mode 100644 index 00000000..0acb2a27 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/ints/TreeSequence.java @@ -0,0 +1,335 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.util.ints; + +import java.util.Iterator; +import java.util.NoSuchElementException; + + +/** + * An implementation of a sparse sequence based on a + * balanced binary search tree. + * + * @author Emina Torlak + */ +public final class TreeSequence extends AbstractSparseSequence + implements Cloneable { + private final IntTree> tree; + private int size; + /** + * Constructs an empty tree sequence. + * @ensures no this.entries' + */ + public TreeSequence() { + tree = new IntTree>(); + size = 0; + } + + /** + * Copy constructor. + * @ensures creatres a deep copy of the original + */ + @SuppressWarnings("unchecked") + private TreeSequence(TreeSequence original) { + this.size = original.size; + try { + this.tree = original.tree.clone(); + } catch (CloneNotSupportedException e) { + throw new InternalError(); // unreachable code; + } + } + + /** + * {@inheritDoc} + * @see kodkod.util.ints.SparseSequence#iterator(int, int) + */ + public Iterator> iterator(int from, int to) { + return from <= to ? new AscendingIterator(from, to) : new DescendingIterator(from, to); + } + + /** + * {@inheritDoc} + * @see kodkod.util.ints.SparseSequence#size() + */ + public int size() { + return size; + } + + /** + * Removes all entries from this sequences. + * @ensures no this.entries' + * @see kodkod.util.ints.SparseSequence#clear() + */ + public void clear() { + tree.clear(); + size = 0; + } + + /** + * {@inheritDoc} + * @see kodkod.util.ints.SparseSequence#put(int, Object) + */ + public V put(int index, V value) { + final Entry e = tree.search(index); + if (e==null) { + size++; + tree.insert(new Entry(index, value)); + return null; + } else { + return e.setValue(value); + } + } + + /** + * {@inheritDoc} + * @see kodkod.util.ints.SparseSequence#get(int) + */ + public V get(int index) { + final Entry e = tree.search(index); + return e==null ? null : e.value; + } + + /** + * Removes the entry with the given index, if it exists, and + * returns the value previously stored at the index. If the + * sequence had no previous mapping for the index, null is returned. + * @ensures this.entries' = this.entries - index->E + * @return this.entries[index] + * @see kodkod.util.ints.SparseSequence#remove(int) + */ + public V remove(int index) { + final Entry e = tree.search(index); + if (e==null) + return null; + else { + size--; + tree.delete(e); + return e.value; + } + } + + /** + * {@inheritDoc} + * @see kodkod.util.ints.SparseSequence#containsIndex(int) + */ + public boolean containsIndex(int index) { + return tree.search(index) != null; + } + + /** + * {@inheritDoc} + * @see kodkod.util.ints.SparseSequence#first() + */ + public IndexedEntry first() { + return tree.min(); + } + + /** + * {@inheritDoc} + * @see kodkod.util.ints.SparseSequence#last() + */ + public IndexedEntry last() { + return tree.max(); + } + + /** + * {@inheritDoc} + * @see kodkod.util.ints.SparseSequence#ceil(int) + */ + public IndexedEntry ceil(int index) { + return tree.searchGTE(index); + } + + /** + * {@inheritDoc} + * @see kodkod.util.ints.SparseSequence#floor(int) + */ + public IndexedEntry floor(int index) { + return tree.searchLTE(index); + } + + /** + * Returns a copy of this sparse sequence. The copy is independent of this + * sequence. + * @return a copy of this sparse sequence. + * @see kodkod.util.ints.SparseSequence#clone() + */ + @SuppressWarnings("unchecked") + public TreeSequence clone() { + // ok to use copy constructor to clone a final class + return new TreeSequence(this); + + } + +// public String toString() { +// return tree.toString(); +// } + + private static final class Entry extends IntTree.Node> implements IndexedEntry, Cloneable { + V value; + + Entry(int key, V value) { + super(key); + this.value = value; + } + + public int index() { + return key; + } + + public V value() { + return value; + } + + /** + * Sets this.value to the given value and returns the previous value. + * @ensures this.value' = value + * @requires this.value + */ + V setValue(V value) { + V oldValue = this.value; + this.value = value; + return oldValue; + } + + public boolean equals(Object o) { + if (o==this) return true; + if (!(o instanceof IndexedEntry)) return false; + return AbstractSparseSequence.equal(this, (IndexedEntry)o); + } + + public int hashCode() { + return AbstractSparseSequence.hashCode(this); + } + + public String toString() { + return key + "=" + value; + } + + @SuppressWarnings("unchecked") + protected Entry clone() throws CloneNotSupportedException { + return (Entry)super.clone(); + } + } + + + private abstract class EntryIterator implements Iterator> { + final int endIndex; + Entry lastReturned ; + Entry next; + + /** + * Constructs a tree iterator which traverses the tree starting at + * the given Entry in either descending or ascending order, depending + * on whether start.index is greater than endIndex. + */ + EntryIterator(Entry start, int endIndex) { + this.next = start; + this.lastReturned = null; + this.endIndex = endIndex; + } + + /** + * Advances the next pointer to its successor, + * if this is an ascending iterator or to + * its predecessor, if this is a descending iterator. + * @requires next != NIL + */ + abstract void advance(); + + public abstract boolean hasNext(); + + public IndexedEntry next() { + if (!hasNext()) + throw new NoSuchElementException(); + lastReturned = next; + advance(); + return lastReturned; + } + + public final void remove() { + if (lastReturned == null) + throw new IllegalStateException(); + if (next==null) { + TreeSequence.this.remove(lastReturned.key); + } else { + final int nextIndex = next.key; + TreeSequence.this.remove(lastReturned.key); + // necessary since the structural modifications made by the delete + // procedure may affect the next pointer + next = tree.search(nextIndex); + } + lastReturned = null; + } + } + + private final class AscendingIterator extends EntryIterator { + /** + * Constructs an ascending iterator over the entries with + * indeces between from and to. + * @requires from <= to + */ + AscendingIterator(int from, int to) { + super(tree.searchGTE(from),to); + } + /** + * Sets next to its successor. + */ + final void advance() { + next = tree.successor(next); + } + + /** + * Returns true if next != NIL and its index is less + * than or equal to the ending index. + */ + public boolean hasNext() { + return next != null && next.key<=endIndex; + } + } + + private final class DescendingIterator extends EntryIterator { + /** + * Constructs a descending iterator over the entries with + * indeces between from and to. + * @requires from >= to + */ + DescendingIterator(int from, int to) { + super(tree.searchLTE(from),to); + } + /** + * Sets next to its predecessor. + */ + final void advance() { + next = tree.predecessor(next); + } + + /** + * Returns true if next != NIL and its index is greater + * than or equal to the ending index. + */ + public boolean hasNext() { + return next != null && next.key>=endIndex; + } + } + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/ints/package.html b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/ints/package.html new file mode 100644 index 00000000..6202e784 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/ints/package.html @@ -0,0 +1,36 @@ + + + + + + + +Provides implementations of ordered collections for storing integer primitives. + +

Package Specification

+ +

Provides several implementations of ordered collections for storing integer primitives.

+ +

Related Documentation

+ +@see kodkod.util.ints.IntSet +@see kodkod.util.ints.IntVector +@see kodkod.util.ints.SparseSequence + + + diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/nodes/AnnotatedNode.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/nodes/AnnotatedNode.java new file mode 100644 index 00000000..73507c16 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/nodes/AnnotatedNode.java @@ -0,0 +1,533 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.util.nodes; + +import static kodkod.ast.RelationPredicate.Name.ACYCLIC; +import static kodkod.ast.RelationPredicate.Name.FUNCTION; +import static kodkod.ast.RelationPredicate.Name.TOTAL_ORDERING; +import static kodkod.ast.operator.FormulaOperator.AND; +import static kodkod.ast.operator.FormulaOperator.IMPLIES; +import static kodkod.ast.operator.FormulaOperator.OR; + +import java.util.Collections; +import java.util.EnumMap; +import java.util.IdentityHashMap; +import java.util.Map; +import java.util.Set; + +import kodkod.ast.BinaryFormula; +import kodkod.ast.ComparisonFormula; +import kodkod.ast.Comprehension; +import kodkod.ast.ConstantExpression; +import kodkod.ast.Decl; +import kodkod.ast.Decls; +import kodkod.ast.ExprToIntCast; +import kodkod.ast.Expression; +import kodkod.ast.Formula; +import kodkod.ast.IfExpression; +import kodkod.ast.IfIntExpression; +import kodkod.ast.IntComparisonFormula; +import kodkod.ast.IntToExprCast; +import kodkod.ast.MultiplicityFormula; +import kodkod.ast.NaryFormula; +import kodkod.ast.Node; +import kodkod.ast.NotFormula; +import kodkod.ast.QuantifiedFormula; +import kodkod.ast.Relation; +import kodkod.ast.RelationPredicate; +import kodkod.ast.SumExpression; +import kodkod.ast.Variable; +import kodkod.ast.operator.ExprCastOperator; +import kodkod.ast.operator.FormulaOperator; +import kodkod.ast.visitor.AbstractDetector; +import kodkod.ast.visitor.AbstractVoidVisitor; +import kodkod.util.collections.ArrayStack; +import kodkod.util.collections.IdentityHashSet; +import kodkod.util.collections.Stack; + +/** + * A node annotated with information about + * structural sharing in its ast/dag. The class + * also provides utility methods for collecting + * various information about annotated nodes. + * + * @specfield node: N // annotated node + * @specfield source: node.*components ->one Node // maps the subnodes of this.node to nodes from + * // which they were derived by some transformation process + * // (e.g. skolemization, predicate inlining) + * @author Emina Torlak + */ +public final class AnnotatedNode { + private final N node; + private final Set sharedNodes; + private final Map source; + + /** + * Constructs a new annotator for the given node. + * @ensures this.node' = node && this.source' = node.*components<:iden + */ + private AnnotatedNode(N node) { + this.node = node; + final SharingDetector detector = new SharingDetector(); + node.accept(detector); + this.sharedNodes = Collections.unmodifiableSet(detector.sharedNodes()); + this.source = Collections.emptyMap(); + } + + + /** + * Constructs a new annotator for the given node and source map. + * @ensures this.node' = node && this.source' = node.*components<:iden ++ source + */ + private AnnotatedNode(N node, Map source) { + this.node = node; + final SharingDetector detector = new SharingDetector(); + node.accept(detector); + this.sharedNodes = Collections.unmodifiableSet(detector.sharedNodes()); + this.source = source; + } + + /** + * Returns an annotation for the given node. The source map of the returned annotation object + * maps each descendant of the node to itself. + * @return { a: AnnotatedNode | a.node = node && a.source = node.*components<:iden } + */ + public static AnnotatedNode annotate(N node) { return new AnnotatedNode(node); } + + /** + * Returns an annotation for the given node. The source map of the returned annotation object + * maps each descendant of the node to its value in the given source map, or to itself + * if the given source map has no value for that descendant. + * @return { a: AnnotatedNode | a.node = node && a.source = (node.*components<:iden) ++ source } + */ + public static AnnotatedNode annotate(N node, Map source) { return new AnnotatedNode(node,source); } + + /** + * Returns an annotation for an n-ary conjunctions of {@linkplain Nodes#roots(Formula) roots} of the given formula. + * The source map of the returned annotation object maps each descendant of the node to itself. + * The root conjunction itself is mapped to the input formula. + * @return { a: AnnotatedNode | a.node = Formula.and(Nodes.roots(formula)) && a.source = (node.^components<:iden) + a.node->formula } + */ + public static AnnotatedNode annotateRoots(Formula formula) { + final Formula flat = Formula.and(Nodes.roots(formula)); + return new AnnotatedNode(flat, Collections.singletonMap(flat, formula)); + } + + /** + * Returns this.node. + * @return this.node + */ + public final N node() { + return node; + } + + /** + * Returns the source of the the given descendant of this.node. + * @requires n in this.node.*components + * @return this.source[n] + */ + public final Node sourceOf(Node n) { + final Node d = source.get(n); + return d==null ? n : d; + } + + + /** + * Returns the set of all non-leaf descendants of this.node that have more than one parent. + * @return {n: Node | some n.children && #(n.~components & this.node.*components) > 1 } + */ + public final Set sharedNodes() { + return sharedNodes; + } + + /** + * Returns the set of all relations at the leaves of this annotated node. + * @return Relation & this.node.*components + */ + public final Set relations() { + final Set relations = new IdentityHashSet(); + final AbstractVoidVisitor visitor = new AbstractVoidVisitor() { + private final Set visited = new IdentityHashSet(sharedNodes.size()); + protected boolean visited(Node n) { + return sharedNodes.contains(n) && !visited.add(n); + } + public void visit(Relation relation) { + relations.add(relation); + } + }; + node.accept(visitor); + return relations; + } + + /** + * Returns true if this.node contains a child whose meaning depends on + * integer bounds (i.e. an ExprToIntCast node with SUM operator or an IntToExprCast node or Expression.INTS constant). + * @return true if this.node contains a child whose meaning depends on + * integer bounds (i.e. an ExprToIntCast node with SUM operator or an IntToExprCast node or Expression.INTS constant). + */ + public final boolean usesInts() { + final AbstractDetector detector = new AbstractDetector(sharedNodes) { + public Boolean visit(IntToExprCast expr) { + return cache(expr, Boolean.TRUE); + } + public Boolean visit(ExprToIntCast intExpr) { + if (intExpr.op()==ExprCastOperator.CARDINALITY) + super.visit(intExpr); + return cache(intExpr, Boolean.TRUE); + } + public Boolean visit(ConstantExpression expr) { + return expr==Expression.INTS ? Boolean.TRUE : Boolean.FALSE; + } + }; + return (Boolean)node.accept(detector); + } + + /** + * Returns a map of RelationPredicate names to sets of top-level relation predicates with + * the corresponding names in this.node. + * @return a map of RelationPredicate names to sets of top-level relation predicates with + * the corresponding names in this.node. A predicate is considered 'top-level' + * if it is a component of the top-level conjunction, if any, of this.node. + */ + public final Map> predicates() { + final PredicateCollector collector = new PredicateCollector(sharedNodes); + node.accept(collector); + return collector.preds; + } + + /** + * Returns a Detector that will return TRUE when applied to a descendent + * of this.node iff the descendent contains a quantified formula. + * @return a Detector that will return TRUE when applied to a descendent + * of this.node iff the descendent contains a quantified formula. + */ + public final AbstractDetector quantifiedFormulaDetector() { + return new AbstractDetector(sharedNodes) { + public Boolean visit(QuantifiedFormula quantFormula) { + return cache(quantFormula, true); + } + }; + } + + /** + * Returns a Detector that will return TRUE when applied to a descendent + * of this.node iff the descendent contains a free variable. + * @return a Detector that will return TRUE when applied to a descendent + * of this.node iff the descendent contains a free variable. + */ + public final AbstractDetector freeVariableDetector() { + return new FreeVariableDetector(sharedNodes); + } + + /** + * Returns a string representation of this annotated node. + * @return string representation of this annotated node. + */ + public String toString() { + final StringBuilder ret = new StringBuilder(); + ret.append("node: "); + ret.append(node); + ret.append("\nshared nodes: "); + ret.append(sharedNodes); + ret.append("\nsources: "); + ret.append(source); + return ret.toString(); + } + + /** + * Detects shared non-leaf descendents of a given node. + * + * @specfield node: Node // node to which the analyzer is applied + */ + private static final class SharingDetector extends AbstractVoidVisitor { + /* maps each internal node with more than one parent to TRUE and all + * other internal nodes to FALSE */ + final IdentityHashMap sharingStatus; + /* @invariant numShareNodes = #sharingStatus.TRUE */ + int numSharedNodes; + + SharingDetector() { + sharingStatus = new IdentityHashMap(); + } + + /** + * Returns the shared internal nodes of this.node. This method should + * be called only after this visitor has been applied to this.node. + * @return {n: Node | #(n.~children & node.*children) > 1 } + */ + IdentityHashSet sharedNodes() { + final IdentityHashSet shared = new IdentityHashSet(numSharedNodes); + for(Map.Entry entry : sharingStatus.entrySet()) { + if (entry.getValue()==Boolean.TRUE) + shared.add(entry.getKey()); + } + return shared; + } + + /** + * Records the visit to the given node in the status map. + * If the node has not been visited before, it is mapped + * to Boolean.FALSE and false is returned. Otherwise, + * it is mapped to Boolean.TRUE and true is returned. + * The first time a Node is mapped to true, numSharedNodes + * is incremented by one. + * @ensures no this.shared[node] => this.shared' = this.shared + node->FALSE, + * this.shared[node] = FALSE => this.shared' = this.shared + node->TRUE, + * this.shared' = this.shared + * @return this.shared'[node] + */ + protected final boolean visited(Node node) { + Boolean status = sharingStatus.get(node); + if (!Boolean.TRUE.equals(status)) { + if (status==null) { + status = Boolean.FALSE; + } else { // status == Boolean.FALSE + status = Boolean.TRUE; + numSharedNodes++; + } + sharingStatus.put(node,status); + } + return status; + } + } + + /** + * A visitor that detects free variables of a node. + * @author Emina Torlak + */ + private static final class FreeVariableDetector extends AbstractDetector { + /* Holds the variables that are currently in scope, with the + * variable at the top of the stack being the last declared variable. */ + + private final Stack varsInScope = new ArrayStack(); + + /** + * Constructs a new free variable detector. + */ + FreeVariableDetector(Set sharedNodes) { + super(sharedNodes); + } + + /** + * Visits the given comprehension, quantified formula, or sum expression. + * The method returns TRUE if the creator body contains any + * variable not bound by the decls; otherwise returns FALSE. + */ + private Boolean visit(Node creator, Decls decls, Node body) { + Boolean ret = lookup(creator); + if (ret!=null) return ret; + boolean retVal = false; + for(Decl decl : decls) { + retVal = decl.expression().accept(this) || retVal; + varsInScope.push(decl.variable()); + } + retVal = ((Boolean)body.accept(this)) || retVal; + for(int i = decls.size(); i > 0; i--) { + varsInScope.pop(); + } + return cache(creator, retVal); + } + /** + * Returns TRUE if the given variable is free in its parent, otherwise returns FALSE. + * @return TRUE if the given variable is free in its parent, otherwise returns FALSE. + */ + public Boolean visit(Variable variable) { + return Boolean.valueOf(varsInScope.search(variable)<0); + } + public Boolean visit(Decl decl) { + Boolean ret = lookup(decl); + if (ret!=null) return ret; + return cache(decl, decl.expression().accept(this)); + } + public Boolean visit(Comprehension comprehension) { + return visit(comprehension, comprehension.decls(), comprehension.formula()); + } + public Boolean visit(SumExpression intExpr) { + return visit(intExpr, intExpr.decls(), intExpr.intExpr()); + } + public Boolean visit(QuantifiedFormula qformula) { + return visit(qformula, qformula.decls(), qformula.formula()); + } + } + + /** + * A visitor that detects and collects + * top-level relation predicates; i.e. predicates that + * are components in the top-level conjunction, if any, on ANY + * path starting at the root. + */ + private static final class PredicateCollector extends AbstractVoidVisitor { + protected boolean negated; + private final Set sharedNodes; + /* if a given node is not mapped at all, it means that it has not been visited; + * if it is mapped to FALSE, it has been visited with negated=FALSE, + * if it is mapped to TRUE, it has been visited with negated=TRUE, + * if it is mapped to null, it has been visited with both values of negated. */ + private final Map visited; + /* holds the top level predicates at the the end of the visit*/ + final EnumMap> preds; + /** + * Constructs a new collector. + * @ensures this.negated' = false + */ + PredicateCollector(Set sharedNodes) { + this.sharedNodes = sharedNodes; + this.visited = new IdentityHashMap(); + this.negated = false; + preds = new EnumMap>(RelationPredicate.Name.class); + preds.put(ACYCLIC, new IdentityHashSet(4)); + preds.put(TOTAL_ORDERING, new IdentityHashSet(4)); + preds.put(FUNCTION, new IdentityHashSet(8)); + } + /** + * Returns true if n has already been visited with the current value of the + * negated flag; otherwise returns false. + * @ensures records that n is being visited with the current value of the negated flag + * @return true if n has already been visited with the current value of the + * negated flag; otherwise returns false. + */ + @Override + protected final boolean visited(Node n) { + if (sharedNodes.contains(n)) { + if (!visited.containsKey(n)) { // first visit + visited.put(n, Boolean.valueOf(negated)); + return false; + } else { + final Boolean visit = visited.get(n); + if (visit==null || visit==negated) { // already visited with same negated value + return true; + } else { // already visited with different negated value + visited.put(n, null); + return false; + } + } + } + return false; + } + /** + * Calls visited(comp); comp's children are not top-level formulas + * so they are not visited. + */ + public void visit(Comprehension comp) { + visited(comp); + } + /** + * Calls visited(ifexpr); ifexpr's children are not top-level formulas + * so they are not visited. + */ + public void visit(IfExpression ifexpr) { + visited(ifexpr); + } + /** + * Calls visited(ifexpr); ifexpr's children are not top-level formulas + * so they are not visited. + */ + public void visit(IfIntExpression ifexpr) { + visited(ifexpr); + } + /** + * Calls visited(intComp); intComp's children are not top-level formulas + * so they are not visited. + */ + public void visit(IntComparisonFormula intComp) { + visited(intComp); + } + /** + * Calls visited(quantFormula); quantFormula's children are not top-level formulas + * so they are not visited. + */ + public void visit(QuantifiedFormula quantFormula) { + visited(quantFormula); + } + /** + * Visits the children of the given formula if it has not been visited already with + * the given value of the negated flag and if binFormula.op==IMPLIES && negated or + * binFormula.op==AND && !negated or binFormula.op==OR && negated. Otherwise does nothing. + * @see kodkod.ast.visitor.AbstractVoidVisitor#visit(kodkod.ast.BinaryFormula) + */ + public void visit(BinaryFormula binFormula) { + if (visited(binFormula)) return; + final FormulaOperator op = binFormula.op(); + + if ((!negated && op==AND) || (negated && op==OR)) { // op==AND || op==OR + binFormula.left().accept(this); + binFormula.right().accept(this); + } else if (negated && op==IMPLIES) { // !(a => b) = !(!a || b) = a && !b + negated = !negated; + binFormula.left().accept(this); + negated = !negated; + binFormula.right().accept(this); + } + } + /** + * Visits the children of the given formula if it has not been visited already with + * the given value of the negated flag and if formula.op==OR && negated or + * formula.op==AND && !negated. Otherwise does nothing. + * @see kodkod.ast.visitor.AbstractVoidVisitor#visit(kodkod.ast.NaryFormula) + */ + public void visit(NaryFormula formula) { + if (visited(formula)) return; + final FormulaOperator op = formula.op(); + if ((!negated && op==AND) || (negated && op==OR)) { // op==AND || op==OR + for(Formula child : formula) { + child.accept(this); + } + } + } + + /** + * Visits the children of the child of the child formula, with + * the negation of the current value of the negated flag, + * if it has not already been visited + * with the current value of this.negated; otherwise does nothing. + */ + public void visit(NotFormula not) { + if (visited(not)) return; + negated = !negated; + not.formula().accept(this); + negated = !negated; + } + /** + * Calls visited(compFormula); compFormula's children are not top-level formulas + * so they are not visited. + */ + public void visit(ComparisonFormula compFormula) { + visited(compFormula); + } + /** + * Calls visited(multFormula); multFormula's child is not top-level formulas + * so it is not visited. + */ + public void visit(MultiplicityFormula multFormula) { + visited(multFormula); + } + /** + * Records the visit to this predicate if it is not negated. + */ + public void visit(RelationPredicate pred) { + if (visited(pred)) return; + if (!negated) { + preds.get(pred.name()).add(pred); + } + } + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/nodes/Nodes.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/nodes/Nodes.java new file mode 100644 index 00000000..bcb03a6e --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/nodes/Nodes.java @@ -0,0 +1,199 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.util.nodes; + +import java.util.AbstractSet; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.ListIterator; +import java.util.Set; + +import kodkod.ast.BinaryFormula; +import kodkod.ast.Formula; +import kodkod.ast.NaryFormula; +import kodkod.ast.Node; +import kodkod.ast.operator.FormulaOperator; +import kodkod.ast.visitor.AbstractDetector; +import kodkod.ast.visitor.AbstractVoidVisitor; +import kodkod.ast.visitor.VoidVisitor; +import kodkod.util.collections.Containers; +import kodkod.util.collections.IdentityHashSet; + +/** + * Provides utility methods for extracting roots (top-level) conjuncts + * of Kodkod formulas + * + * @author Emina Torlak + */ +public final class Nodes { + private Nodes() {} + + /** + * Returns the roots of the given formula. + * In other words, breaks up the given formula into its conjunctive + * components, {f0, ..., fk}, + * such that, for all 0<=i<=k, fi is not a conjunction and + * [[f0 && ... && fk]] <=> [[formula]]. + * @return subformulas, {f0, ..., fk}, of the given formula such that, for all 0<=i<=k, + * fi is not a conjuction and [[f0 && ... && fk]] <=> [[formula]]. + */ + public static Set roots(Formula formula) { + + final List formulas = new LinkedList(); + formulas.add(formula); + + final ListIterator itr = formulas.listIterator(); + while(itr.hasNext()) { + final Formula f = itr.next(); + if (f instanceof BinaryFormula) { + final BinaryFormula bin = (BinaryFormula) f; + if (bin.op()==FormulaOperator.AND) { + itr.remove(); + itr.add(bin.left()); + itr.add(bin.right()); + itr.previous(); + itr.previous(); + } + } else if (f instanceof NaryFormula) { + final NaryFormula nf = (NaryFormula) f; + if (nf.op()==FormulaOperator.AND) { + itr.remove(); + for(Formula child : nf) { + itr.add(child); + } + for(int i = nf.size(); i>0; i--) { + itr.previous(); + } + } + } + } + + return new LinkedHashSet(formulas); + } + + /** + * Returns an unmodifiable set consisting of the children of the given formula, if the formula is a binary or an nary conjunction. Otherwise + * returns a singleton set containing the formula itself. + * @return an unmodifiable set consisting of children of the given formula, if the formula is a binary or an nary conjunction. Otherwise + * returns a singleton set containing the formula itself. + */ + public static Set conjuncts(Formula formula) { + if (formula instanceof BinaryFormula) { + final BinaryFormula bin = (BinaryFormula) formula; + if (bin.op()==FormulaOperator.AND) { + final Formula left = bin.left(), right = bin.right(); + if (left==right) return Collections.singleton(left); + else return new AbstractSet() { + @Override + public boolean contains(Object o) { return left==o || right==o; } + @Override + public Iterator iterator() { return Containers.iterate(left, right); } + @Override + public int size() { return 2; } + + }; + } + } else if (formula instanceof NaryFormula) { + final NaryFormula nf = (NaryFormula) formula; + if (nf.op()==FormulaOperator.AND) { + final LinkedHashSet children = new LinkedHashSet(1+(nf.size()*4)/3); + for(Formula child : nf) { children.add(child); } + return Collections.unmodifiableSet(children); + } + } + + return Collections.singleton(formula); + + } + + /** + * Returns a minimal subset of {@linkplain #roots(Formula) roots} of the given formula such that all nodes in the given collection + * are reachable from those roots. The returned subset is a local minimum in that none of its members can be removed without leaving + * some node in the descendants set unreachable from the remaining roots. + * @requires descendants in formula.*components + * @return { s: Set | s.elements in roots(formula) and descendants in s.elements.*components and + * no s': Set | s.containsAll(s') and s'.size() minRoots(Formula formula, Collection descendants) { + + final Set desc = new IdentityHashSet(descendants); + final VoidVisitor visitor = new AbstractVoidVisitor() { + final Set visited = new IdentityHashSet(); + @Override + protected boolean visited(Node n) { + if (visited.add(n)) { + desc.remove(n); + return false; + } + return true; + } + }; + + final Set roots = new LinkedHashSet(); + for(Formula root : roots(formula)) { + final int size = desc.size(); + root.accept(visitor); + if (desc.size() allRoots(Formula formula, Collection descendants) { + final Set desc = new IdentityHashSet(descendants); + final AbstractDetector detector = new AbstractDetector(Collections.EMPTY_SET) { + protected Boolean lookup(Node n) { + return desc.contains(n) ? Boolean.TRUE : cache.get(n); + } + protected Boolean cache(Node n, boolean val) { + final Boolean ret = Boolean.valueOf(val); + cache.put(n, ret); + return ret; + } + }; + + final Set roots = new LinkedHashSet(); + for(Formula root : roots(formula)) { + if (root.accept(detector)) { + roots.add(root); + } + } + + return roots; + } + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/nodes/PrettyPrinter.java b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/nodes/PrettyPrinter.java new file mode 100644 index 00000000..5c2debf3 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/nodes/PrettyPrinter.java @@ -0,0 +1,769 @@ +/* + * Kodkod -- Copyright (c) 2005-2011, Emina Torlak + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package kodkod.util.nodes; + +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; + +import kodkod.ast.BinaryExpression; +import kodkod.ast.BinaryFormula; +import kodkod.ast.BinaryIntExpression; +import kodkod.ast.ComparisonFormula; +import kodkod.ast.Comprehension; +import kodkod.ast.ConstantExpression; +import kodkod.ast.ConstantFormula; +import kodkod.ast.Decl; +import kodkod.ast.Decls; +import kodkod.ast.ExprToIntCast; +import kodkod.ast.Expression; +import kodkod.ast.Formula; +import kodkod.ast.IfExpression; +import kodkod.ast.IfIntExpression; +import kodkod.ast.IntComparisonFormula; +import kodkod.ast.IntConstant; +import kodkod.ast.IntExpression; +import kodkod.ast.IntToExprCast; +import kodkod.ast.LeafExpression; +import kodkod.ast.MultiplicityFormula; +import kodkod.ast.NaryExpression; +import kodkod.ast.NaryFormula; +import kodkod.ast.NaryIntExpression; +import kodkod.ast.Node; +import kodkod.ast.NotFormula; +import kodkod.ast.ProjectExpression; +import kodkod.ast.QuantifiedFormula; +import kodkod.ast.Relation; +import kodkod.ast.RelationPredicate; +import kodkod.ast.SumExpression; +import kodkod.ast.UnaryExpression; +import kodkod.ast.UnaryIntExpression; +import kodkod.ast.Variable; +import kodkod.ast.operator.ExprOperator; +import kodkod.ast.operator.FormulaOperator; +import kodkod.ast.operator.IntOperator; +import kodkod.ast.operator.Multiplicity; +import kodkod.ast.visitor.VoidVisitor; + +/** + * Pretty-prints Kodkod nodes. + * + * @author Emina Torlak + */ +public final class PrettyPrinter { + + /** + * Returns a dot representation of the given node that can be visualized with GraphViz. + * @return a dot representation of the given node that can be visualized with GraphViz. + */ + public static String dotify(Node node) { + return Dotifier.apply(node); + } + /** + * Returns a pretty-printed string representation of the + * given node, with each line offset by at least the given + * number of whitespaces. The line parameter determines the + * length of each pretty-printed line, including the offset. + * @requires 0 <= offset < line + * @return a pretty-printed string representation of the + * given node + */ + public static String print(Node node, int offset, int line) { + final Formatter formatter = new Formatter(offset,line); + node.accept(formatter); + return formatter.tokens.toString(); + } + + /** + * Returns a pretty-printed string representation of the + * given node, with each line offset by at least the given + * number of whitespaces. + * @requires 0 <= offset < 80 + * @return a pretty-printed string representation of the + * given node + */ + public static String print(Node node, int offset) { + return print(node,offset,80); + } + + /** + * Returns a pretty-printed string representation of the + * given formulas, with each line offset by at least the given + * number of whitespaces. + * @requires 0 <= offset < 80 + * @return a pretty-printed string representation of the + * given formulas + */ + public static String print(Set formulas, int offset) { + return print(formulas,offset,80); + } + + /** + * Returns a pretty-printed string representation of the + * given formulas, with each line offset by at least the given + * number of whitespaces. The line parameter determines the + * length of each pretty-printed line, including the offset. + * @requires 0 <= offset < line + * @return a pretty-printed string representation of the + * given formulas + */ + public static String print(Set formulas, int offset, int line) { + final Formatter formatter = new Formatter(offset,line); + for(Formula f : formulas) { + f.accept(formatter); + formatter.newline(); + } + return formatter.tokens.toString(); + } + + + + /** + * Generates a buffer of tokens comprising the string representation + * of a given node. The buffer contains at least the parentheses + * needed to correctly represent the node's tree structure. + * + * @specfield tokens: seq + * @author Emina Torlak + */ + private static class Formatter implements VoidVisitor { + final StringBuilder tokens ; + //final int offset; + private final int lineLength; + private int indent, lineStart; + + /** + * Constructs a new tokenizer. + * @ensures no this.tokens + */ + Formatter(int offset, int line) { + assert offset >= 0 && offset < line; + this.tokens = new StringBuilder(); + //this.offset = offset; + this.lineLength = line; + this.lineStart = 0; + this.indent = offset; + indent(); + } + + /*--------------FORMATTERS---------------*/ + + + /** @ensures this.tokens' = concat [ this.tokens, " ", token, " " ]*/ + private void infix(Object token) { + space(); + tokens.append(token); + space(); + } + + /** @ensures this.tokens' = concat [ this.tokens, token, " " ]*/ + private void keyword(Object token) { + append(token); + space(); + } + + /** @ensures this.tokens' = concat [ this.tokens, ", " ]*/ + private void comma() { + tokens.append(","); + space(); + } + + /** @ensures this.tokens' = concat [ this.tokens, ": " ]*/ + private void colon() { + tokens.append(":"); + space(); + } + + /** @ensures adds this.indent spaces to this.tokens */ + private void indent() { for(int i = 0; i < indent; i++) { space(); } } + + /** @ensures adds newline plus this.indent spaces to this.tokens */ + private void newline() { + tokens.append("\n"); + lineStart = tokens.length(); + indent(); + } + + /** @ensures this.tokens' = concat[ this.tokens, " " ] **/ + private void space() { tokens.append(" "); } + + /** @ensures this.tokens' = concat [ this.tokens, token ]*/ + private void append(Object token) { + final String str = String.valueOf(token); + if ((tokens.length() - lineStart + str.length()) > lineLength) { + newline(); + } + tokens.append(str); + } + + /*--------------LEAVES---------------*/ + /** @ensures this.tokens' = concat[ this.tokens, node ] */ + public void visit(Relation node) { append(node); } + + /** @ensures this.tokens' = concat[ this.tokens, node ] */ + public void visit(Variable node) { append(node); } + + /** @ensures this.tokens' = concat[ this.tokens, node ] */ + public void visit(ConstantExpression node) { append(node); } + + /** @ensures this.tokens' = concat[ this.tokens, node ] */ + public void visit(IntConstant node) { append(node); } + + /** @ensures this.tokens' = concat[ this.tokens, node ] */ + public void visit(ConstantFormula node) { append(node); } + + /*--------------DECLARATIONS---------------*/ + /** + * @ensures this.tokens' = + * concat[ this.tokens, tokenize[ node.variable ], ":", tokenize[ node.expression ] + **/ + public void visit(Decl node) { + node.variable().accept(this); + colon(); + if (node.multiplicity()!=Multiplicity.ONE) { + append(node.multiplicity()); + space(); + } + node.expression().accept(this); + } + + /** + * @ensures this.tokens' = + * concat[ this.tokens, tokenize[ node.declarations[0] ], ",", + * ..., tokenize[ node.declarations[ node.size() - 1 ] ] ] + **/ + public void visit(Decls node) { + Iterator decls = node.iterator(); + decls.next().accept(this); + while(decls.hasNext()) { + comma(); + decls.next().accept(this); + } + } + + /*--------------UNARY NODES---------------*/ + + /** @ensures this.tokenize' = + * (parenthesize => concat [ this.tokens, "(", tokenize[child], ")" ] else + * concat [ this.tokens, tokenize[child] ]*/ + private void visitChild(Node child, boolean parenthesize) { + if (parenthesize) { append("("); } + child.accept(this); + if (parenthesize) { append(")"); } + } + + /** @return true if the given expression should be parenthesized when a + * child of a compound parent */ + private boolean parenthesize(Expression child) { + return child instanceof BinaryExpression || child instanceof IfExpression; + } + + /** @return true if the given expression should be parenthesized when a + * child of a compound parent */ + private boolean parenthesize(IntExpression child) { + return !(child instanceof UnaryIntExpression || + child instanceof IntConstant || + child instanceof ExprToIntCast); + } + + /** @return true if the given formula should be parenthesized when a + * child of a compound parent */ + private boolean parenthesize(Formula child) { + return !(child instanceof NotFormula || child instanceof ConstantFormula || + child instanceof RelationPredicate); + } + + /** @ensures appends the given op and child to this.tokens; the child is + * parenthesized if it's an instance of binary expression or an if expression. **/ + public void visit(UnaryExpression node) { + append(node.op()); + visitChild(node.expression(), parenthesize(node.expression())); + } + + + /** @ensures appends the given op and child to this.tokens; the child is + * parenthesized if it's not an instance of unary int expression or int constant. **/ + public void visit(UnaryIntExpression node) { + final IntExpression child = node.intExpr(); + final IntOperator op = node.op(); + final boolean parens = + (op==IntOperator.ABS) || (op==IntOperator.SGN) || + parenthesize(child); + append(node.op()); + visitChild(child, parens); + } + + /** @ensures appends the given op and child to this.tokens; the child is + * parenthesized if it's not an instance of not formula, constant formula, or + * relation predicate. **/ + public void visit(NotFormula node) { + append("!"); + final boolean pchild = parenthesize(node.formula()); + indent += pchild ? 2 : 1; + visitChild(node.formula(), parenthesize(node.formula())); + indent -= pchild ? 2 : 1; + } + + /** @ensures appends the given op and child to this.tokens; the child is + * parenthesized if it's an instance of binary expression or an if expression. **/ + public void visit(MultiplicityFormula node) { + keyword(node.multiplicity()); + visitChild(node.expression(), parenthesize(node.expression())); + } + + /*--------------BINARY NODES---------------*/ + + /** @return true if the given expression needs to be parenthesized if a + * child of a binary expression with the given operator */ + private boolean parenthesize(ExprOperator op, Expression child) { + return child instanceof IfExpression || + child instanceof NaryExpression || + (child instanceof BinaryExpression && + (op==ExprOperator.JOIN || + ((BinaryExpression)child).op()!=op)); + } + + /** @ensures appends the tokenization of the given node to this.tokens */ + public void visit(BinaryExpression node) { + final ExprOperator op = node.op(); + visitChild(node.left(), parenthesize(op, node.left())); + infix(op); + visitChild(node.right(), parenthesize(op, node.right())); + } + + + + /** @return true if the given operator is assocative */ + private boolean associative(IntOperator op) { + switch(op) { + case DIVIDE : case MODULO : case SHA : case SHL : case SHR : return false; + default : return true; + } + } + + /** @return true if the given int expression needs to be parenthesized if a + * child of a binary int expression with the given operator */ + private boolean parenthesize(IntOperator op, IntExpression child) { + return child instanceof SumExpression || + child instanceof IfIntExpression || + child instanceof NaryIntExpression || + (child instanceof BinaryIntExpression && + (!associative(op) || ((BinaryIntExpression)child).op()!=op)); + } + + /** @ensures appends the tokenization of the given node to this.tokens */ + public void visit(BinaryIntExpression node) { + final IntOperator op = node.op(); + visitChild(node.left(), parenthesize(op, node.left())); + infix(op); + visitChild(node.right(), parenthesize(op, node.right())); + } + + /** @return true if the given formula needs to be parenthesized if a + * child of a binary formula with the given operator */ + private boolean parenthesize(FormulaOperator op, Formula child) { + return child instanceof QuantifiedFormula || + //child instanceof NaryFormula || + (child instanceof BinaryFormula && + (op==FormulaOperator.IMPLIES || + ((BinaryFormula)child).op()!=op)); + } + + /** @ensures appends the tokenization of the given node to this.tokens */ + public void visit(BinaryFormula node) { + final FormulaOperator op = node.op(); + final boolean pleft = parenthesize(op, node.left()); + if (pleft) indent++; + visitChild(node.left(), pleft); + if (pleft) indent--; + infix(op); + newline(); + final boolean pright = parenthesize(op, node.right()); + if (pright) indent++; + visitChild(node.right(), pright); + if (pright) indent--; + } + + /** @ensures this.tokens' = concat[ this.tokens, tokenize[node.left], node.op, tokenize[node.right] */ + public void visit(ComparisonFormula node) { + visitChild(node.left(), parenthesize(node.left())); + infix(node.op()); + visitChild(node.right(), parenthesize(node.right())); + } + + /** @ensures this.tokens' = concat[ this.tokens, tokenize[node.left], node.op, tokenize[node.right] */ + public void visit(IntComparisonFormula node) { + visitChild(node.left(), parenthesize(node.left())); + infix(node.op()); + visitChild(node.right(), parenthesize(node.right())); + } + + /*--------------TERNARY NODES---------------*/ + + /** @ensures appends the tokenization of the given node to this.tokens */ + public void visit(IfExpression node) { + visitChild(node.condition(), parenthesize(node.condition())); + infix("=>"); + indent++; + newline(); + visitChild(node.thenExpr(), parenthesize(node.thenExpr())); + infix("else"); + newline(); + visitChild(node.elseExpr(), parenthesize(node.elseExpr())); + indent--; + } + + /** @ensures appends the tokenization of the given node to this.tokens */ + public void visit(IfIntExpression node) { + visitChild(node.condition(), parenthesize(node.condition())); + infix("=>"); + indent++; + newline(); + visitChild(node.thenExpr(), parenthesize(node.thenExpr())); + infix("else"); + newline(); + visitChild(node.elseExpr(), parenthesize(node.elseExpr())); + indent--; + } + + /*--------------VARIABLE CREATOR NODES---------------*/ + /** @ensures this.tokens' = concat[ this.tokens, + * "{", tokenize[node.decls], "|", tokenize[ node.formula ], "}" ]*/ + public void visit(Comprehension node) { + append("{"); + node.decls().accept(this); + infix("|"); + node.formula().accept(this); + append("}"); + } + + /** @ensures this.tokens' = concat[ this.tokens, "sum", + * tokenize[node.decls], "|", tokenize[ node.intExpr ], ]*/ + public void visit(SumExpression node) { + keyword("sum"); + node.decls().accept(this); + infix("|"); + node.intExpr().accept(this); + } + + /** @ensures this.tokens' = concat[ this.tokens, node.quantifier, + * tokenize[node.decls], "|", tokenize[ node.formula ] ]*/ + public void visit(QuantifiedFormula node) { + keyword(node.quantifier()); + node.decls().accept(this); + infix("|"); + indent++; + newline(); + node.formula().accept(this); + indent--; + } + + /*--------------NARY NODES---------------*/ + + /** @ensures appends the tokenization of the given node to this.tokens */ + public void visit(NaryExpression node) { + final ExprOperator op = node.op(); + visitChild(node.child(0), parenthesize(op, node.child(0))); + for(int i = 1, size = node.size(); i < size; i++) { + infix(op); + visitChild(node.child(i), parenthesize(op, node.child(i))); + } + } + /** @ensures appends the tokenization of the given node to this.tokens */ + public void visit(NaryIntExpression node) { + final IntOperator op = node.op(); + visitChild(node.child(0), parenthesize(op, node.child(0))); + for(int i = 1, size = node.size(); i < size; i++) { + infix(op); + visitChild(node.child(i), parenthesize(op, node.child(i))); + } + } + /** @ensures appends the tokenization of the given node to this.tokens */ + public void visit(NaryFormula node) { + final FormulaOperator op = node.op(); + boolean parens = parenthesize(op, node.child(0)); + if (parens) indent++; + visitChild(node.child(0), parens); + if (parens) indent--; + for(int i = 1, size = node.size(); i < size; i++) { + infix(op); + newline(); + parens = parenthesize(op, node.child(i)); + if (parens) indent++; + visitChild(node.child(i), parens); + if (parens) indent--; + } + } + /*--------------OTHER NODES---------------*/ + + /** @ensures appends the tokenization of the given node to this.tokens */ + public void visit(ProjectExpression node) { + append("project"); + append("["); + node.expression().accept(this); + comma(); + append("<"); + final Iterator cols = node.columns(); + cols.next().accept(this); + while(cols.hasNext()) { + comma(); + cols.next().accept(this); + } + append(">"); + append("]"); + } + + /** @ensures this.tokens' = concat[ this.tokens, "Int","[", + * tokenize[node.intExpr], "]" ] **/ + public void visit(IntToExprCast node) { + append("Int"); + append("["); + node.intExpr().accept(this); + append("]"); + } + + /** @ensures this.tokens' = concat[ this.tokens, "int","[", + * tokenize[node.expression], "]" ] **/ + public void visit(ExprToIntCast node) { + switch(node.op()) { + case SUM: + append("int"); + append("["); + node.expression().accept(this); + append("]"); + break; + case CARDINALITY : + append("#"); + append("("); + node.expression().accept(this); + append(")"); + break; + default : throw new IllegalArgumentException("unknown operator: " + node.op()); + } + + } + + /** @ensures appends the tokenization of the given node to this.tokens */ + public void visit(RelationPredicate node) { + switch(node.name()) { + case ACYCLIC : + append("acyclic"); + append("["); + node.relation().accept(this); + append("]"); + break; + case FUNCTION : + RelationPredicate.Function func = (RelationPredicate.Function)node; + append("function"); + append("["); + func.relation().accept(this); + colon(); + func.domain().accept(this); + infix("->"); + keyword(func.targetMult()); + func.range().accept(this); + append("]"); + break; + case TOTAL_ORDERING : + RelationPredicate.TotalOrdering ord = (RelationPredicate.TotalOrdering)node; + append("ord"); + append("["); + ord.relation().accept(this); + comma(); + ord.ordered().accept(this); + comma(); + ord.first().accept(this); + comma(); + ord.last().accept(this); + append("]"); + break; + default: + throw new AssertionError("unreachable"); + } + + } + + } + + private static class Dotifier implements VoidVisitor { + private final StringBuilder graph = new StringBuilder(); + private final Map ids = new LinkedHashMap(); + + static String apply(Node node) { + final Dotifier dot = new Dotifier(); + dot.graph.append("digraph {\n"); + node.accept(dot); + dot.graph.append("}"); + return dot.graph.toString(); + } + + + private boolean visited(Node n) { + if (ids.containsKey(n)) return true; + ids.put(n, ids.size()); + return false; + } + + private String id(Node n) { return "N" + ids.get(n); } + + private void node(Node n, String label) { + graph.append(id(n)); + graph.append("[ label=\"" ); + graph.append(ids.get(n)); + graph.append("("); + graph.append(label); + graph.append(")\"];\n"); + } + + private void edge(Node n1, Node n2) { + if (n2 instanceof LeafExpression || n2 instanceof ConstantFormula || n2 instanceof IntConstant) { + + } + graph.append(id(n1)); + graph.append("->"); + graph.append(id(n2)); + graph.append(";\n"); + } + + private void visit(Node parent, Object label) { + if (visited(parent)) return; + node(parent, label.toString()); + } + + private void visit(Node parent, Object label, Node child) { + if (visited(parent)) return; + node(parent, label.toString()); + child.accept(this); + edge(parent, child); + } + + private void visit(Node parent, Object label, Node left, Node right) { + if (visited(parent)) return; + node(parent, label.toString()); + left.accept(this); + right.accept(this); + edge(parent, left); + edge(parent, right); + } + + private void visit(Node parent, Object label, Node left, Node middle, Node right) { + if (visited(parent)) return; + node(parent, label.toString()); + left.accept(this); + middle.accept(this); + right.accept(this); + edge(parent, left); + edge(parent, middle); + edge(parent, right); + } + + private void visit(Node parent, Object label, Iterator children) { + if (visited(parent)) return; + node(parent, label.toString()); + while(children.hasNext()) { + Node child = children.next(); + child.accept(this); + edge(parent, child); + } + } + + private void visit(Node parent, Object label, Node child, Iterator children) { + if (visited(parent)) return; + node(parent, label.toString()); + child.accept(this); + edge(parent, child); + while(children.hasNext()) { + Node other = children.next(); + other.accept(this); + edge(parent, other); + } + } + + public void visit(Decls decls) { visit(decls, "decls", decls.iterator()); } + + public void visit(Decl decl) { visit(decl, "decl", decl.variable(), decl.expression()); } + + public void visit(Relation relation) { visit(relation, relation.name()); } + public void visit(Variable variable) { visit(variable, variable.name()); } + public void visit(ConstantExpression constExpr) { visit(constExpr, constExpr.name()); } + + + public void visit(NaryExpression expr) { + visit(expr, expr.op(), expr.iterator()); + } + public void visit(BinaryExpression binExpr) { + visit(binExpr, binExpr.op(), binExpr.left(), binExpr.right()); + } + public void visit(UnaryExpression unaryExpr) { + visit(unaryExpr, unaryExpr.op(), unaryExpr.expression()); + } + public void visit(Comprehension comprehension) { + visit(comprehension, "setcomp", comprehension.decls(), comprehension.formula()); + } + public void visit(IfExpression ifExpr) { + visit(ifExpr, "ite", ifExpr.condition(), ifExpr.thenExpr(), ifExpr.elseExpr()); + } + public void visit(ProjectExpression project) { + visit(project, "proj", project.expression(), project.columns()); + } + + public void visit(IntToExprCast castExpr) { visit(castExpr, castExpr.op(), castExpr.intExpr()); } + public void visit(IntConstant intConst) { visit(intConst, intConst.value()); } + + public void visit(IfIntExpression intExpr) { + visit(intExpr, "ite", intExpr.condition(), intExpr.thenExpr(), intExpr.elseExpr()); + } + public void visit(ExprToIntCast intExpr) { visit(intExpr, intExpr.op(), intExpr.expression()); } + public void visit(NaryIntExpression intExpr) { visit(intExpr, intExpr.op(), intExpr.iterator());} + public void visit(BinaryIntExpression intExpr) { visit(intExpr, intExpr.op(), intExpr.left(), intExpr.right()); } + public void visit(UnaryIntExpression intExpr) { visit(intExpr, intExpr.op(), intExpr.intExpr());} + public void visit(SumExpression intExpr) { visit(intExpr, "sum", intExpr.decls(), intExpr.intExpr()); } + + public void visit(IntComparisonFormula intComp) { visit(intComp, intComp.op(), intComp.left(), intComp.right());} + public void visit(QuantifiedFormula quantFormula) { + visit(quantFormula, quantFormula.quantifier(), quantFormula.decls(), quantFormula.formula()); + } + public void visit(NaryFormula formula) { visit(formula, formula.op(), formula.iterator()); } + public void visit(BinaryFormula binFormula) { visit(binFormula, binFormula.op(), binFormula.left(), binFormula.right()); } + public void visit(NotFormula not) { visit(not, "not", not.formula()); } + public void visit(ConstantFormula constant) { visit(constant, constant.booleanValue()); } + public void visit(ComparisonFormula compFormula) { visit(compFormula, compFormula.op(), compFormula.left(), compFormula.right());} + public void visit(MultiplicityFormula multFormula) { + visit(multFormula, multFormula.multiplicity(), multFormula.expression()); + } + public void visit(RelationPredicate pred) { + if (visited(pred)) return; + + if (pred.name()==RelationPredicate.Name.FUNCTION) { + final RelationPredicate.Function fp = (RelationPredicate.Function) pred; + visit(fp, fp.name(), fp.domain(), fp.range() ); + } else if (pred.name()==RelationPredicate.Name.TOTAL_ORDERING) { + final RelationPredicate.TotalOrdering tp = (RelationPredicate.TotalOrdering) pred; + visit(tp, tp.name(), tp.ordered(), tp.first(), tp.last() ); + } else { + throw new IllegalArgumentException("Unknown predicate: " + pred); + } + } + + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/nodes/package.html b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/nodes/package.html new file mode 100644 index 00000000..03fd26ae --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/kodkod/util/nodes/package.html @@ -0,0 +1,36 @@ + + + + + + + +Provides utility methods for constructing, analyzing, and pretty printing Kodkod nodes. + +

Package Specification

+ +

Provides utility methods for constructing, analyzing, and pretty printing Kodkod nodes.

+ +

Related Documentation

+ +@see kodkod.util.nodes.Nodes +@see kodkod.util.nodes.AnnotatedNode +@see kodkod.util.nodes.PrettyPrinter + + + diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/book/appendixA/addressBook1.als b/Source/eu.modelwriter.alloyanalyzer/src/models/book/appendixA/addressBook1.als new file mode 100644 index 00000000..06c2abf8 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/book/appendixA/addressBook1.als @@ -0,0 +1,19 @@ +module appendixA/addressBook1 + +abstract sig Name { + address: set Addr+Name + } + +sig Alias, Group extends Name { } + +sig Addr { } + +fact { + // the invariants should go here + } + +pred show { + // simulation constraints should go here + } + +run show for 3 diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/book/appendixA/addressBook2.als b/Source/eu.modelwriter.alloyanalyzer/src/models/book/appendixA/addressBook2.als new file mode 100644 index 00000000..02ba6373 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/book/appendixA/addressBook2.als @@ -0,0 +1,27 @@ +module appendixA/addressBook2 + +sig Addr, Name { } + +sig Book { + addr: Name -> (Name + Addr) + } + +pred inv [b: Book] { + let addr = b.addr | + all n: Name { + n not in n.^addr + some addr.n => some n.^addr & Addr + } + } + +pred add [b, b': Book, n: Name, t: Name+Addr] { + b'.addr = b.addr + n->t + } + +pred del [b, b': Book, n: Name, t: Name+Addr] { + b'.addr = b.addr - n->t + } + +fun lookup [b: Book, n: Name] : set Addr { + n.^(b.addr) & Addr + } diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/book/appendixA/barbers.als b/Source/eu.modelwriter.alloyanalyzer/src/models/book/appendixA/barbers.als new file mode 100644 index 00000000..6205b5f4 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/book/appendixA/barbers.als @@ -0,0 +1,9 @@ +module appendixA/barbers + +sig Man { shaves: set Man } + +one sig Barber extends Man { } + +fact { + Barber.shaves = { m: Man | m not in m.shaves } + } diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/book/appendixA/closure.als b/Source/eu.modelwriter.alloyanalyzer/src/models/book/appendixA/closure.als new file mode 100644 index 00000000..6a50e404 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/book/appendixA/closure.als @@ -0,0 +1,16 @@ +module appendixA/closure + +pred transCover [R, r: univ->univ] { + // You have to fill in the appropriate formula here +} + +pred transClosure [R, r: univ->univ] { + transCover [R, r] + // You have to fill in the appropriate formula here +} + +assert Equivalence { + all R, r: univ->univ | transClosure [R,r] iff R = ^r +} + +check Equivalence for 3 diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/book/appendixA/distribution.als b/Source/eu.modelwriter.alloyanalyzer/src/models/book/appendixA/distribution.als new file mode 100644 index 00000000..6614b2e6 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/book/appendixA/distribution.als @@ -0,0 +1,7 @@ +module appendixA/distribution + +assert union { + all s: set univ, p, q: univ->univ | s.(p+q) = s.p + s.q +} + +check union for 4 diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/book/appendixA/phones.als b/Source/eu.modelwriter.alloyanalyzer/src/models/book/appendixA/phones.als new file mode 100644 index 00000000..98c13677 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/book/appendixA/phones.als @@ -0,0 +1,6 @@ +module appendixA/phones + +sig Phone { + requests: set Phone, + connects: lone Phone + } diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/book/appendixA/prison.als b/Source/eu.modelwriter.alloyanalyzer/src/models/book/appendixA/prison.als new file mode 100644 index 00000000..42e9ea00 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/book/appendixA/prison.als @@ -0,0 +1,17 @@ +module appendixA/prison + +sig Gang { members: set Inmate } + +sig Inmate { room: Cell } + +sig Cell { } + +pred safe { + // your constraints should go here + } + +pred show { + // your constraints should go here + } + +run show diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/book/appendixA/properties.als b/Source/eu.modelwriter.alloyanalyzer/src/models/book/appendixA/properties.als new file mode 100644 index 00000000..cc06cc1e --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/book/appendixA/properties.als @@ -0,0 +1,23 @@ +module appendixA/properties + +pred show { + some r: univ->univ { + some r -- nonempty + r.r in r -- transitive + no iden & r -- irreflexive + ~r in r -- symmetric + ~r.r in iden -- functional + r.~r in iden -- injective + univ in r.univ -- total + univ in univ.r -- onto + } + } + +run show for 4 + +assert ReformulateNonEmptinessOK { + all r: univ->univ | + some r iff (some x, y: univ | x->y in r) + } + +check ReformulateNonEmptinessOK diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/book/appendixA/ring.als b/Source/eu.modelwriter.alloyanalyzer/src/models/book/appendixA/ring.als new file mode 100644 index 00000000..ec577666 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/book/appendixA/ring.als @@ -0,0 +1,9 @@ +module appendixA/ring + +sig Node { next: set Node } + +pred isRing { + // You have to fill in the appropriate formula here +} + +run isRing for exactly 4 Node diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/book/appendixA/spanning.als b/Source/eu.modelwriter.alloyanalyzer/src/models/book/appendixA/spanning.als new file mode 100644 index 00000000..5218e700 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/book/appendixA/spanning.als @@ -0,0 +1,17 @@ +module appendixA/spanning + +pred isTree [r: univ->univ] { + // You have to fill in the appropriate formula here +} + +pred spans [r1, r2: univ->univ] { + // You have to fill in the appropriate formula here +} + +pred show [r, t1, t2: univ->univ] { + spans [t1,r] and isTree [t1] + spans [t2,r] and isTree [t2] + t1 != t2 +} + +run show for 3 diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/book/appendixA/tree.als b/Source/eu.modelwriter.alloyanalyzer/src/models/book/appendixA/tree.als new file mode 100644 index 00000000..74750d40 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/book/appendixA/tree.als @@ -0,0 +1,7 @@ +module appendixA/tree + +pred isTree [r:univ->univ] { + // You have to fill in the appropriate formula here +} + +run isTree for 4 diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/book/appendixA/tube.als b/Source/eu.modelwriter.alloyanalyzer/src/models/book/appendixA/tube.als new file mode 100644 index 00000000..03af8de9 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/book/appendixA/tube.als @@ -0,0 +1,21 @@ +module appendixA/tube + +abstract sig Station { + jubilee, central, circle: set Station + } + +sig Jubilee, Central, Circle in Station {} + +one sig + Stanmore, BakerStreet, BondStreet, Westminster, Waterloo, + WestRuislip, EalingBroadway, NorthActon, NottingHillGate, + LiverpoolStreet, Epping + extends Station {} + +fact { + // the constraints should go here + } + +pred show {} + +run show diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/book/appendixA/undirected.als b/Source/eu.modelwriter.alloyanalyzer/src/models/book/appendixA/undirected.als new file mode 100644 index 00000000..937cedb1 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/book/appendixA/undirected.als @@ -0,0 +1,11 @@ +module appendixA/undirected + +sig Node { adjs: set Node } + +pred acyclic { + adjs = ~adjs + // You have to fill in additional formula here +} + +run acyclic for 4 + diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/book/appendixE/hotel.thm b/Source/eu.modelwriter.alloyanalyzer/src/models/book/appendixE/hotel.thm new file mode 100644 index 00000000..fe20fbe1 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/book/appendixE/hotel.thm @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/book/appendixE/p300-hotel.als b/Source/eu.modelwriter.alloyanalyzer/src/models/book/appendixE/p300-hotel.als new file mode 100644 index 00000000..813846b2 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/book/appendixE/p300-hotel.als @@ -0,0 +1,66 @@ +module hotel + +open util/ordering [Time] as timeOrder + +sig Key, Time {} + +sig Card { + fst, snd: Key + } + +sig Room { + key: Key one->Time + } + +one sig Desk { + issued: Key->Time, + prev: (Room->lone Key)->Time + } + +sig Guest { + cards: Card->Time + } + +pred init [t: Time] { + Desk.prev.t = key.t + no issued.t and no cards.t ------ bug! (see page 303) + } + +pred checkin [t,t': Time, r: Room, g: Guest] { + some c: Card { + c.fst = r.(Desk.prev.t) + c.snd not in Desk.issued.t + cards.t' = cards.t + g->c ------------- bug! (see page 306) + Desk.issued.t' = Desk.issued.t + c.snd + Desk.prev.t' = Desk.prev.t ++ r->c.snd + } + key.t = key.t' + } + +pred enter [t,t': Time, r: Room, g: Guest] { + some c: g.cards.t | + let k = r.key.t { + c.snd = k and key.t' = key.t + or c.fst = k and key.t' = key.t ++ r->c.snd + } + issued.t = issued.t' and (Desk<:prev).t = prev.t' + cards.t = cards.t' + } + +fact Traces { + init [first] + all t: Time - last | some g: Guest, r: Room | + checkin [t, t.next, r, g] or enter[t, t.next, r, g] + } + +assert NoIntruder { + no t1: Time, g: Guest, g': Guest-g, r: Room | + let t2=t1.next, t3=t2.next, t4=t3.next { + enter [t1, t2, r, g] + enter [t2, t3, r, g'] + enter [t3, t4, r, g] + } + } + +-- This check reveals a bug (similar to Fig E.3) in which the initial key was issued twice. +check NoIntruder for 3 but 6 Time, 1 Room, 2 Guest diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/book/appendixE/p303-hotel.als b/Source/eu.modelwriter.alloyanalyzer/src/models/book/appendixE/p303-hotel.als new file mode 100644 index 00000000..652de3d8 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/book/appendixE/p303-hotel.als @@ -0,0 +1,70 @@ +module hotel + +open util/ordering [Time] as timeOrder + +sig Key, Time {} + +sig Card { + fst, snd: Key + } + +sig Room { + key: Key one->Time + } + +one sig Desk { + issued: Key->Time, + prev: (Room->lone Key)->Time + } + +sig Guest { + cards: Card->Time + } + +pred init [t: Time] { + Desk.prev.t = key.t + Desk.issued.t = Room.key.t and no cards.t + } + +pred checkin [t,t': Time, r: Room, g: Guest] { + some c: Card { + c.fst = r.(Desk.prev.t) + c.snd not in Desk.issued.t + cards.t' = cards.t + g->c ------------- bug! (see page 306) + Desk.issued.t' = Desk.issued.t + c.snd + Desk.prev.t' = Desk.prev.t ++ r->c.snd + } + key.t = key.t' + } + +pred enter [t,t': Time, r: Room, g: Guest] { + some c: g.cards.t | + let k = r.key.t { + c.snd = k and key.t' = key.t + or c.fst = k and key.t' = key.t ++ r->c.snd + } + issued.t = issued.t' and (Desk<:prev).t = prev.t' + cards.t = cards.t' + } + +fact Traces { + init [first] + all t: Time - last | some g: Guest, r: Room | + checkin [t, t.next, r, g] or enter[t, t.next, r, g] + } + +assert NoIntruder { + no t1: Time, g: Guest, g': Guest-g, r: Room | + let t2=t1.next, t3=t2.next, t4=t3.next { + enter [t1, t2, r, g] + enter [t2, t3, r, g'] + enter [t3, t4, r, g] + } + } + +-- This check now succeeds without finding any counterexample. +check NoIntruder for 3 but 6 Time, 1 Room, 2 Guest + +-- To increase our confidence, we can increase the scope. +-- This time, it finds a counterexample. +check NoIntruder for 4 but 7 Time, 1 Room, 2 Guest diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/book/appendixE/p306-hotel.als b/Source/eu.modelwriter.alloyanalyzer/src/models/book/appendixE/p306-hotel.als new file mode 100644 index 00000000..efcd7e7d --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/book/appendixE/p306-hotel.als @@ -0,0 +1,73 @@ +module hotel + +open util/ordering [Time] as timeOrder + +sig Key, Time {} + +sig Card { + fst, snd: Key + } + +sig Room { + key: Key one->Time + } + +one sig Desk { + issued: Key->Time, + prev: (Room->lone Key)->Time + } + +sig Guest { + cards: Card->Time + } + +pred init [t: Time] { + Desk.prev.t = key.t + Desk.issued.t = Room.key.t and no cards.t + } + +pred checkin [t,t': Time, r: Room, g: Guest] { + some c: Card { + c.fst = r.(Desk.prev.t) + c.snd not in Desk.issued.t + cards.t' = cards.t ++ g->c + Desk.issued.t' = Desk.issued.t + c.snd + Desk.prev.t' = Desk.prev.t ++ r->c.snd + } + key.t = key.t' + } + +pred enter [t,t': Time, r: Room, g: Guest] { + some c: g.cards.t | + let k = r.key.t { + c.snd = k and key.t' = key.t + or c.fst = k and key.t' = key.t ++ r->c.snd + } + issued.t = issued.t' and (Desk<:prev).t = prev.t' + cards.t = cards.t' + } + +fact Traces { + init [first] + all t: Time - last | some g: Guest, r: Room | + checkin [t, t.next, r, g] or enter[t, t.next, r, g] + } + +assert NoIntruder { + no t1: Time, g: Guest, g': Guest-g, r: Room | + let t2=t1.next, t3=t2.next, t4=t3.next { + enter [t1, t2, r, g] + enter [t2, t3, r, g'] + enter [t3, t4, r, g] + } + } + +-- This check now succeeds without finding any counterexample. +check NoIntruder for 3 but 6 Time, 1 Room, 2 Guest + +-- This check now succeeds without finding any counterexample. +check NoIntruder for 4 but 7 Time, 1 Room, 2 Guest + +-- We can try to increase the scope further. +-- This check also succeeds without finding any counterexample. +check NoIntruder for 6 but 12 Time, 3 Room, 3 Guest diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter2/addressBook1a.als b/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter2/addressBook1a.als new file mode 100644 index 00000000..643c0f2c --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter2/addressBook1a.als @@ -0,0 +1,12 @@ +module tour/addressBook1a ----- Page 6 + +sig Name, Addr { } + +sig Book { + addr: Name -> lone Addr +} + +pred show { } + +// This command generates an instance similar to Fig 2.1 +run show for 3 but 1 Book diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter2/addressBook1b.als b/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter2/addressBook1b.als new file mode 100644 index 00000000..1d4255e9 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter2/addressBook1b.als @@ -0,0 +1,14 @@ +module tour/addressBook1b ----- Page 8 + +sig Name, Addr { } + +sig Book { + addr: Name -> lone Addr +} + +pred show [b: Book] { + #b.addr > 1 +} + +// This command generates an instance similar to Fig 2.2 +run show for 3 but 1 Book diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter2/addressBook1c.als b/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter2/addressBook1c.als new file mode 100644 index 00000000..85b18616 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter2/addressBook1c.als @@ -0,0 +1,15 @@ +module tour/addressBook1c ----- Page 8 + +sig Name, Addr { } + +sig Book { + addr: Name -> lone Addr +} + +pred show [b: Book] { + #b.addr > 1 + some n: Name | #n.(b.addr) > 1 +} + +// This command should not find any instance. +run show for 3 but 1 Book diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter2/addressBook1d.als b/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter2/addressBook1d.als new file mode 100644 index 00000000..c553a4f9 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter2/addressBook1d.als @@ -0,0 +1,15 @@ +module tour/addressBook1d ----- Page 9 + +sig Name, Addr { } + +sig Book { + addr: Name -> lone Addr +} + +pred show [b: Book] { + #b.addr > 1 + #Name.(b.addr) > 1 +} + +// This command generates an instance similar to Fig 2.3 +run show for 3 but 1 Book diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter2/addressBook1e.als b/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter2/addressBook1e.als new file mode 100644 index 00000000..18165f34 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter2/addressBook1e.als @@ -0,0 +1,14 @@ +module tour/addressBook1e ----- Page 11 + +sig Name, Addr { } + +sig Book { + addr: Name -> lone Addr +} + +pred add [b, b': Book, n: Name, a: Addr] { + b'.addr = b.addr + n->a +} + +// This command generates an instance similar to Fig 2.4 +run add for 3 but 2 Book diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter2/addressBook1f.als b/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter2/addressBook1f.als new file mode 100644 index 00000000..8774cd7b --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter2/addressBook1f.als @@ -0,0 +1,19 @@ +module tour/addressBook1f ----- Page 12 + +sig Name, Addr { } + +sig Book { + addr: Name -> lone Addr +} + +pred add [b, b': Book, n: Name, a: Addr] { + b'.addr = b.addr + n->a +} + +pred showAdd [b, b': Book, n: Name, a: Addr] { + add [b, b', n, a] + #Name.(b'.addr) > 1 +} + +// This command generates an instance similar to Fig 2.5 +run showAdd for 3 but 2 Book diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter2/addressBook1g.als b/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter2/addressBook1g.als new file mode 100644 index 00000000..4eeac73a --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter2/addressBook1g.als @@ -0,0 +1,29 @@ +module tour/addressBook1g ----- Page 14 + +sig Name, Addr { } + +sig Book { + addr: Name -> lone Addr +} + +pred add [b, b': Book, n: Name, a: Addr] { + b'.addr = b.addr + n->a +} + +pred del [b, b': Book, n: Name] { + b'.addr = b.addr - n->Addr +} + +fun lookup [b: Book, n: Name] : set Addr { + n.(b.addr) +} + +assert delUndoesAdd { + all b, b', b'': Book, n: Name, a: Addr | + add [b, b', n, a] and del [b', b'', n] + implies + b.addr = b''.addr +} + +// This command generates an instance similar to Fig 2.6 +check delUndoesAdd for 3 diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter2/addressBook1h.als b/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter2/addressBook1h.als new file mode 100644 index 00000000..115eab78 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter2/addressBook1h.als @@ -0,0 +1,64 @@ +module tour/addressBook1h ------- Page 14..16 + +sig Name, Addr { } + +sig Book { + addr: Name -> lone Addr +} + +pred show [b: Book] { + #b.addr > 1 + #Name.(b.addr) > 1 +} +run show for 3 but 1 Book + +pred add [b, b': Book, n: Name, a: Addr] { + b'.addr = b.addr + n->a +} + +pred del [b, b': Book, n: Name] { + b'.addr = b.addr - n->Addr +} + +fun lookup [b: Book, n: Name] : set Addr { + n.(b.addr) +} + +pred showAdd [b, b': Book, n: Name, a: Addr] { + add [b, b', n, a] + #Name.(b'.addr) > 1 +} +run showAdd for 3 but 2 Book + +assert delUndoesAdd { + all b, b', b'': Book, n: Name, a: Addr | + no n.(b.addr) and add [b, b', n, a] and del [b', b'', n] + implies + b.addr = b''.addr +} + +assert addIdempotent { + all b, b', b'': Book, n: Name, a: Addr | + add [b, b', n, a] and add [b', b'', n, a] + implies + b'.addr = b''.addr +} + +assert addLocal { + all b, b': Book, n, n': Name, a: Addr | + add [b, b', n, a] and n != n' + implies + lookup [b, n'] = lookup [b', n'] +} + +// This command should not find any counterexample. +check delUndoesAdd for 3 + +// This command should not find any counterexample. +check delUndoesAdd for 10 but 3 Book + +// This command should not find any counterexample. +check addIdempotent for 3 + +// This command should not find any counterexample. +check addLocal for 3 but 2 Book diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter2/addressBook2a.als b/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter2/addressBook2a.als new file mode 100644 index 00000000..ab94951b --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter2/addressBook2a.als @@ -0,0 +1,16 @@ +module tour/addressBook2a ----- Page 18 + +abstract sig Target { } +sig Addr extends Target { } +abstract sig Name extends Target { } + +sig Alias, Group extends Name { } + +sig Book { + addr: Name->Target +} + +pred show [b:Book] { some b.addr } + +// This command generates an instance similar to Fig 2.9 +run show for 3 but 1 Book diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter2/addressBook2b.als b/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter2/addressBook2b.als new file mode 100644 index 00000000..a6d1da91 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter2/addressBook2b.als @@ -0,0 +1,18 @@ +module tour/addressBook2b ----- Page 19 + +abstract sig Target { } +sig Addr extends Target { } +abstract sig Name extends Target { } + +sig Alias, Group extends Name { } + +sig Book { + addr: Name->Target +} { + no n: Name | n in n.^addr +} + +pred show [b:Book] { some b.addr } + +// This command generates an instance similar to Fig 2.10 +run show for 3 but 1 Book diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter2/addressBook2c.als b/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter2/addressBook2c.als new file mode 100644 index 00000000..33ded6b3 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter2/addressBook2c.als @@ -0,0 +1,18 @@ +module tour/addressBook2c ----- Page 20 + +abstract sig Target { } +sig Addr extends Target { } +abstract sig Name extends Target { } + +sig Alias, Group extends Name { } + +sig Book { + addr: Name->Target +} { + no n: Name | n in n.^addr +} + +pred show [b:Book] { some Alias.(b.addr) } + +// This command generates an instance similar to Fig 2.11 +run show for 3 but 1 Book diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter2/addressBook2d.als b/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter2/addressBook2d.als new file mode 100644 index 00000000..adac4fbe --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter2/addressBook2d.als @@ -0,0 +1,19 @@ +module tour/addressBook2d ----- Page 21 + +abstract sig Target { } +sig Addr extends Target { } +abstract sig Name extends Target { } + +sig Alias, Group extends Name { } + +sig Book { + addr: Name->Target +} { + no n: Name | n in n.^addr + all a: Alias | lone a.addr +} + +pred show [b:Book] { some Alias.(b.addr) } + +// This command generates an instance similar to Fig 2.12 +run show for 3 but 1 Book diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter2/addressBook2e.als b/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter2/addressBook2e.als new file mode 100644 index 00000000..3baea5ac --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter2/addressBook2e.als @@ -0,0 +1,56 @@ +module tour/addressBook2e --- this is the final model in Fig 2.14 + +abstract sig Target { } +sig Addr extends Target { } +abstract sig Name extends Target { } + +sig Alias, Group extends Name { } + +sig Book { + names: set Name, + addr: names->some Target +} { + no n: Name | n in n.^addr + all a: Alias | lone a.addr +} + +pred add [b, b': Book, n: Name, t: Target] { b'.addr = b.addr + n->t } +pred del [b, b': Book, n: Name, t: Target] { b'.addr = b.addr - n->t } +fun lookup [b: Book, n: Name] : set Addr { n.^(b.addr) & Addr } + +assert delUndoesAdd { + all b, b', b'': Book, n: Name, t: Target | + no n.(b.addr) and add [b, b', n, t] and del [b', b'', n, t] + implies + b.addr = b''.addr +} + +// This should not find any counterexample. +check delUndoesAdd for 3 + +assert addIdempotent { + all b, b', b'': Book, n: Name, t: Target | + add [b, b', n, t] and add [b', b'', n, t] + implies + b'.addr = b''.addr +} + +// This should not find any counterexample. +check addIdempotent for 3 + +assert addLocal { + all b, b': Book, n, n': Name, t: Target | + add [b, b', n, t] and n != n' + implies + lookup [b, n'] = lookup [b', n'] +} + +// This shows a counterexample similar to Fig 2.13 +check addLocal for 3 but 2 Book + +assert lookupYields { + all b: Book, n: b.names | some lookup [b,n] +} + +// This shows a counterexample similar to Fig 2.12 +check lookupYields for 4 but 1 Book diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter2/addressBook3a.als b/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter2/addressBook3a.als new file mode 100644 index 00000000..5ff9eee0 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter2/addressBook3a.als @@ -0,0 +1,38 @@ +module tour/addressBook3a ----- Page 25 + +open util/ordering [Book] as BookOrder + +abstract sig Target { } +sig Addr extends Target { } +abstract sig Name extends Target { } + +sig Alias, Group extends Name { } + +sig Book { + names: set Name, + addr: names->some Target +} { + no n: Name | n in n.^addr + all a: Alias | lone a.addr +} + +pred add [b, b': Book, n: Name, t: Target] { b'.addr = b.addr + n->t } +pred del [b, b': Book, n: Name, t: Target] { b'.addr = b.addr - n->t } +fun lookup [b: Book, n: Name] : set Addr { n.^(b.addr) & Addr } + +pred init [b: Book] { no b.addr } + +fact traces { + init [first] + all b: Book-last | + let b' = b.next | + some n: Name, t: Target | + add [b, b', n, t] or del [b, b', n, t] +} + +------------------------------------------------------ + +pred show { } + +// This command generates an instance similar to Fig 2.15 +run show for 4 diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter2/addressBook3b.als b/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter2/addressBook3b.als new file mode 100644 index 00000000..5c4b835d --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter2/addressBook3b.als @@ -0,0 +1,76 @@ +module tour/addressBook3b ----- Page 26 + +open util/ordering [Book] as BookOrder + +abstract sig Target { } +sig Addr extends Target { } +abstract sig Name extends Target { } + +sig Alias, Group extends Name { } + +sig Book { + names: set Name, + addr: names->some Target +} { + no n: Name | n in n.^addr + all a: Alias | lone a.addr +} + +pred add [b, b': Book, n: Name, t: Target] { b'.addr = b.addr + n->t } +pred del [b, b': Book, n: Name, t: Target] { b'.addr = b.addr - n->t } +fun lookup [b: Book, n: Name] : set Addr { n.^(b.addr) & Addr } + +pred init [b: Book] { no b.addr } + +fact traces { + init [first] + all b: Book-last | + let b' = b.next | + some n: Name, t: Target | + add [b, b', n, t] or del [b, b', n, t] +} + +------------------------------------------------------ + +assert delUndoesAdd { + all b, b', b'': Book, n: Name, t: Target | + no n.(b.addr) and add [b, b', n, t] and del [b', b'', n, t] + implies + b.addr = b''.addr +} + +// This should not find any counterexample. +check delUndoesAdd for 3 + +------------------------------------------------------ + +assert addIdempotent { + all b, b', b'': Book, n: Name, t: Target | + add [b, b', n, t] and add [b', b'', n, t] + implies + b'.addr = b''.addr +} + +// This should not find any counterexample. +check addIdempotent for 3 + +------------------------------------------------------ + +assert addLocal { + all b, b': Book, n, n': Name, t: Target | + add [b, b', n, t] and n != n' + implies + lookup [b, n'] = lookup [b', n'] +} + +// This should not find any counterexample. +check addLocal for 3 but 2 Book + +------------------------------------------------------ + +assert lookupYields { + all b: Book, n: b.names | some lookup [b,n] +} + +// This shows a counterexample similar to Fig 2.16 +check lookupYields for 3 but 4 Book diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter2/addressBook3c.als b/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter2/addressBook3c.als new file mode 100644 index 00000000..284f8c36 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter2/addressBook3c.als @@ -0,0 +1,81 @@ +module tour/addressBook3c ----- Page 27 + +open util/ordering [Book] as BookOrder + +abstract sig Target { } +sig Addr extends Target { } +abstract sig Name extends Target { } + +sig Alias, Group extends Name { } + +sig Book { + names: set Name, + addr: names->some Target +} { + no n: Name | n in n.^addr + all a: Alias | lone a.addr +} + +pred add [b, b': Book, n: Name, t: Target] { + t in Addr or some lookup [b, Name&t] + b'.addr = b.addr + n->t +} + +pred del [b, b': Book, n: Name, t: Target] { b'.addr = b.addr - n->t } + +fun lookup [b: Book, n: Name] : set Addr { n.^(b.addr) & Addr } + +pred init [b: Book] { no b.addr } + +fact traces { + init [first] + all b: Book-last | + let b' = b.next | + some n: Name, t: Target | + add [b, b', n, t] or del [b, b', n, t] +} + +------------------------------------------------------ + +assert delUndoesAdd { + all b, b', b'': Book, n: Name, t: Target | + no n.(b.addr) and add [b, b', n, t] and del [b', b'', n, t] + implies + b.addr = b''.addr +} + +// This should not find any counterexample. +check delUndoesAdd for 3 + +------------------------------------------------------ + +assert addIdempotent { + all b, b', b'': Book, n: Name, t: Target | + add [b, b', n, t] and add [b', b'', n, t] + implies + b'.addr = b''.addr +} + +// This should not find any counterexample. +check addIdempotent for 3 + +------------------------------------------------------ + +assert addLocal { + all b, b': Book, n, n': Name, t: Target | + add [b, b', n, t] and n != n' + implies + lookup [b, n'] = lookup [b', n'] +} + +// This should not find any counterexample. +check addLocal for 3 but 2 Book + +------------------------------------------------------ + +assert lookupYields { + all b: Book, n: b.names | some lookup [b,n] +} + +// This shows a counterexample similar to Fig 2.17 +check lookupYields for 3 but 4 Book diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter2/addressBook3d.als b/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter2/addressBook3d.als new file mode 100644 index 00000000..aceac514 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter2/addressBook3d.als @@ -0,0 +1,87 @@ +module tour/addressBook3d ----- this is the final model in fig 2.18 + +open util/ordering [Book] as BookOrder + +abstract sig Target { } +sig Addr extends Target { } +abstract sig Name extends Target { } + +sig Alias, Group extends Name { } + +sig Book { + names: set Name, + addr: names->some Target +} { + no n: Name | n in n.^addr + all a: Alias | lone a.addr +} + +pred add [b, b': Book, n: Name, t: Target] { + t in Addr or some lookup [b, Name&t] + b'.addr = b.addr + n->t +} + +pred del [b, b': Book, n: Name, t: Target] { + no b.addr.n or some n.(b.addr) - t + b'.addr = b.addr - n->t +} + +fun lookup [b: Book, n: Name] : set Addr { n.^(b.addr) & Addr } + +pred init [b: Book] { no b.addr } + +fact traces { + init [first] + all b: Book-last | + let b' = b.next | + some n: Name, t: Target | + add [b, b', n, t] or del [b, b', n, t] +} + +------------------------------------------------------ + +assert delUndoesAdd { + all b, b', b'': Book, n: Name, t: Target | + no n.(b.addr) and add [b, b', n, t] and del [b', b'', n, t] + implies + b.addr = b''.addr +} + +// This should not find any counterexample. +check delUndoesAdd for 3 + +------------------------------------------------------ + +assert addIdempotent { + all b, b', b'': Book, n: Name, t: Target | + add [b, b', n, t] and add [b', b'', n, t] + implies + b'.addr = b''.addr +} + +// This should not find any counterexample. +check addIdempotent for 3 + +------------------------------------------------------ + +assert addLocal { + all b, b': Book, n, n': Name, t: Target | + add [b, b', n, t] and n != n' + implies + lookup [b, n'] = lookup [b', n'] +} + +// This should not find any counterexample. +check addLocal for 3 but 2 Book + +------------------------------------------------------ + +assert lookupYields { + all b: Book, n: b.names | some lookup [b,n] +} + +// This should not find any counterexample. +check lookupYields for 3 but 4 Book + +// This should not find any counterexample. +check lookupYields for 6 diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter2/theme.thm b/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter2/theme.thm new file mode 100644 index 00000000..2b839993 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter2/theme.thm @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter4/filesystem.als b/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter4/filesystem.als new file mode 100644 index 00000000..c45e776b --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter4/filesystem.als @@ -0,0 +1,28 @@ +module chapter4/filesystem ----- The model from page 125 + +abstract sig Object {} + +sig Dir extends Object {contents: set Object} + +one sig Root extends Dir { } + +sig File extends Object {} + +fact { + Object in Root.*contents + } + +assert SomeDir { + all o: Object - Root | some contents.o + } +check SomeDir // This assertion is valid + +assert RootTop { + no o: Object | Root in o.contents + } +check RootTop // This assertion should produce a counterexample + +assert FileInDir { + all f: File | some contents.f + } +check FileInDir // This assertion is valid diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter4/grandpa1.als b/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter4/grandpa1.als new file mode 100644 index 00000000..c05c85c9 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter4/grandpa1.als @@ -0,0 +1,44 @@ +module language/grandpa1 ---- Page 84, 85 + +abstract sig Person { + father: lone Man, + mother: lone Woman + } + +sig Man extends Person { + wife: lone Woman + } + +sig Woman extends Person { + husband: lone Man + } + +fact { + no p: Person | p in p.^(mother+father) + wife = ~husband + } + +assert NoSelfFather { + no m: Man | m = m.father + } + +// This should not find any counterexample. +check NoSelfFather + +fun grandpas [p: Person] : set Person { + p.(mother+father).father + } + +pred ownGrandpa [p: Person] { + p in p.grandpas + } + +// This should not find any instance. +run ownGrandpa for 4 Person + +assert NoSelfGrandpa { + no p: Person | p in p.grandpas + } + +// This should not find any counterexample +check NoSelfGrandpa for 4 Person diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter4/grandpa2.als b/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter4/grandpa2.als new file mode 100644 index 00000000..08dda731 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter4/grandpa2.als @@ -0,0 +1,38 @@ +module language/grandpa2 ---- Page 86 + +abstract sig Person { + father: lone Man, + mother: lone Woman + } + +sig Man extends Person { + wife: lone Woman + } + +sig Woman extends Person { + husband: lone Man + } + +fact { + no p: Person | p in p.^(mother+father) + wife = ~husband + } + +assert NoSelfFather { + no m: Man | m = m.father + } + +// This should not find any counterexample. +check NoSelfFather + +fun grandpas [p: Person] : set Person { + let parent = mother + father + father.wife + mother.husband | + p.parent.parent & Man + } + +pred ownGrandpa [p: Person] { + p in p.grandpas + } + +// This generates an instance similar to Fig 4.2 +run ownGrandpa for 4 Person diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter4/grandpa3.als b/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter4/grandpa3.als new file mode 100644 index 00000000..d9147cbf --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter4/grandpa3.als @@ -0,0 +1,70 @@ +module language/grandpa3 ---- the final model in fig 4.4 + +abstract sig Person { + father: lone Man, + mother: lone Woman + } + +sig Man extends Person { + wife: lone Woman + } + +sig Woman extends Person { + husband: lone Man + } + +fact Biology { + no p: Person | p in p.^(mother+father) + } + +fact Terminology { + wife = ~husband + } + +fact SocialConvention { + no (wife+husband) & ^(mother+father) + } + +------------------------------------------ + +assert NoSelfFather { + no m: Man | m = m.father + } + +// This should not find any counterexample. +check NoSelfFather + +------------------------------------------ + +fun grandpas [p: Person] : set Person { + let parent = mother + father + father.wife + mother.husband | + p.parent.parent & Man + } + +pred ownGrandpa [p: Person] { + p in p.grandpas + } + +// This generates an instance similar to Fig 4.3 +run ownGrandpa for 4 Person + +------------------------------------------ + +pred SocialConvention1 { + no (wife + husband) & ^(mother + father) + } + +pred SocialConvention2 { + let parent = mother + father { + no m: Man | some m.wife and m.wife in m.*parent.mother + no w: Woman | some w.husband and w.husband in w.*parent.father + } + } + +// This assertion was described on page 90. +assert Same { + SocialConvention1 iff SocialConvention2 + } + +// This should not find any counterexample +check Same diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter4/lights.als b/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter4/lights.als new file mode 100644 index 00000000..24997705 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter4/lights.als @@ -0,0 +1,41 @@ +module chapter4/lights ----- The model from page 127 + +abstract sig Color {} + +one sig Red, Yellow, Green extends Color {} + +fun colorSequence: Color -> Color { + Color <: iden + Red->Green + Green->Yellow + Yellow->Red + } + +sig Light {} +sig LightState {color: Light -> one Color} +sig Junction {lights: set Light} + +fun redLights [s: LightState]: set Light { s.color.Red } + +pred mostlyRed [s: LightState, j: Junction] { + lone j.lights - redLights[s] + } + +pred trans [s, s': LightState, j: Junction] { + lone x: j.lights | s.color[x] != s'.color[x] + all x: j.lights | + let step = s.color[x] -> s'.color[x] { + step in colorSequence + step in Red->(Color-Red) => j.lights in redLights[s] + } + } + +assert Safe { + all s, s': LightState, j: Junction | + mostlyRed [s, j] and trans [s, s', j] => mostlyRed [s', j] + } + +check Safe for 3 but 1 Junction + +//assert ColorSequenceDeterministic { +// all c: Color | lone c.colorSequence +// } +// +//check ColorSequenceDeterministic diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter5/addressBook.als b/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter5/addressBook.als new file mode 100644 index 00000000..5d7dee56 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter5/addressBook.als @@ -0,0 +1,26 @@ +module chapter5/addressBook --- the model in fig 5.1 + +abstract sig Target {} + +sig Addr extends Target {} +sig Name extends Target {} +sig Book {addr: Name -> Target} + +fact Acyclic {all b: Book | no n: Name | n in n.^(b.addr)} + +pred add [b, b': Book, n: Name, t: Target] { + b'.addr = b.addr + n -> t + } + +// This command should produce an instance similar to Fig 5.2 +run add for 3 but 2 Book + +fun lookup [b: Book, n: Name]: set Addr {n.^(b.addr) & Addr} + +assert addLocal { + all b,b': Book, n,n': Name, t: Target | + add [b,b',n,t] and n != n' => lookup [b,n'] = lookup [b',n'] + } + +// This command should produce a counterexample similar to Fig 5.3 +check addLocal for 3 but 2 Book diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter5/lists.als b/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter5/lists.als new file mode 100644 index 00000000..79cbd0da --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter5/lists.als @@ -0,0 +1,23 @@ +module chapter5/lists ---- page 157 + +some sig Element {} + +abstract sig List {} +one sig EmptyList extends List {} +sig NonEmptyList extends List { + element: Element, + rest: List + } + +fact ListGenerator { + all list: List, e: Element | + some list': List | list'.rest = list and list'.element = e + } + +assert FalseAssertion { + all list: List | list != list + } + +// This check finds no counterexample since +// the only possible counterexamples are infinite. +check FalseAssertion diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter5/sets1.als b/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter5/sets1.als new file mode 100644 index 00000000..dc998820 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter5/sets1.als @@ -0,0 +1,16 @@ +module chapter5/sets1 ----- page 156 + +sig Set { + elements: set Element +} + +sig Element {} + +assert Closed { + all s0, s1: Set | + some s2: Set | + s2.elements = s0.elements + s1.elements + } + +// This check should produce a counterexample +check Closed diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter5/sets2.als b/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter5/sets2.als new file mode 100644 index 00000000..265a1e64 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter5/sets2.als @@ -0,0 +1,21 @@ +module chapter5/sets2 ----- page 157 + +sig Set { + elements: set Element +} + +sig Element {} + +assert Closed { + all s0, s1: Set | + some s2: Set | + s2.elements = s0.elements + s1.elements + } + +fact SetGenerator { + some s: Set | no s.elements + all s: Set, e: Element | some s': Set | s'.elements = s.elements + e + } + +// This check should not produce a counterexample +check Closed for 4 Element, 16 Set diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter6/hotel.thm b/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter6/hotel.thm new file mode 100644 index 00000000..acdb8d09 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter6/hotel.thm @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter6/hotel1.als b/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter6/hotel1.als new file mode 100644 index 00000000..22610689 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter6/hotel1.als @@ -0,0 +1,101 @@ +module chapter6/hotel1 --- the model up to the top of page 191 + +open util/ordering[Time] as to +open util/ordering[Key] as ko + +sig Key {} +sig Time {} + +sig Room { + keys: set Key, + currentKey: keys one -> Time + } + +fact DisjointKeySets { + -- each key belongs to at most one room + Room<:keys in Room lone-> Key + } + +one sig FrontDesk { + lastKey: (Room -> lone Key) -> Time, + occupant: (Room -> Guest) -> Time + } + +sig Guest { + keys: Key -> Time + } + +fun nextKey [k: Key, ks: set Key]: set Key { + min [k.nexts & ks] + } + +pred init [t: Time] { + no Guest.keys.t + no FrontDesk.occupant.t + all r: Room | FrontDesk.lastKey.t [r] = r.currentKey.t + } + +pred entry [t, t': Time, g: Guest, r: Room, k: Key] { + k in g.keys.t + let ck = r.currentKey | + (k = ck.t and ck.t' = ck.t) or + (k = nextKey[ck.t, r.keys] and ck.t' = k) + noRoomChangeExcept [t, t', r] + noGuestChangeExcept [t, t', none] + noFrontDeskChange [t, t'] + } + +pred noFrontDeskChange [t, t': Time] { + FrontDesk.lastKey.t = FrontDesk.lastKey.t' + FrontDesk.occupant.t = FrontDesk.occupant.t' + } + +pred noRoomChangeExcept [t, t': Time, rs: set Room] { + all r: Room - rs | r.currentKey.t = r.currentKey.t' + } + +pred noGuestChangeExcept [t, t': Time, gs: set Guest] { + all g: Guest - gs | g.keys.t = g.keys.t' + } + +pred checkout [t, t': Time, g: Guest] { + let occ = FrontDesk.occupant { + some occ.t.g + occ.t' = occ.t - Room ->g + } + FrontDesk.lastKey.t = FrontDesk.lastKey.t' + noRoomChangeExcept [t, t', none] + noGuestChangeExcept [t, t', none] + } + +pred checkin [t, t': Time, g: Guest, r: Room, k: Key] { + g.keys.t' = g.keys.t + k + let occ = FrontDesk.occupant { + no occ.t [r] + occ.t' = occ.t + r -> g + } + let lk = FrontDesk.lastKey { + lk.t' = lk.t ++ r -> k + k = nextKey [lk.t [r], r.keys] + } + noRoomChangeExcept [t, t', none] + noGuestChangeExcept [t, t', g] + } + +fact traces { + init [first] + all t: Time-last | let t' = t.next | + some g: Guest, r: Room, k: Key | + entry [t, t', g, r, k] + or checkin [t, t', g, r, k] + or checkout [t, t', g] + } + +assert NoBadEntry { + all t: Time, r: Room, g: Guest, k: Key | + let t' = t.next, o = FrontDesk.occupant.t[r] | + entry [t, t', g, r, k] and some o => g in o + } + +// This generates a counterexample similar to Fig 6.6 +check NoBadEntry for 3 but 2 Room, 2 Guest, 5 Time diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter6/hotel2.als b/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter6/hotel2.als new file mode 100644 index 00000000..09378f2e --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter6/hotel2.als @@ -0,0 +1,110 @@ +module chapter6/hotel2 --- the final model in Fig 6.7 + +open util/ordering[Time] as to +open util/ordering[Key] as ko + +sig Key {} +sig Time {} + +sig Room { + keys: set Key, + currentKey: keys one -> Time + } + +fact DisjointKeySets { + -- each key belongs to at most one room + Room<:keys in Room lone-> Key + } + +one sig FrontDesk { + lastKey: (Room -> lone Key) -> Time, + occupant: (Room -> Guest) -> Time + } + +sig Guest { + keys: Key -> Time + } + +fun nextKey [k: Key, ks: set Key]: set Key { + min [k.nexts & ks] + } + +pred init [t: Time] { + no Guest.keys.t + no FrontDesk.occupant.t + all r: Room | FrontDesk.lastKey.t [r] = r.currentKey.t + } + +pred entry [t, t': Time, g: Guest, r: Room, k: Key] { + k in g.keys.t + let ck = r.currentKey | + (k = ck.t and ck.t' = ck.t) or + (k = nextKey[ck.t, r.keys] and ck.t' = k) + noRoomChangeExcept [t, t', r] + noGuestChangeExcept [t, t', none] + noFrontDeskChange [t, t'] + } + +pred noFrontDeskChange [t, t': Time] { + FrontDesk.lastKey.t = FrontDesk.lastKey.t' + FrontDesk.occupant.t = FrontDesk.occupant.t' + } + +pred noRoomChangeExcept [t, t': Time, rs: set Room] { + all r: Room - rs | r.currentKey.t = r.currentKey.t' + } + +pred noGuestChangeExcept [t, t': Time, gs: set Guest] { + all g: Guest - gs | g.keys.t = g.keys.t' + } + +pred checkout [t, t': Time, g: Guest] { + let occ = FrontDesk.occupant { + some occ.t.g + occ.t' = occ.t - Room ->g + } + FrontDesk.lastKey.t = FrontDesk.lastKey.t' + noRoomChangeExcept [t, t', none] + noGuestChangeExcept [t, t', none] + } + +pred checkin [t, t': Time, g: Guest, r: Room, k: Key] { + g.keys.t' = g.keys.t + k + let occ = FrontDesk.occupant { + no occ.t [r] + occ.t' = occ.t + r -> g + } + let lk = FrontDesk.lastKey { + lk.t' = lk.t ++ r -> k + k = nextKey [lk.t [r], r.keys] + } + noRoomChangeExcept [t, t', none] + noGuestChangeExcept [t, t', g] + } + +fact traces { + init [first] + all t: Time-last | let t' = t.next | + some g: Guest, r: Room, k: Key | + entry [t, t', g, r, k] + or checkin [t, t', g, r, k] + or checkout [t, t', g] + } + +fact NoIntervening { + all t: Time-last | let t' = t.next, t" = t'.next | + all g: Guest, r: Room, k: Key | + checkin [t, t', g, r, k] => (entry [t', t", g, r, k] or no t") + } + +assert NoBadEntry { + all t: Time, r: Room, g: Guest, k: Key | + let t' = t.next, o = FrontDesk.occupant.t[r] | + entry [t, t', g, r, k] and some o => g in o + } + +// After adding the NoIntervening fact, +// these commands no longer generate counterexamples +check NoBadEntry for 3 but 2 Room, 2 Guest, 5 Time +check NoBadEntry for 3 but 3 Room, 3 Guest, 7 Time +check NoBadEntry for 5 but 3 Room, 3 Guest, 9 Time diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter6/hotel3.als b/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter6/hotel3.als new file mode 100644 index 00000000..b3ad4b49 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter6/hotel3.als @@ -0,0 +1,92 @@ +module chapter6/hotel3 --- model in Fig 6.10 without the NonIntervening fact + +open util/ordering[Time] as to +open util/ordering[Key] as ko + +sig Key, Time {} + +sig Room { + keys: set Key, + currentKey: keys one -> Time + } + +fact { + Room <: keys in Room lone -> Key + } + +one sig FrontDesk { + lastKey: (Room -> lone Key) -> Time, + occupant: (Room -> Guest) -> Time + } + +sig Guest { + keys: Key -> Time + } + +fun nextKey [k: Key, ks: set Key]: set Key { + min [k.nexts & ks] + } + +pred init [t: Time] { + no Guest.keys.t + no FrontDesk.occupant.t + all r: Room | FrontDesk.lastKey.t [r] = r.currentKey.t + } + +abstract sig Event { + pre, post: Time, + guest: Guest + } + +abstract sig RoomKeyEvent extends Event { + room: Room, + key: Key + } + +sig Entry extends RoomKeyEvent { } { + key in guest.keys.pre + let ck = room.currentKey | + (key = ck.pre and ck.post = ck.pre) or + (key = nextKey[ck.pre, room.keys] and ck.post = key) + currentKey.post = currentKey.pre ++ room->key + } + +sig Checkin extends RoomKeyEvent { } { + keys.post = keys.pre + guest -> key + let occ = FrontDesk.occupant { + no occ.pre [room] + occ.post = occ.pre + room -> guest + } + let lk = FrontDesk.lastKey { + lk.post = lk.pre ++ room -> key + key = nextKey [lk.pre [room], room.keys] + } + } + +sig Checkout extends Event { } { + let occ = FrontDesk.occupant { + some occ.pre.guest + occ.post = occ.pre - Room -> guest + } + } + +fact Traces { + init [first] + all t: Time-last | + let t' = t.next | + some e: Event { + e.pre = t and e.post = t' + currentKey.t != currentKey.t' => e in Entry + occupant.t != occupant.t' => e in Checkin + Checkout + (lastKey.t != lastKey.t' or keys.t != keys.t') => e in Checkin + } + } + +assert NoBadEntry { + all e: Entry | + let o=FrontDesk.occupant.(e.pre) [e.room] | + some o => e.guest in o + } + +// This generates a counterexample similar to Fig 6.13 +check NoBadEntry for 5 but 3 Room, 3 Guest, 5 Time, 4 Event diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter6/hotel4.als b/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter6/hotel4.als new file mode 100644 index 00000000..35f3566b --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter6/hotel4.als @@ -0,0 +1,103 @@ +module chapter6/hotel4 --- model in Fig 6.10 with the NonIntervening fact + +open util/ordering[Time] as to +open util/ordering[Key] as ko + +sig Key, Time {} + +sig Room { + keys: set Key, + currentKey: keys one -> Time + } + +fact { + Room <: keys in Room lone -> Key + } + +one sig FrontDesk { + lastKey: (Room -> lone Key) -> Time, + occupant: (Room -> Guest) -> Time + } + +sig Guest { + keys: Key -> Time + } + +fun nextKey [k: Key, ks: set Key]: set Key { + min [k.nexts & ks] + } + +pred init [t: Time] { + no Guest.keys.t + no FrontDesk.occupant.t + all r: Room | FrontDesk.lastKey.t [r] = r.currentKey.t + } + +abstract sig Event { + pre, post: Time, + guest: Guest + } + +abstract sig RoomKeyEvent extends Event { + room: Room, + key: Key + } + +sig Entry extends RoomKeyEvent { } { + key in guest.keys.pre + let ck = room.currentKey | + (key = ck.pre and ck.post = ck.pre) or + (key = nextKey[ck.pre, room.keys] and ck.post = key) + currentKey.post = currentKey.pre ++ room->key + } + +sig Checkin extends RoomKeyEvent { } { + keys.post = keys.pre + guest -> key + let occ = FrontDesk.occupant { + no occ.pre [room] + occ.post = occ.pre + room -> guest + } + let lk = FrontDesk.lastKey { + lk.post = lk.pre ++ room -> key + key = nextKey [lk.pre [room], room.keys] + } + } + +sig Checkout extends Event { } { + let occ = FrontDesk.occupant { + some occ.pre.guest + occ.post = occ.pre - Room -> guest + } + } + +fact Traces { + init [first] + all t: Time-last | + let t' = t.next | + some e: Event { + e.pre = t and e.post = t' + currentKey.t != currentKey.t' => e in Entry + occupant.t != occupant.t' => e in Checkin + Checkout + (lastKey.t != lastKey.t' or keys.t != keys.t') => e in Checkin + } + } + +assert NoBadEntry { + all e: Entry | + let o=FrontDesk.occupant.(e.pre) [e.room] | + some o => e.guest in o + } + +fact NoIntervening { + all c: Checkin | + c.post = last + or some e: Entry { + e.pre = c.post + e.room = c.room + e.guest = c.guest + } + } + +// After adding the NoIntervening fact, +// this command no longer generates a counterexample +check NoBadEntry for 5 but 3 Room, 3 Guest, 9 Time, 8 Event diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter6/mediaAssets.als b/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter6/mediaAssets.als new file mode 100644 index 00000000..668b3130 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter6/mediaAssets.als @@ -0,0 +1,117 @@ +module chapter6/mediaAssets + +sig ApplicationState { + catalogs: set Catalog, + catalogState: catalogs -> one CatalogState, + currentCatalog: catalogs, + buffer: set Asset + } + +sig Catalog, Asset {} + +sig CatalogState { + assets: set Asset, + disj hidden, showing: set assets, + selection: set assets + Undefined + } { + hidden+showing = assets + } + +one sig Undefined {} + +pred catalogInv [cs: CatalogState] { + cs.selection = Undefined or (some cs.selection and cs.selection in cs.showing) + } + +pred appInv [xs: ApplicationState] { + all cs: xs.catalogs | catalogInv [xs.catalogState[cs]] + } + +pred showSelected [cs, cs': CatalogState] { + cs.selection != Undefined + cs'.showing = cs.selection + cs'.selection = cs.selection + cs'.assets = cs.assets + } + +pred hideSelected [cs, cs': CatalogState] { + cs.selection != Undefined + cs'.hidden = cs.hidden + cs.selection + cs'.selection = Undefined + cs'.assets = cs.assets + } + +pred cut [xs, xs': ApplicationState] { + let cs = xs.currentCatalog.(xs.catalogState), sel = cs.selection { + sel != Undefined + xs'.buffer = sel + some cs': CatalogState { + cs'.assets = cs.assets - sel + cs'.showing = cs.showing - sel + cs'.selection = Undefined + xs'.catalogState = xs.catalogState ++ xs.currentCatalog -> cs' + } + } + xs'.catalogs = xs.catalogs + xs'.currentCatalog = xs.currentCatalog + } + +pred paste [xs, xs': ApplicationState] { + let cs = xs.currentCatalog.(xs.catalogState), buf = xs.buffer { + xs'.buffer = buf + some cs': CatalogState { + cs'.assets = cs.assets + buf + cs'.showing = cs.showing + (buf - cs.assets) + cs'.selection = buf - cs.assets + xs'.catalogState = xs.catalogState ++ xs.currentCatalog -> cs' + } + } + xs'.catalogs = xs.catalogs + xs'.currentCatalog = xs.currentCatalog + } + +assert HidePreservesInv { + all cs, cs': CatalogState | + catalogInv [cs] and hideSelected [cs, cs'] => catalogInv [cs'] + } + +// This check should not find any counterexample +check HidePreservesInv + +pred sameApplicationState [xs, xs': ApplicationState] { + xs'.catalogs = xs.catalogs + all c: xs.catalogs | sameCatalogState [c.(xs.catalogState), c.(xs'.catalogState)] + xs'.currentCatalog = xs.currentCatalog + xs'.buffer = xs.buffer + } + +pred sameCatalogState [cs, cs': CatalogState] { + cs'.assets = cs.assets + cs'.showing = cs.showing + cs'.selection = cs.selection + } + +assert CutPaste { + all xs, xs', xs": ApplicationState | + (appInv [xs] and cut [xs, xs'] and paste [xs', xs"]) => sameApplicationState [xs, xs"] + } + +// This check should find a counterexample +check CutPaste + +assert PasteCut { + all xs, xs', xs": ApplicationState | + (appInv [xs] and paste [xs, xs'] and cut [xs', xs"]) => sameApplicationState [xs, xs"] + } + +// This check should find a counterexample +check PasteCut + +assert PasteNotAffectHidden { + all xs, xs': ApplicationState | + (appInv [xs] and paste [xs, xs']) => + let c = xs.currentCatalog | xs'.catalogState[c].hidden = xs.catalogState[c].hidden + } + +// This check should not find any counterexample +check PasteNotAffectHidden diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter6/memory/abstractMemory.als b/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter6/memory/abstractMemory.als new file mode 100644 index 00000000..3d676d50 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter6/memory/abstractMemory.als @@ -0,0 +1,33 @@ +module chapter6/memory/abstractMemory [Addr, Data] ----- the model from page 217 + +sig Memory { + data: Addr -> lone Data + } + +pred init [m: Memory] { + no m.data + } + +pred write [m, m': Memory, a: Addr, d: Data] { + m'.data = m.data ++ a -> d + } + +pred read [m: Memory, a: Addr, d: Data] { + let d' = m.data [a] | some d' implies d = d' + } + +fact Canonicalize { + no disj m, m': Memory | m.data = m'.data + } + +// This command should not find any counterexample +WriteRead: check { + all m, m': Memory, a: Addr, d1, d2: Data | + write [m, m', a, d1] and read [m', a, d2] => d1 = d2 + } + +// This command should not find any counterexample +WriteIdempotent: check { + all m, m', m": Memory, a: Addr, d: Data | + write [m, m', a, d] and write [m', m", a, d] => m' = m" + } diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter6/memory/cacheMemory.als b/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter6/memory/cacheMemory.als new file mode 100644 index 00000000..c95fe22b --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter6/memory/cacheMemory.als @@ -0,0 +1,43 @@ +module chapter6/memory/cacheMemory [Addr, Data] ----- the model from page 219 + +sig CacheSystem { + main, cache: Addr -> lone Data + } + +pred init [c: CacheSystem] { + no c.main + c.cache + } + +pred write [c, c': CacheSystem, a: Addr, d: Data] { + c'.main = c.main + c'.cache = c.cache ++ a -> d + } + +pred read [c: CacheSystem, a: Addr, d: Data] { + some d + d = c.cache [a] + } + +pred load [c, c': CacheSystem] { + some addrs: set c.main.Data - c.cache.Data | + c'.cache = c.cache ++ addrs <: c.main + c'.main = c.main + } + +pred flush [c, c': CacheSystem] { + some addrs: some c.cache.Data { + c'.main = c.main ++ addrs <: c.cache + c'.cache = c.cache - addrs -> Data + } + } + +// This command should not find any counterexample +LoadNotObservable: check { + all c, c', c": CacheSystem, a1, a2: Addr, d1, d2, d3: Data | + { + read [c, a2, d2] + write [c, c', a1, d1] + load [c', c"] + read [c", a2, d3] + } implies d3 = (a1=a2 => d1 else d2) + } diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter6/memory/checkCache.als b/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter6/memory/checkCache.als new file mode 100644 index 00000000..8214389b --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter6/memory/checkCache.als @@ -0,0 +1,34 @@ +module chapter6/memory/checkCache [Addr, Data] + +open chapter6/memory/cacheMemory [Addr, Data] as cache +open chapter6/memory/abstractMemory [Addr, Data] as amemory + +fun alpha [c: CacheSystem]: Memory { + {m: Memory | m.data = c.main ++ c.cache} + } + +// This check should not produce a counterexample +ReadOK: check { + // introduction of m, m' ensures that they exist, and gives witnesses if counterexample + all c: CacheSystem, a: Addr, d: Data, m: Memory | + cache/read [c, a, d] and m = alpha [c] => amemory/read [m, a, d] + } + +// This check should not produce a counterexample +WriteOK: check { + all c, c': CacheSystem, a: Addr, d: Data, m, m': Memory | + cache/write [c, c', a, d] and m = alpha [c] and m' = alpha [c'] + => amemory/write [m, m', a, d] + } + +// This check should not produce a counterexample +LoadOK: check { + all c, c': CacheSystem, m, m': Memory | + cache/load [c, c'] and m = alpha [c] and m' = alpha [c'] => m = m' + } + +// This check should not produce a counterexample +FlushOK: check { + all c, c': CacheSystem, m, m': Memory | + cache/flush [c, c'] and m = alpha [c] and m' = alpha [c'] => m = m' + } diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter6/memory/checkFixedSize.als b/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter6/memory/checkFixedSize.als new file mode 100644 index 00000000..20d291d1 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter6/memory/checkFixedSize.als @@ -0,0 +1,28 @@ +module chapter6/memory/checkFixedSize [Addr, Data] + +open chapter6/memory/fixedSizeMemory_H [Addr, Data] as fmemory +open chapter6/memory/abstractMemory [Addr, Data] as amemory + +// define abstraction function from history-extended concrete state to abstract state +pred alpha [fm: fmemory/Memory_H, am: amemory/Memory] { + am.data = fm.data - (fm.unwritten -> Data) + } + +// This check should not find a counterexample +initOk: check { + all fm: fmemory/Memory_H, am: amemory/Memory | + fmemory/init [fm] and alpha [fm, am] => amemory/init [am] + } + +// This check should not find a counterexample +readOk: check { + all fm: fmemory/Memory_H, a: Addr, d: Data, am: amemory/Memory | + fmemory/read [fm, a, d] and alpha [fm, am] => amemory/read [am, a, d] + } + +// This check should not find a counterexample +writeOk: check { + all fm, fm': fmemory/Memory_H, a: Addr, d: Data, am, am': amemory/Memory | + fmemory/write [fm, fm', a, d] and alpha [fm, am] and alpha [fm', am'] + implies amemory/write [am, am', a, d] + } diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter6/memory/fixedSizeMemory.als b/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter6/memory/fixedSizeMemory.als new file mode 100644 index 00000000..df6ab2a9 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter6/memory/fixedSizeMemory.als @@ -0,0 +1,21 @@ +module chapter6/memory/fixedSizeMemory [Addr, Data] + +sig Memory { + data: Addr -> one Data + } + +pred init [m: Memory] { + // This predicate is empty in order to allow non-deterministic initialization + } + +pred write [m, m': Memory, a: Addr, d: Data] { + m'.data = m.data ++ a -> d + } + +pred read [m: Memory, a: Addr, d: Data] { + d = m.data [a] + } + +fact Canonicalize { + no disj m, m': Memory | m.data = m'.data + } diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter6/memory/fixedSizeMemory_H.als b/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter6/memory/fixedSizeMemory_H.als new file mode 100644 index 00000000..15785991 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter6/memory/fixedSizeMemory_H.als @@ -0,0 +1,21 @@ +module chapter6/memory/fixedSizeMemory_H [Addr, Data] + +open chapter6/memory/fixedSizeMemory [Addr, Data] as memory + +sig Memory_H extends memory/Memory { + unwritten: set Addr + } + +pred init [m: Memory_H] { + memory/init [m] + m.unwritten = Addr + } + +pred read [m: Memory_H, a: Addr, d: Data] { + memory/read [m, a, d] + } + +pred write [m, m': Memory_H, a: Addr, d: Data] { + memory/write [m, m', a, d] + m'.unwritten = m.unwritten - a + } diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter6/ringElection.thm b/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter6/ringElection.thm new file mode 100644 index 00000000..0d172180 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter6/ringElection.thm @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter6/ringElection1.als b/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter6/ringElection1.als new file mode 100644 index 00000000..043b6a07 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter6/ringElection1.als @@ -0,0 +1,57 @@ +module chapter6/ringElection1 --- the version up to the top of page 181 + +open util/ordering[Time] as TO +open util/ordering[Process] as PO + +sig Time {} + +sig Process { + succ: Process, + toSend: Process -> Time, + elected: set Time + } + +fact ring { + all p: Process | Process in p.^succ + } + +pred init [t: Time] { + all p: Process | p.toSend.t = p + } + +pred step [t, t': Time, p: Process] { + let from = p.toSend, to = p.succ.toSend | + some id: from.t { + from.t' = from.t - id + to.t' = to.t + (id - p.succ.prevs) + } + } + +fact defineElected { + no elected.first + all t: Time-first | elected.t = {p: Process | p in p.toSend.t - p.toSend.(t.prev)} + } + +fact traces { + init [first] + all t: Time-last | + let t' = t.next | + all p: Process | + step [t, t', p] or step [t, t', succ.p] or skip [t, t', p] + } + +pred skip [t, t': Time, p: Process] { + p.toSend.t = p.toSend.t' + } + +pred show { some elected } +run show for 3 Process, 4 Time +// This generates an instance similar to Fig 6.4 + +assert AtMostOneElected { lone elected.Time } +check AtMostOneElected for 3 Process, 7 Time +// This should not find any counterexample + +assert AtLeastOneElected { some t: Time | some elected.t } +check AtLeastOneElected for 3 Process, 7 Time +// This generates a counterexample in which nothing happens diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter6/ringElection2.als b/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter6/ringElection2.als new file mode 100644 index 00000000..16bee928 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/book/chapter6/ringElection2.als @@ -0,0 +1,74 @@ +module chapter6/ringElection2 --- the final version (as depicted in Fig 6.1) + +open util/ordering[Time] as TO +open util/ordering[Process] as PO + +sig Time {} + +sig Process { + succ: Process, + toSend: Process -> Time, + elected: set Time + } + +fact ring { + all p: Process | Process in p.^succ + } + +pred init [t: Time] { + all p: Process | p.toSend.t = p + } + +pred step [t, t': Time, p: Process] { + let from = p.toSend, to = p.succ.toSend | + some id: from.t { + from.t' = from.t - id + to.t' = to.t + (id - p.succ.prevs) + } + } + +fact defineElected { + no elected.first + all t: Time-first | elected.t = {p: Process | p in p.toSend.t - p.toSend.(t.prev)} + } + +fact traces { + init [first] + all t: Time-last | + let t' = t.next | + all p: Process | + step [t, t', p] or step [t, t', succ.p] or skip [t, t', p] + } + +pred skip [t, t': Time, p: Process] { + p.toSend.t = p.toSend.t' + } + +pred show { some elected } +run show for 3 Process, 4 Time +// This generates an instance similar to Fig 6.4 + +assert AtMostOneElected { lone elected.Time } +check AtMostOneElected for 3 Process, 7 Time +// This should not find any counterexample + +pred progress { + all t: Time - TO/last | + let t' = TO/next [t] | + some Process.toSend.t => some p: Process | not skip [t, t', p] + } + +assert AtLeastOneElected { progress => some elected.Time } +check AtLeastOneElected for 3 Process, 7 Time +// This should not find any counterexample + +pred looplessPath { no disj t, t': Time | toSend.t = toSend.t' } + +// This produces an instance +run looplessPath for 3 Process, 12 Time + +// This does not produce an instance +run looplessPath for 3 Process, 13 Time + +// Therefore, we can conclude that a scope of 12 for Time is +// sufficient to reach all states of the protocol for a three-node ring. diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/examples/algorithms/dijkstra.als b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/algorithms/dijkstra.als new file mode 100644 index 00000000..ab342811 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/algorithms/dijkstra.als @@ -0,0 +1,120 @@ +module examples/algorithms/dijkstra + +/* + * Models how mutexes are grabbed and released by processes, and + * how Dijkstra's mutex ordering criterion can prevent deadlocks. + * + * For a detailed description, see: + * E. W. Dijkstra, Cooperating sequential processes. Technological + * University, Eindhoven, The Netherlands, September 1965. + * Reprinted in Programming Languages, F. Genuys, Ed., Academic + * Press, New York, 1968, 43-112. + * + * Acknowledgements to Ulrich Geilmann for finding and fixing a bug + * in the GrabMutex predicate. + * + */ + +open util/ordering [State] as so +open util/ordering [Mutex] as mo + +sig Process {} +sig Mutex {} + +sig State { holds, waits: Process -> Mutex } + + +pred Initial [s: State] { no s.holds + s.waits } + +pred IsFree [s: State, m: Mutex] { + // no process holds this mutex + no m.~(s.holds) + // all p: Process | m !in p.(this.holds) +} + +pred IsStalled [s: State, p: Process] { some p.(s.waits) } + +pred GrabMutex [s: State, p: Process, m: Mutex, s': State] { + // a process can only act if it is not + // waiting for a mutex + !s.IsStalled[p] + // can only grab a mutex we do not yet hold + m !in p.(s.holds) + // mutexes are grabbed in order + all m': p.(s.holds) | mo/lt[m',m] + s.IsFree[m] => { + // if the mutex is free, we now hold it, + // and do not become stalled + p.(s'.holds) = p.(s.holds) + m + no p.(s'.waits) + } else { + // if the mutex was not free, + // we still hold the same mutexes we held, + // and are now waiting on the mutex + // that we tried to grab. + p.(s'.holds) = p.(s.holds) + p.(s'.waits) = m + } + all otherProc: Process - p | { + otherProc.(s'.holds) = otherProc.(s.holds) + otherProc.(s'.waits) = otherProc.(s.waits) + } +} + +pred ReleaseMutex [s: State, p: Process, m: Mutex, s': State] { + !s.IsStalled[p] + m in p.(s.holds) + p.(s'.holds) = p.(s.holds) - m + no p.(s'.waits) + no m.~(s.waits) => { + no m.~(s'.holds) + no m.~(s'.waits) + } else { + some lucky: m.~(s.waits) | { + m.~(s'.waits) = m.~(s.waits) - lucky + m.~(s'.holds) = lucky + } + } + all mu: Mutex - m { + mu.~(s'.waits) = mu.~(s.waits) + mu.~(s'.holds)= mu.~(s.holds) + } +} + +/** + * for every adjacent (pre,post) pair of States, + * one action happens: either some process grabs a mutex, + * or some process releases a mutex, + * or nothing happens (have to allow this to show deadlocks) + */ +pred GrabOrRelease { + Initial[so/first] && + ( + all pre: State - so/last | let post = so/next [pre] | + (post.holds = pre.holds && post.waits = pre.waits) + || + (some p: Process, m: Mutex | pre.GrabMutex [p, m, post]) + || + (some p: Process, m: Mutex | pre.ReleaseMutex [p, m, post]) + + ) +} + +pred Deadlock { + some Process + some s: State | all p: Process | some p.(s.waits) +} + +assert DijkstraPreventsDeadlocks { + GrabOrRelease => ! Deadlock +} + + +pred ShowDijkstra { + GrabOrRelease && Deadlock + some waits +} + +run Deadlock for 3 expect 1 +run ShowDijkstra for 5 State, 2 Process, 2 Mutex expect 1 +check DijkstraPreventsDeadlocks for 5 State, 5 Process, 4 Mutex expect 0 diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/examples/algorithms/dijkstra.thm b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/algorithms/dijkstra.thm new file mode 100644 index 00000000..3be2d6c2 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/algorithms/dijkstra.thm @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/examples/algorithms/messaging.als b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/algorithms/messaging.als new file mode 100644 index 00000000..0815fb44 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/algorithms/messaging.als @@ -0,0 +1,228 @@ +module examples/algorithms/messaging + +/* + * Generic messaging among several nodes + * + * By default, messages can be lost (i.e. never become visible to the + * recipient node) or may be arbitrarily delayed. Also, by default + * out-of-order delivery is allowed. + */ + +open util/ordering[Tick] as ord +open util/relation as rel + +sig Node {} + +sig MsgState { + /** Node that sent the message */ + from: Node, + + /** Intended recipient(s) of a message; note that broadcasts are allowed */ + to: set Node +} + +sig Msg { + state: MsgState, + + /** Timestamp: the tick on which the message was sent */ + sentOn: Tick, + + /** tick at which node reads message, if read */ + readOn: Node -> lone Tick +}{ + readOn.Tick in state.to +} + +sig Tick { + /** The state of each node */ + state: Node -> one NodeState, + + /** + * Definition of what each node does on this tick: + * + * Typically, a node would determine + * the messages it sends and its next state, based on its current + * state and the messages it reads. + * + * Messages that the node _can_ read in this tick, i.e. messages available + * for reading at the beginning of this tick. The messages that + * the node actually reads are a subset of this set. Determined by + * constraints in this module. + */ + visible: Node -> Msg, + + /** + * Messages that the node _actually reads_ in this tick. Must be a subset + * of visible. Determined by constraints of the particular system + * that uses this module. + */ + read: Node -> Msg, + + /** + * Messages sent by the node in this tick. They become visible to + * (and can be read by) their recipients on the next tick. + */ + sent: Node -> Msg, + + /** + * Messages available for sending at this tick. A given message + * atom is only used once, and then it gets removed from the available + * set and cannot be used to represent messages sent on subsequent ticks. + * Also, two different nodes cannot send the same message atom. + * So, a message atom represents a particular single physical message + * sent by a given node on a given tick. + */ + available: set Msg, + + /** + * For each node, at each tick, the number of messages it _needs_ to send. + * Used to rule out "proofs" of liveness violations that are caused + * solely by not having enough messages available for sending. + */ + needsToSend: Node -> Msg +} + +fun MsgsSentOnTick[t: Tick]: set Msg { t.sent[Node] } +fun MsgsVisibleOnTick[t: Tick]: set Msg { t.visible[Node] } +fun MsgsReadOnTick[t: Tick]: set Msg { t.read[Node] } + +fact MsgMovementConstraints { + // At the beginning, no messages have been sent yet + no ord/first.visible[Node] + + // Messages sent on a given tick become visible to recipient(s) + // on the subsequent tick. + all pre: Tick - ord/last | + let post = ord/next[pre] | { + // messages sent on this tick are no longer available on subsequent tick + post.available = pre.available - MsgsSentOnTick[pre] + } + + all t: Tick | { + // Messages sent on a tick are taken from the pool of available + // (not-yet-sent) message atoms + MsgsSentOnTick[t] in t.available + + // Timestamps are correct + MsgsSentOnTick[t].sentOn in t + MsgsReadOnTick[t].readOn[Node] in t + + // The only new message atoms are those sent by nodes + MsgsSentOnTick[t] = t.sent[Node] + + all n: Node, m: Msg | + m.readOn[n] = t => m in t.read[n] + // Return addresses are correct + all n: Node | t.sent[n].state.from in n + + // messages sent to a node on a tick become visible to that node on some subseqent tick, + // and permanently stop being visible to that node on the tick after that node reads the message + all n: Node, m: Msg | { + // message starts being visible to node n no earlier than it is sent; + // only messages sent to this node are visible to this node. + (m in t.visible[n] => (n in m.state.to && m.sentOn in ord/prevs[t])) + // message permanently stops being visible immediately after being read + (m in t.read[n] => m !in ord/nexts[t].visible[n]) + } + } +} + +sig NodeState { +} + +fun MsgsLiveOnTick[t: Tick]: set Msg { + Msg - { future: Msg | future.sentOn in ord/nexts[t] } + - { past: Msg | all n: past.state.to | past.readOn[n] in ord/prevs[t] } +} + +pred TicksEquivalent[t1, t2: Tick] { + t1.state = t2.state + some r: (MsgsLiveOnTick[t1] - MsgsVisibleOnTick[t1]) one -> one (MsgsLiveOnTick[t2] - MsgsVisibleOnTick[t2]) | + all m1: dom[r] | let m2 = m1.r | { + m1.(Msg<:state) = m2.state + } + some r: MsgsVisibleOnTick[t1] one -> one MsgsVisibleOnTick[t2] | + all m1: dom[r] | let m2 = m1.r | { + m1.(Msg<:state) = m2.state + } +} + +pred Loop { + some t: Tick - ord/last | TicksEquivalent[t, ord/last] +} + +fact CleanupViz { + // cleans up visualization without precluding any interesting traces + + // all messages must be sent + Msg in Tick.sent[Node] +} + +pred ReadInOrder { + // + // This function ensures that messages are read in order. + // + + // for all pairs of nodes + all n1, n2: Node | + // for all pairs of messages sent from n1 to n2 + all m1, m2: Msg | + { + m1.state.from = n1 + m2.state.from = n1 + m1.state.to = n2 + m2.state.to = n2 + } => { + // if both m1 and m2 are read by n2, *and* + // n2 reads m1 before m2, then m1 must have + // been sent before m2 + (some m1.readOn[n2] && some m2.readOn[n2] && + m1.readOn[n2] in ord/prevs[m2.readOn[n2]]) => + ord/lte[m1.sentOn, m2.sentOn] + } +} + +fact ReadOnlyVisible { read in visible } + +/** + * this function ensures that messages will not + * be lost, i.e. a message send to a node will + * eventually be visible to that node + */ +pred NoLostMessages { + all m: Msg | + (m.sentOn != ord/last) => (all n: m.state.to | + some t: ord/nexts[m.sentOn] | + m in t.visible[n]) +} + +/** + * this function ensures that there will be + * no shortage of messages in the available + * message pool during the trace + */ +pred NoMessageShortage { + all t: Tick - ord/last | + (sum n: Node | # t.needsToSend[n]) =< # t.available +} + +pred SomeState { + # Node > 1 + //# Tick$read > 1 +} + +pred OutOfOrder { + ! ReadInOrder + # Msg = 2 +} + +run SomeState for 2 expect 1 +run OutOfOrder for 4 expect 1 + + + +// DEFINED VARIABLES +// Defined variables are uncalled, no-argument functions. +// They are helpful for getting good visualization. +fun FROM: Msg -> Node {{m: Msg, n: Node | n in m.state.from}} +fun TO: Msg -> Node {{m: Msg, n: Node | n in m.state.to}} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/examples/algorithms/messaging.thm b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/algorithms/messaging.thm new file mode 100644 index 00000000..53f28d8d --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/algorithms/messaging.thm @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/examples/algorithms/opt_spantree.als b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/algorithms/opt_spantree.als new file mode 100644 index 00000000..7c129325 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/algorithms/opt_spantree.als @@ -0,0 +1,220 @@ +module examples/algorithms/opt_spantree + +/* + * Direct specification of a distributed spanning tree algorithm + * over arbitrary network topologies + * + * Each process has a parent and a level, both of which are + * initally null. A distinct root node exists at which the + * algorithm starts. In the first step, the root assigns itself + * the level of zero and sends its level to its neighbors. + * Subsequently, if a node reads a message with level k, it sets + * its level to k+1, records the sender of the message as its + * parent, and sends the level k+1 to its neighbors. Once a node + * has set its level and parent, it ignores subsequent messages. + * Eventually, the parent pointers will form a spanning tree, + * rooted at Root. + * + * We model communication through a state-reading model, in which + * nodes can directly read the state of their neighbors. Messages + * are not explicity modelled. This makes no difference for this + * algorithm since once a node sends a message, the state of the + * node stays the same as the contents of the message. + */ + +open util/ordering[Lvl] as lo +open util/ordering[State] as so +open util/graph[Process] as graph + +sig Process { + adj : set Process +} + +one sig Root extends Process {} + +/** + * intuitively, the depth level at which + * a process resides in the spanning tree, + * with the root at level zero + */ +sig Lvl {} + +fact processGraph { + graph/noSelfLoops[adj] // for viz + graph/undirected[adj] // adjacency is symmetric + Process in Root.*adj // everything reachable from root +} + +sig State { + /** + * the set of processes which execute in this state. + * used to allow flexibility in how many processes + * run simultaneously + */ + runs : set Process, + + /** + * the level of a process in this state + */ + lvl: Process -> lone Lvl, + + /** + * who the process thinks is its parent in this state. + * the parent pointers should eventually become + * the spanning tree + */ + parent: Process -> lone Process +} + +/** + * initially, the lvl and parent fields are blank + */ +pred Init { + let fs = so/first | { + no fs.lvl + no fs.parent + } +} + +/** + * simple NOP transition + */ +pred TRNop[p : Process, pre, post: State] { + pre.lvl[p] = post.lvl[p] + pre.parent[p] = post.parent[p] +} + +/** + * preconditions for a process to actually act + * in a certain pre-state + * used to preclude stalling of entire system + * for no reason (see TransIfPossible) + */ +pred TRActPreConds[p : Process, pre : State] { + // can't already have a level + no pre.lvl[p] + // must have a neighbor with a set level so + // p can read it + // Root doesn't need to read value from a + // neighbor + (p = Root || some pre.lvl[p.adj]) +} + +/** + * transition which changes state of a process + */ +pred TRAct[p : Process, pre, post : State] { + // can't already have a level + no pre.lvl[p] + (p = Root) => { + // the root sets its level to + // 0, and has no parent pointer + post.lvl[p] = lo/first + no post.parent[p] + } else { + // choose some adjacent process + // whose level is already set + some adjProc: p.adj | + let nLvl = pre.lvl[adjProc] | { + some nLvl + // p's parent is the adjacent + // process, and p's level is one greater than + // the level of the adjacent process (since + // its one level deeper) + post.lvl[p] = lo/next[nLvl] + post.parent[p] = adjProc + } + } +} + +pred Trans[p : Process, pre, post : State] { + TRAct[p, pre, post] || + TRNop[p, pre, post] +} + +/** + * all processes do a nop transition in some + * state only when no process can act because + * preconditions are not met + */ +fact TransIfPossible { + all s : State - so/last | + (all p : Process | TRNop[p, s, so/next[s]]) => + (all p : Process | !TRActPreConds[p,s]) +} + +fact LegalTrans { + Init + all s : State - so/last | + let s' = so/next[s] | { + all p : Process | + p in s.runs => Trans[p, s, s'] else TRNop[p,s,s'] + } +} + +pred PossTrans[s, s' : State] { + all p : Process | Trans[p,s,s'] +} + +pred SpanTreeAtState[s : State] { + // all processes reachable through inverted parent pointers + // from root (spanning) + Process in Root.*~(s.parent) + // parent relation is a tree (DAG) + // we only need to check the DAG condition since there can + // be at most one parent for a process (constrained by + // multiplicity) + graph/dag[~(s.parent)] +} + +/** + * show a run that produces a spanning tree + */ +pred SuccessfulRun { + SpanTreeAtState[so/last] + all s : State - so/last | !SpanTreeAtState[s] +} + +/** + * show a trace without a loop + */ +pred TraceWithoutLoop { + all s, s' : State | s!=s' => { + !EquivStates[s, s'] + (s' in so/nexts[s] && (s' != so/next[s])) => !PossTrans[s,s'] + } + all s: State | !SpanTreeAtState[s] +} + +/** + * defines equivalent states + */ +pred EquivStates[s, s' : State] { + s.lvl = s'.lvl + s.parent = s'.parent +} + +/** + * show a trace that violates liveness + */ +pred BadLivenessTrace { + // two different states equivalent (loop) + some s, s' : State | s!=s' && EquivStates[s, s'] + all s : State | !SpanTreeAtState[s] +} + +/** + * check that once spanning tree is constructed, + * it remains + */ +assert Closure { + all s : State - so/last | + SpanTreeAtState[s] => (s.parent = so/next[s].parent) +} + +// note that for the worst case topology and choice of root, +// the scope of Lvl must equal the scope of Process +run SuccessfulRun for 4 State, exactly 5 Process, 3 Lvl expect 1 +// run TraceWithoutLoop for 8 but 9 State expect 1 +run BadLivenessTrace for 5 but 7 State expect 0 +check Closure for 5 but 7 State expect 0 diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/examples/algorithms/opt_spantree.thm b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/algorithms/opt_spantree.thm new file mode 100644 index 00000000..1254faed --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/algorithms/opt_spantree.thm @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/examples/algorithms/peterson.als b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/algorithms/peterson.als new file mode 100644 index 00000000..6e6d59e5 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/algorithms/peterson.als @@ -0,0 +1,206 @@ +module examples/algorithms/peterson + +/* + * Model of Peterson's algorithm for mutual exclusion of n + * processes. The names kept similar to Murphi specification + * to make correspondence clear. + */ + +open util/ordering[priority] as po +open util/ordering[State] as so + +sig pid { +} + +sig priority { +} + +fact { + # priority = # pid + 1 +} + +abstract sig label_t {} + +// here subtyping would help +one sig L0, L1, L2, L3, L4 extends label_t {} + +sig State { + P: pid -> label_t, + Q: pid -> priority, + turn: priority -> pid, + localj: pid -> priority +} + +pred NOPTrans[i: pid, pre, post : State] { + post.P[i] = pre.P[i] + post.Q[i] = pre.Q[i] + post.localj[i] = pre.localj[i] +} + +pred L0TransPre[i : pid, pre : State] { + // precondition + pre.P[i] = L0 +} + +pred L0Trans[i: pid, pre, post : State] { + L0TransPre[i, pre] + // localj[i] := 1 + post.localj[i] = po/next[po/first] + post.P[i] = L1 + post.Q[i] = pre.Q[i] + // something for turn? + post.turn = pre.turn +} + +pred L1TransPre[i : pid, pre : State] { + // precondition + pre.P[i] = L1 +} + +pred L1Trans[i : pid, pre, post : State] { + L1TransPre[i, pre] + post.localj[i] = pre.localj[i] + post.Q[i] = pre.localj[i] + post.P[i] = L2 + // something for turn? + post.turn = pre.turn +} + +pred L2TransPre[i : pid, pre : State] { + // precondition + pre.P[i] = L2 +} + +pred L2Trans[i : pid, pre, post : State] { + L2TransPre[i, pre] + post.localj[i] = pre.localj[i] + post.Q[i] = pre.Q[i] + post.P[i] = L3 + post.turn[post.localj[i]] = i + all j : priority - post.localj[i] | + post.turn[j] = pre.turn[j] +} + +pred L3TransPre[i : pid, pre : State] { + // precondition + pre.P[i] = L3 + + all k : pid - i | + po/lt[pre.Q[k], pre.localj[i]] || + pre.turn[pre.localj[i]] != i +} + +pred L3Trans[i : pid, pre, post : State] { + L3TransPre[i, pre] + post.localj[i] = po/next[pre.localj[i]] + po/lt[post.localj[i], po/last] => + post.P[i] = L1 + else + post.P[i] = L4 + post.Q[i] = pre.Q[i] + post.turn = pre.turn +} + +pred L4TransPre[i : pid, pre : State] { + // precondition + pre.P[i] = L4 +} + +pred L4Trans[i : pid, pre, post : State] { + L4TransPre[i, pre] + + post.P[i] = L0 + post.Q[i] = po/first + post.localj[i] = pre.localj[i] + post.turn = pre.turn +} + +pred RealTrans[i : pid, pre, post : State] { + L0Trans[i,pre,post] || + L1Trans[i,pre,post] || + L2Trans[i,pre,post] || + L3Trans[i,pre,post] || + L4Trans[i,pre,post] +} + +pred SomePre[i : pid, pre : State] { + L0TransPre[i, pre] || + L1TransPre[i, pre] || + L2TransPre[i, pre] || + L3TransPre[i, pre] || + L4TransPre[i, pre] +} + +fact Init { + let firstState = so/first | { + all i : pid | { + firstState.P[i] = L0 + firstState.Q[i] = po/first + } + no firstState.turn + no firstState.localj + } +} + +fact LegalTrans { + all pre : State - so/last | + let post = so/next[pre] | { + /*some i : pid | { + // HACK: + // need to specify that if some node + // can make a non-NOP transition, it + // does, but i can't figure out how + // right now + Trans(i,pre,post) && !NOPTrans(i,pre,post) + all j : pid - i | + NOPTrans(j,pre,post) + }*/ + all i : pid | + RealTrans[i,pre,post] || NOPTrans[i,pre,post] + (all i : pid | NOPTrans[i,pre,post]) => { + all i : pid | !SomePre[i,pre] + post.turn = pre.turn + } + } +} + +assert Safety { + all i1, i2 : pid, s : State | i1!=i2 => not (s.P[i1] = L4 && s.P[i2] = L4) +} + +assert NotStuck { + all pre : State - so/last | + let post = so/next[pre] | + some i : pid | + RealTrans[i, pre, post] && !NOPTrans[i,pre,post] +} + +pred TwoRun { + some s1, s2: State, i1, i2: pid | { + s1!=s2 + i1!=i2 + s1.P[i1] = L4 + s2.P[i2] = L4 + } +} + +pred ThreeRun { + some disj s1, s2, s3: State, disj i1, i2, i3: pid | { + s1.P[i1] = L4 + s2.P[i2] = L4 + s3.P[i3] = L4 + } +} + +// 2 pids requires 8 states +// 3 pids requires 16 states +run TwoRun for 13 but 3 pid, 4 priority, 5 label_t expect 1 + +// haven't run this one successfully yet +run ThreeRun for 19 but 3 pid,4 priority,5 label_t expect 1 + +// how many states do we need for this to match murphi? +check Safety for 10 but 2 pid, 3 priority, 5 label_t expect 0 + +// this assertion is trivial because of the hack described above +check NotStuck for 10 but 2 pid, 3 priority, 5 label_t expect 0 diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/examples/algorithms/ringlead.als b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/algorithms/ringlead.als new file mode 100644 index 00000000..b5a786a8 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/algorithms/ringlead.als @@ -0,0 +1,150 @@ +module examples/algorithms/ringlead + +/* + * Model of leader election on a ring + * + * Each process has a unique ID, IDs are ordered. + * The algorithm elects the process with the highest + * ID the leader, as follows. First, each process + * sends its own ID to its right neighbor. + * Then, whenever a process receives an ID, if the + * ID is greater than the process' own ID it forwards + * the ID to its right neighbor, otherwise does nothing. + * When a process receives its own ID that process + * is the leader. + */ + +open util/boolean as bool +open examples/algorithms/messaging as msg +open util/ordering[msg/Node] as nodeOrd +open util/ordering[msg/Tick] as tickOrd + +sig RingLeadNode extends msg/Node { + rightNeighbor: msg/Node +} + +fact DefineRing { + (one msg/Node || (no n: msg/Node | n = n.rightNeighbor)) + all n: msg/Node | msg/Node in n.^rightNeighbor +} + +sig RingLeadMsgState extends msg/MsgState { + id: msg/Node +} + +sig MsgViz extends msg/Msg { + vFrom: msg/Node, + vTo: set msg/Node, + vId: msg/Node +} + +fact { + MsgViz = msg/Msg + vFrom = state.from + vTo = state.to + vId = state.id +} + + +sig RingLeadNodeState extends msg/NodeState { + leader: Bool +} + + +pred RingLeadFirstTrans [self: msg/Node, pre, post: msg/NodeState, + sees, reads, sends, needsToSend: set msg/Msg] { + one sends + # needsToSend = 1 + sends.state.to = self.rightNeighbor + sends.state.id = self + post.leader = False +} + +fact InitRingLeadState { + all n: msg/Node | + tickOrd/first.state[n].leader = False +} + +pred RingLeadRestTrans [self: msg/Node, pre, post: msg/NodeState, + sees, reads, sends, needsToSend: set msg/Msg] { + RingLeadTransHelper[self, sees, reads, sends, needsToSend] + post.leader = True iff (pre.leader = True || + self in reads.state.id) +} + +/** + * we take any messages whose node ids are higher than ours, + * and we forward them to the right neighbor. we drop + * all other messages. if we get a message with our own + * id, we're the leader. + */ +pred RingLeadTransHelper[self: msg/Node, sees, reads, sends, needsToSend: set msg/Msg] { + reads = sees + + all received: reads | + (received.state.id in nodeOrd/nexts[self]) => + (one weSend: sends | (weSend.state.id = received.state.id && weSend.state.to = self.rightNeighbor)) + + all weSend: sends | { + let mID = weSend.state.id | { + mID in nodeOrd/nexts[self] + mID in reads.state.id + weSend.state.to = self.rightNeighbor + } + //weSend.sentBecauseOf = { received : reads | received.id = weSend.id } + //all otherWeSend: sends - weSend | otherWeSend.id != weSend.id + } + + # needsToSend = # { m: reads | m.state.id in nodeOrd/nexts[self] } +} +fact RingLeadTransitions { + all n: msg/Node { + all t: msg/Tick - tickOrd/last | { + t = tickOrd/first => + RingLeadFirstTrans[n, t.state[n], tickOrd/next[t].state[n], t.visible[n], t.read[n], t.sent[n], t.needsToSend[n]] + else + RingLeadRestTrans[n, t.state[n], tickOrd/next[t].state[n], t.visible[n], t.read[n], t.sent[n], t.needsToSend[n]] + } + // also constrain last tick + RingLeadTransHelper[n, tickOrd/last.visible[n], tickOrd/last.read[n], tickOrd/last.sent[n], tickOrd/last.needsToSend[n]] + } +} + +assert OneLeader { + all t: msg/Tick | + lone n: msg/Node | + t.state[n].leader = True +} + +fact CleanupViz { + RingLeadNode = msg/Node + RingLeadMsgState = msg/MsgState + RingLeadNodeState = msg/NodeState +} + +pred SomeLeaderAtTick[t: msg/Tick] { + some n: msg/Node | t.state[n].leader = True +} + +pred NeverFindLeader { + msg/Loop + all t: msg/Tick | ! SomeLeaderAtTick[t] +} + +assert Liveness { + (msg/NoLostMessages && msg/NoMessageShortage) => ! NeverFindLeader +} + +pred SomeLeader { some t: msg/Tick | SomeLeaderAtTick[t] } + +assert LeaderHighest { + all t: msg/Tick, n: msg/Node | + t.state[n].leader = True => n = nodeOrd/last +} + +run NeverFindLeader for 1 but 3 msg/Tick, 2 Bool, 2 msg/NodeState expect 1 +check Liveness for 3 but 6 msg/Msg, 2 Bool, 2 msg/NodeState expect 0 +check OneLeader for 5 but 2 Bool, 2 msg/NodeState expect 0 +run SomeLeader for 2 but 3 msg/Node, 5 msg/Msg, 5 msg/Tick, 5 msg/MsgState expect 1 +check LeaderHighest for 3 but 2 msg/NodeState, 5 msg/Msg, 5 msg/MsgState, 5 msg/Tick expect 0 + diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/examples/algorithms/ringlead.thm b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/algorithms/ringlead.thm new file mode 100644 index 00000000..95977a51 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/algorithms/ringlead.thm @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/examples/algorithms/s_ringlead.als b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/algorithms/s_ringlead.als new file mode 100644 index 00000000..8f96b477 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/algorithms/s_ringlead.als @@ -0,0 +1,153 @@ +module examples/algorithms/s_ringlead + +/* + * Model of leader election on a ring. + * + * Each process has a unique ID, IDs are ordered. The algorithm + * elects the process with the highest ID the leader, as follows. + * First, each process sends its own ID to its right neighbor. + * Then, whenever a process receives an ID, if the ID is greater + * than the process' own ID it forwards the ID to its right + * neighbor, otherwise does nothing. When a process receives its + * own ID that process is the leader. + * + * Note: This file needs higher order quantifiers turned on. + */ + +open util/ordering[State] as so +open util/ordering[Process] as po +open util/graph[Process] as graph + +sig Process { + rightNeighbor: Process +} + +sig State { + // buffer which the right neighbor can read from + buffer: Process -> Process, + //sends, reads: Process -> Process, + runs: set Process, + leader: set Process +} + +fact DefineRing { + graph/ring[rightNeighbor] +} + +fact InitialState { + no so/first.buffer + no so/first.leader + Process in so/first.runs +} + + +fact CleanupLast { + let ls = so/last | + no ls.runs +} + +pred ValidTrans2[s, s': State] { + all p : s.runs | VT2Helper[p,s,s'] + all p : Process - s.runs | NOP2[p,s,s'] + NoMagicMsg[s,s'] + +} + +pred NoMagicMsg[s, s' : State] { + // no magically appearing messages + all p : Process, m : s'.buffer[p] | + m in s.buffer[p] || (!NOP2[p,s,s'] && + ((s = so/first && m = p) || + (s != so/first && m in s.buffer[p.~rightNeighbor] + && m !in s'.buffer[p.~rightNeighbor] && po/gt[m,p]))) +} + +pred PossTrans[s, s' : State] { + all p : Process | VT2Helper[p,s,s'] || NOP2[p,s,s'] + NoMagicMsg[s,s'] +} + +pred VT2Helper[p : Process, s, s' : State] { + ( + let readable=s.buffer[p.~rightNeighbor] | + (s = so/first) => { + p = s'.buffer[p] + readable in s'.buffer[p.~rightNeighbor] + p !in s'.leader + } else { + (some readable) => { + some m : set readable | { + m !in s'.buffer[p.~rightNeighbor] + // nothing else gets deleted + readable - m in s'.buffer[p.~rightNeighbor] + { m': m | po/gt[m',p] } /*m & nexts(p)*/ in s'.buffer[p] + p in s'.leader iff (p in s.leader || p in m) + } + } else NOP2[p,s,s'] + } + ) +} + +pred NOP2[p : Process, s,s': State] { + p in s'.leader iff p in s.leader + // no reads + s.buffer[p.~rightNeighbor] in s'.buffer[p.~rightNeighbor] + // no sends + s'.buffer[p] in s.buffer[p] +} + +pred Preconds[p : Process, s : State] { + s = so/first || some s.buffer[p.~rightNeighbor] +} + +fact TransIfPossible { + all s : State - so/last | + (all p : Process | NOP2[p, s, so/next[s]]) => + (all p : Process | !Preconds[p,s]) +} + +fact LegalTrans { + all s : State - so/last | + let s' = so/next[s] | + ValidTrans2[s,s'] +} + +pred EquivStates[s, s': State] { + s.buffer = s'.buffer + s.leader = s'.leader +} + +assert Safety { + all s : State | lone s.leader +} + +pred Legit[s : State] { + one s.leader +} + +pred BadLivenessTrace { + all s : State | !Legit[s] + let ls = so/last | + some s : State - ls | { + EquivStates[s, ls] + Process in (so/nexts[s] + s).runs + } +} + +pred TraceWithoutLoop { + all t1, t2 : State | t1!=t2 => !EquivStates[t1,t2] + all s, s' : State | (s' in (so/nexts[s] - so/next[s])) => !PossTrans[s,s'] + all s : State | !Legit[s] +} + +pred AltTrans { + SomeLeader +} + +pred SomeLeader { some State.leader } + +run BadLivenessTrace for 3 but 8 State expect 0 +run SomeLeader for 4 but 6 State expect 1 +check Safety for 7 expect 0 +// run TraceWithoutLoop for 5 but 13 State expect 1 +run AltTrans for 5 but 8 State expect 1 diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/examples/algorithms/stable_mutex_ring.als b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/algorithms/stable_mutex_ring.als new file mode 100644 index 00000000..d4a21f12 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/algorithms/stable_mutex_ring.als @@ -0,0 +1,202 @@ +module examples/algorithms/stable_mutex_ring + +/* + * Dijkstra's K-state mutual exclusion algorithm for a ring + * + * Original paper describing the algorithm: + * [1] E.W.Dijkstra, "Self-Stabilizing Systems in Spite of + * Distributed Control", Comm. ACM, vol. 17, no. 11, pp. + * 643-644, Nov. 1974 + * + * Proof of algorithm's correctness: + * [2] E.W.Dijkstra, "A Belated Proof of Self-Stabilization", + * in Distributed Computing, vol. 1, no. 1, pp. 5-6, 1986 + * + * SMV analysis of this algorithm is described in: + * [3] "Symbolic Model Checking for Self-Stabilizing Algorithms", + * by Tatsuhiro Tsuchiya, Shini'ichi Nagano, Rohayu Bt Paidi, and + * Tohru Kikuno, in IEEE Transactions on Parallel and Distributed + * Systems, vol. 12, no. 1, January 2001 + * + * Description of algorithm (adapted from [3]): + * + * Consider a distributed system that consists of n processes + * connected in the form of a ring. We assume the state-reading + * model in which processes can directly read the state of their + * neighbors. We define _privilege_ of a process as its ability to + * change its current state. This ability is based on a Boolean + * predicate that consists of its current state and the state of + * one of its neighboring processes. + * + * We then define the legitimate states as those in which the + * following two properties hold: 1) exactly one process has a + * privilege, and 2) every process will eventually have a privilege. + * These properties correspond to a form of mutual exclusion, because + * the privileged process can be regarded as the only process that is + * allowed in its critical section. + * + * In the K-state algorithm, the state of each process is in + * {0,1,2,...,K-1}, where K is an integer larger than or equal to n. + * For any process p_i, we use the symbols S and L to denote its + * state and the state of its neighbor p_{i-1}, respectively, and + * process p_0 is treated differently from all other processes. The + * K-state algorithm is described below. + * + * process p_0: if (L=S) { S := (S+1) mod K; } + * process P_i(i=1,...,n-1): if (L!=S) { S:=L; } + */ + +open util/ordering[Tick] as to +open util/graph[Process] as pg +open util/graph[Val] as vg + +sig Process { + rightNeighbor: Process +} + +sig Val { + nextVal : Val +} + +fact MoreValThanProcess { + # Val > # Process +} + +fact DefineRings { + pg/ring[rightNeighbor] + vg/ring[nextVal] + //Val$nextVal = Ord[Val].next + (Ord[Val].last -> Ord[Val].first) +} + +sig Tick { + val: Process -> one Val, + runs: set Process, // processes scheduled to run on this tick + // for visualization + priv: set Process // the set of priviledged processes on this tick +} +{ + priv = { p : Process | Privileged[p, this] } +} + +one sig FirstProc extends Process { +} + + +fun FirstProcTrans[curVal, neighborVal : Val]: Val { + (curVal = neighborVal) => curVal.nextVal else curVal +} + +fun RestProcTrans[curVal, neighborVal : Val]: Val { + (curVal != neighborVal) => neighborVal else curVal +} + +fact LegalTrans { + all tp : Tick - to/last | + let tn = to/next[tp] | { + all p: Process | + let curVal = tp.val[p], neighborVal = tp.val[p.rightNeighbor], newVal = tn.val[p] | { + p !in tp.runs => newVal = curVal else { + p = FirstProc => + newVal = FirstProcTrans[curVal, neighborVal] + else + newVal = RestProcTrans[curVal, neighborVal] + } + } + } +} + +pred TickTrans[tp, tn : Tick] { + all p : Process | + let curVal = tp.val[p], neighborVal = tp.val[p.rightNeighbor], newVal = tn.val[p] | { + p = FirstProc => + newVal = FirstProcTrans[curVal, neighborVal] + else + newVal = RestProcTrans[curVal, neighborVal] + } +} + +/** + * whether this process can enter its critical section + * on this tick + */ +pred Privileged[p : Process, t : Tick] { + p = FirstProc => + t.val[p] = t.val[p.rightNeighbor] + else + t.val[p] != t.val[p.rightNeighbor] +} + +pred IsomorphicStates[val1, val2: Process -> one Val] { + some processMap: Process one -> one Process, + valMap: Val one -> one Val | { + FirstProc.processMap = FirstProc + all p: Process, v: Val | { + p->v in val1 iff (p.processMap) -> (v.valMap) in val2 + } + all v1,v2: Val | v1->v2 in nextVal iff (v1.valMap) -> (v2.valMap) in nextVal + all p1,p2: Process | p1->p2 in rightNeighbor + iff (p1.processMap) -> (p2.processMap) in rightNeighbor + } +} + +/** + * Find a trace that goes into a loop + * containing a bad tick, i.e. a tick + * at which two distinct processes + * try to run their critical sections + * simultaneously. In such a trace the + * algorithm never "stabilizes". + */ +pred BadSafetyTrace { + let lst = to/last | + some t : Tick - lst | { + //IsomorphicStates(ft.val, lst.val) + t.val = lst.val + Process in (to/nexts[t] + t - lst).runs + some badTick : to/nexts[t] + t | + BadTick[badTick] + } +} + +/** + * Two different processes simultaneously + * try to run their critical sections at this tick + */ +pred BadTick[badTick : Tick] { + some p1 , p2 : Process | { + p1!=p2 + Privileged[p1, badTick] + Privileged[p2, badTick] + } +} + +assert Closure { + not BadTick[to/first] => (all t : Tick | not BadTick[t]) +} + +pred TwoPrivileged { + BadTick[to/first] + some p1, p2 : Process, t1, t2 : Tick - to/first | { + p1!=p2 + Privileged[p1,t1] + Privileged[p2,t2] + } +} + +pred TraceWithoutLoop { + all t1, t2 : Tick | t1!=t2 => t1.val != t2.val +} + +pred TraceShorterThanMaxSimpleLoop { + to/first.val = to/last.val + all t : Tick - to/first - to/last | + !(t.val = to/first.val) +} + +run TraceShorterThanMaxSimpleLoop for 7 but 2 Process, 3 Val expect 1 +run TwoPrivileged for 5 but 3 Process, 4 Val expect 1 +check Closure for 5 but 5 Process, 6 Val expect 0 +//run BadSafetyTrace for 16 but 3 Process, 4 Val +//run TraceWithoutLoop for 21 but 4 Process, 5 Val + + diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/examples/algorithms/stable_mutex_ring.thm b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/algorithms/stable_mutex_ring.thm new file mode 100644 index 00000000..5f31f3ac --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/algorithms/stable_mutex_ring.thm @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/examples/algorithms/stable_orient_ring.als b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/algorithms/stable_orient_ring.als new file mode 100644 index 00000000..c6fec3fe --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/algorithms/stable_orient_ring.als @@ -0,0 +1,90 @@ +module examples/algorithms/stable_orient_ring + +/* + * A self-stabilizing algorithm for orienting uniform rings. + * Communication model is the state-reading model. + */ + +open util/boolean as bool +open util/ordering[Tick] as ord +open util/graph[Process] as graph + +sig Process { + rightNeighbor: Process, + AP1, AP2: Process +} + +fun leftNeighbor[p: Process]: Process { + p.~(rightNeighbor) +} + +fact { + all p: Process { + (p.AP1=p.rightNeighbor && p.AP2=leftNeighbor[p]) || + (p.AP2=p.rightNeighbor && p.AP1=leftNeighbor[p]) + } +} + +fact DefineRing { + graph/ring[rightNeighbor] +} + +sig Tick { + runs: set Process, + dir, S, T: Process -> one Bool, + ring_: Process -> Process +} +{ + all p: Process | p.ring_ = (p.dir=True => p.AP1 else p.AP2) +} + +pred Eq3[b1,b2,b3: Bool] { b1 = b2 && b2 = b3 } +pred Eq4[b1,b2,b3,b4: Bool] { Eq3[b1,b2,b3] && b3=b4 } + +fact Transitions { + all tp: Tick - ord/last | let tn = ord/next[tp] | + all p: Process | + let p1 = p.AP1, p2 = p.AP2, pS = tp.S, pT=tp.T, nS=tn.S, nT=tn.T | + let S1p=p1.pS, S2p=p2.pS, + T1p=p1.pT, T2p=p2.pT, + Sp = p.pS, Sn=p.nS, + Tp = p.pT, Tn=p.nT, + dirp = p.(tp.dir), dirn = p.(tn.dir) | { + p !in tp.runs => ( Sn = Sp && Tn = Tp && dirn = dirp ) else ( + S1p = S2p => ( Sn = Not[S1p] && Tn = True && dirn=dirp) else ( + (Eq3[S1p, Sp, Not[S2p]] && + Eq4[Not[T1p],Tp,T2p,True]) => + (Sn = Not[Sp] && Tn = False && dirn = True) + else ( + (Eq3[Not[S1p],Sp,S2p] && Eq4[T1p,Tp,Not[T2p],True]) => + (Sn = Not[Sp] && Tn = False && dirn = False) else ( + ((Eq3[S1p,Sp,Not[S2p]] && T1p=Tp) || + (Eq3[Not[S1p],Sp,S2p] && Tp=T2p)) => + (Tn = Not[Tp] && Sn=Sp && dirn=dirp) else ( + Sn=Sp && Tn=Tp && dirn=dirp + ) + ) + ) + ) + ) + } +} + +pred RingAtTick[t: Tick] { + let rng = t.ring_ | + graph/ring[rng] || graph/ring[~rng] +} + +assert Closure { + // if the ring is properly oriented + all t: Tick - ord/last | + RingAtTick[t] => RingAtTick[ord/next[t]] +} + +pred SomeState { + !graph/ring[ord/first.ring_] + some t: Tick | graph/ring[t.ring_] +} + +run SomeState for 1 but 2 Tick, 2 Bool, 3 Process expect 1 +check Closure for 1 but 2 Tick, 2 Bool, 3 Process expect 1 diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/examples/algorithms/stable_orient_ring.thm b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/algorithms/stable_orient_ring.thm new file mode 100644 index 00000000..0a8cba73 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/algorithms/stable_orient_ring.thm @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/examples/algorithms/stable_ringlead.als b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/algorithms/stable_ringlead.als new file mode 100644 index 00000000..577257f8 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/algorithms/stable_ringlead.als @@ -0,0 +1,180 @@ +module examples/algorithms/stable_ringlead + +/* + * Huang's self-stabilizing leader-election algorithm + * for rings. + */ + +open util/ordering[Process] as po +open util/ordering[Val] as vo +open util/ordering[State] as so +open util/graph[Process] as graph + +sig Process { + rightNeighbor: Process +} + +sig Val { + nextVal : Val +} + +fact { + graph/ring[rightNeighbor] + vo/next + (vo/last -> vo/first) = nextVal + # Val = # Process +} + +sig State { + val : Process -> one Val, + running : set Process + // for visualization + //leader : set Process +} { + //leader = { p : Process | LeaderAtState(p, this) } +} + +fact { + no so/last.running +} + +fun LeadersAtState[t : State] : set Process { + { p : Process | LeaderAtState[p,t] } +} + +pred LeaderAtState[p : Process, t : State] { ValAtState[p,t] = vo/first } + +fun ValAtState[p : Process, t : State] : Val { t.val[p] } + +fun LeftValAtState[p : Process, t : State] : Val { t.val[p.~rightNeighbor] } + +fun RightValAtState[p : Process, t : State] : Val { t.val[p.rightNeighbor] } + +fun XAtState[p : Process, t : State] : Int { + g[LeftValAtState[p,t],ValAtState[p,t]] +} + +fun YAtState[p : Process, t : State] : Int { + g[ValAtState[p,t],RightValAtState[p,t]] +} + +fun g[a, b : Val] : Int { + (a = b) => Int[# Val] else minus[b,a] +} + +fun minus[v1, v2 : Val] : Int { + Int[ (v1 = v2) => 0 + else vo/gt[v1, v2] => (# (vo/nexts[v2] & vo/prevs[v1] + v1)) + else (# (Val - (vo/nexts[v1] & vo/prevs[v2] + v1))) + ] +} + +fun Trans[oldVal : Val, x, y : Int] : Val { + ((int x = int y && int y = # Val) || (int x < int y)) => oldVal.nextVal else oldVal +} + +pred OneAtATimeTrans { + all tp: State - so/last | + let tn = so/next[tp] | + some p : Process | { + tp.running = p + TransHelper[p,tp,tn] + all other : Process - p | + ValAtState[other,tn] = ValAtState[other,tp] + } +} + +pred DDaemonTrans { + all tp: State - so/last | + let tn = so/next[tp] | { + some tp.running + all p : tp.running | TransHelper[p,tp,tn] + all other : Process - tp.running | + ValAtState[other,tn] = ValAtState[other,tp] + } +} + +pred TransHelper[p : Process, tp, tn : State] { + let oldVal = ValAtState[p, tp], + newVal = ValAtState[p, tn], + x = XAtState[p, tp], + y = YAtState[p,tp] | + newVal = Trans[oldVal, x, y] + +} + +pred StateTrans[s, s' : State] { + all p : Process | + TransHelper[p, s, s'] || ValAtState[p,s] = ValAtState[p,s'] +} + + + +pred CBadLivenessTrace { + OneAtATimeTrans + BadLivenessHelper +} + +pred DBadLivenessTrace { + DDaemonTrans + BadLivenessHelper +} + +pred BadLivenessHelper { + let ls = so/last | + some s : State - ls | { + s.val = ls.val + // fair + Process in (so/nexts[s] + s - ls).running + } + all s : State | ! Legit[s] + } + +pred CTraceWithoutLoop { + OneAtATimeTrans + all t, t' : State | t!=t' => t.val != t'.val +} + +pred DTraceWithoutLoop { + DDaemonTrans + all t, t' : State | t!=t' => { + t.val != t'.val + (t' in so/nexts[t] && t' != so/next[t]) => !StateTrans[t,t'] + } + all t : State | !Legit[t] +} + +pred ConvergingRun { + OneAtATimeTrans + !Legit[so/first] + some t : State | Legit[t] +} + +pred OnlyFairLoops { + OneAtATimeTrans + all s, s' : State | + (s' in so/nexts[s] && s'.val = s.val) => + (let loopStates = (so/nexts[s] & so/prevs[s']) + s + s' | Process in loopStates.running) +} + +assert CMustConverge { + OnlyFairLoops => (some s : State | Legit[s]) +} + +pred Legit [s : State] { + one LeadersAtState[s] + all p : Process | { + int XAtState[p,s] < # Val + int YAtState[p,s] < # Val + } + all p, p' : Process | { + int XAtState[p,s] = int XAtState[p',s] + int YAtState[p,s] = int YAtState[p',s] + } +} + +run ConvergingRun for 3 but 4 State expect 1 +run DTraceWithoutLoop for 3 but 4 State expect 1 +run DBadLivenessTrace for 3 but 4 State expect 1 +run CTraceWithoutLoop for 3 but 5 State expect 0 +run CBadLivenessTrace for 4 but 5 State expect 1 +check CMustConverge for 3 but 4 State expect 0 diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/examples/algorithms/stable_ringlead.thm b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/algorithms/stable_ringlead.thm new file mode 100644 index 00000000..d580f3a1 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/algorithms/stable_ringlead.thm @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/examples/case_studies/INSLabel.als b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/case_studies/INSLabel.als new file mode 100644 index 00000000..9f8a2024 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/case_studies/INSLabel.als @@ -0,0 +1,149 @@ +module examples/case_studies/INSLabel + +/* + * Models an Intentional Naming System (INS), a scheme for + * dynamic resource discovery in a dynamic environment. + * + * For a detailed description, see: + * http://sdg.lcs.mit.edu/pubs/2000/INS_ASE00.pdf + * + * author: Sarfraz Khurshid + */ + +sig Record {} + +sig Label {} + +sig Node { + label: Label +} + +sig LabelTree { + root: Node, + nodes: set Node, + children: nodes one -> (nodes - root) +} +{ // connected + nodes = root.*children + some root.children +} + +pred Get[db: DB, r: Record, a:Advertisement] { + root[a] = root[db] + nodes[a] = + nodes[db] & r.~(db.recs).*(~(db.children)) + anodes[a] = + anodes[db] & r.~(db.recs).*(~(db.children)) + vnodes[a] = + vnodes[db] & r.~(db.recs).*(~(db.children)) + all n: a.nodes | + n.~(a.children) = n.~(db.children) +} + +sig Attribute extends Label {} + +sig Value extends Label {} + +one sig Wildcard, Null extends Value {} + +sig AVTree extends LabelTree { + vnodes, anodes: set nodes +} +{ + root in vnodes + root.label = Null + Null !in (vnodes - root).label + anodes.label + anodes.label in Attribute + vnodes.label in Value + all n: nodes | all /* disj */ c,c': n.children | + c.label != c'.label + all a: anodes | a.children in vnodes && some a.children + all v: vnodes | v.children in anodes + no Wildcard.~label.children +} + +one sig Query extends AVTree {} +{ + all a: anodes | one a.children +} + +one sig Advertisement extends AVTree {} +{ + Wildcard !in vnodes.label +} + +one sig DB extends AVTree { + records: set Record, + recs: (vnodes - root) some -> records +} +{ + Wildcard !in vnodes.label + all a: anodes | no a.recs + all v: vnodes { + no v.children => some v.recs + no v.recs & v.^(~children).recs } + all a: anodes | all disj v,v': a.children | + (no v.*children.recs & v'.*children.recs) +} + +one sig State { + conforms: Query -> Advertisement -> Node -> Node, + lookup: DB -> Query -> Node -> Node -> Record +} + +fact ConformsFixPoint { + all q: Query | all a: Advertisement | + all nq: Node | all na: Node | + q.ConformsAux[a,nq,na] <=> + { + nq.label in Wildcard + na.label + all nq': q.children[nq] | some na': a.children[na] | + q.ConformsAux[a,nq',na'] + } +} + +pred Query.ConformsAux[a: Advertisement, nq: Node, na: Node] { + na in State.conforms[this][a][nq] +} + +pred Conforms[q: Query, a:Advertisement] { + q.ConformsAux[a, q.root, a.root] +} + +fact LookupFixPoint { + all db: DB, q: Query, T: Node, n: Node, r: Record | + r in db.LookupAux[q,T,n] <=> // record r is in the result if and only if + { + all na: n.(q.children) | all nv: na.(q.children) | // for all child av-pairs (na,nv) of av-pair n in q + some Ta: T.(db.children) { + Ta.label = na.label // Ta is a child node with attribute na + nv.label = Wildcard => // wildcard matching + r in Ta.^(db.children).(db.recs) else // r is a record of any child of Ta + (some Tv: Ta.(db.children) { // normal matching + Tv.label = nv.label // Tv is a child of Ta with value nv + no nv.(q.children) => // if Tv is a leaf-node + r in Tv.*(db.children).(db.recs) else // r is a record of Tv or of v + r in db.LookupAux[q,Tv,nv] }) } // else r is a record of the recursive call at Tv + } +} + +fun DB.LookupAux[q: Query, vd: Node, vq: Node]: set Record { // helper function for Lookup + State.lookup[this][q][vd][vq] +} + +fun Lookup[db: DB, q: Query]: set Record { // models Lookup-Name algorithm invocation + db.LookupAux[q, db.root, q.root] +} + +assert LookupConforms2 { //soundness and completeness + all q: Query | all db: DB | all r: Record | all a: Advertisement | + Get[db,r,a] => // all n: a.nodes | n.~(db.children) + {r in db.Lookup[q] <=> q.Conforms[a]} +} + +// < 10 sec +check LookupConforms2 for 4 but 1 State, 3 LabelTree, 2 Record expect 0 +// ~ 25 min +//check LookupConforms2 for 6 but 1 State, 3 LabelTree, 2 Record +//check LookupConforms2 for 6 but 1 State, 3 LabelTree, 3 Record +run Lookup for 3 expect 0 diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/examples/case_studies/chord.als b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/case_studies/chord.als new file mode 100644 index 00000000..f7a042f1 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/case_studies/chord.als @@ -0,0 +1,260 @@ +module examples/case_studies/chord + +/* + * Models the chord distributed hash table lookup protocol. + * + * For a detailed description, see: + * http://www.pdos.lcs.mit.edu/papers/chord:sigcomm01/ + */ + +open util/relation as rel + +sig Id {next: Id} +fact {all i: Id | Id in i.*next} + +/** + * true iff i precedes j in the order starting at from + */ +pred less_than [from, i,j: Id] { + let next' = Id<:next - (Id->from) | j in i.^next' // if from=j, returns true if # nodes > 1 + } +pred less_than_eq [from, i,j: Id] { + let next' = Id<:next - (Id->from) | j in i.*next' + } + +sig Node {id: Id} +fact {all m,n: Node | m!=n => m.id != n.id} + + +sig NodeData { + prev, next: Node, + finger: Id -> lone Node, + closest_preceding_finger: Id -> one Node, + find_predecessor: Id -> one Node, + find_successor: Id -> one Node + } + +sig State { + active: set Node, + data: active -> one NodeData + } + +/** + * node n's next node is defined to be the m where n's finger table maps the id + * that follows n.id to m + * next holds the first entry of the finger table + */ +fact {all s: State | all n: s.active | n.(s.data).next = n.(s.data).finger[n.id.next]} + +pred NextCorrect [s: State] { + all n: s.active { + -- no intervening node (ie, close enough) + no n': s.active - n | less_than [n.id, n'.id, n.(s.data).next.id] + -- can reach all other active nodes (ie, far enough) + -- need this because can't rule out case of next being node itself (because of 1-node ring) + -- s.active in n.*(s.data.next) + n.(s.data).next != n || #s.active = 1 + } + } + +pred NextCorrect' [s: State] { +-- next seems to be correct for 1,2,3 nodes + all n: s.active | let nd = (s.data)[n] { + let next' = Id<:next - (Id -> nd.next.id) { + no n' : s.active { n'.id in n.id.^next' } + }} + } + +// valid +assert Same1 {all s: State | NextCorrect[s] => NextCorrect'[s]} +check Same1 for 3 but 1 State expect 0 + +// valid unless active condition removed +assert Same2 {all s: State | s.active = Node => (NextCorrect'[s] => NextCorrect[s])} +check Same2 for 3 but 1 State expect 0 + +-- assert NextInFinger {all s: State | all n: s.active | some n.s.data.finger[n.id.next] } + + +-- says that finger entry maps an id to a node so that there are no intervening nodes +-- between the id and the node +pred FingersCorrect [s: State] { + all nd: s.active.(s.data) | all start:nd.finger.univ | + nd.finger[start] in s.active && + (no n' : s.active | less_than [start, n'.id, nd.finger[start].id]) + } + + +pred FingersCorrect' [s: State] { + all n: s.active | let nd = (s.data)[n] | all start: Node.~(nd.finger) { + nd.finger[start] in s.active && + (let next' = Id<:next - (nd.finger[start].id -> Id) { + no n' : s.active - nd.finger[start] { + n'.id in start.*next' + } + }) + } + } + + +assert SameFC {all s: State | FingersCorrect [s] iff FingersCorrect'[s]} +check SameFC for 3 but 1 State expect 0 + + +pred ShowMeFC { + all s : State | s.active = Node && FingersCorrect[s] +} + +run ShowMeFC for 2 but 1 State expect 1 + +pred ClosestPrecedingFinger[s: State] { + all n: s.active | let nd = n.(s.data) | + all i: Id | let cpf = nd.closest_preceding_finger[i] { + no n': nd.finger[Id] + n - cpf | less_than [cpf.id, n'.id, i] + cpf in nd.finger[Id] + n + cpf.id != i || # s.active = 1 + //less_than (n.id, cpf.id, i) + } + } + + +pred ClosestPrecedingFinger'[s: State] { + all n: s.active | let nd = (s.data)[n] | all i: Id { + let next' = Id<:next - (Id -> i) { + nd.next.id in n.id.^next' => + // nd.closest_preceding_finger[i] = nd.next, + (some n1: nd.finger[Id] { + nd.closest_preceding_finger[i] = n1 + //n1 in nd.finger[Id] + n1.id in n.id.^next' + no n2: nd.finger[Id] | n2.id in n1.id.^next' + }) else + nd.closest_preceding_finger[i] = n + }} + } + + +assert SameCPF {all s: State | FingersCorrect[s] => (ClosestPrecedingFinger [s] iff ClosestPrecedingFinger' [s])} +assert SameCPF1 {all s: State | FingersCorrect[s] => (ClosestPrecedingFinger [s] => ClosestPrecedingFinger' [s])} +assert SameCPF2 { + all s: State | ((s.active = Node && FingersCorrect[s] && ClosestPrecedingFinger' [s]) + => ClosestPrecedingFinger [s]) } + +check SameCPF for 3 but 1 State expect 0 +check SameCPF1 for 2 but 1 State expect 0 +check SameCPF2 for 3 but 1 State expect 0 + + +pred ShowMeCPF { + all s : State | s.active = Node && FingersCorrect[s] && + // not ClosestPrecedingFinger(s) && ClosestPrecedingFinger'(s) + ClosestPrecedingFinger[s] + //all s : State | all nd : s.active.s.data | nd.finger[Id] = Node + # Node = 2 + # State = 1 +} + + +run ShowMeCPF for 2 but 1 State expect 1 + + +pred FindPredecessor[s: State] { + all n: s.active | let nd = n.(s.data) | all i: Id { + nd.find_predecessor[i] = + (less_than_eq [n.id, i, nd.next.id] && (n.id != i || # s.active = 1) + => n + else (nd.closest_preceding_finger[i].(s.data).find_predecessor)[i]) + } + } + + +assert FPisActive { + all s: State | FingersCorrect[s] && ClosestPrecedingFinger[s] && FindPredecessor[s] + => (all n: s.active | all nd: n.(s.data) | nd.find_predecessor[Id] in s.active) } +check FPisActive for 3 but 1 State expect 1 + + +pred FindPredecessor'[s: State] { + all n: s.active | let nd = (s.data)[n] | all i: Id { + let next' = Id<:next - (nd.next.id -> Id) { + one s.active or i in n.id.^next' => // *next' -> ^next' 1/8/02 + nd.find_predecessor[i] = n else + nd.find_predecessor[i] = + ((s.data)[nd.closest_preceding_finger[i]]).find_predecessor[i] + }} + } + + +assert SameFP {all s: State | FingersCorrect[s] // && s.active = Node + => (FindPredecessor [s] iff FindPredecessor' [s])} + +assert SameFP1 { + all s: State | FingersCorrect[s] && s.active = Node + => (FindPredecessor [s] => FindPredecessor' [s])} +assert SameFP2 { + all s: State | FingersCorrect[s] && s.active = Node + => (FindPredecessor' [s] => FindPredecessor [s])} + +check SameFP for 3 but 1 State expect 1 +check SameFP1 for 3 but 1 State expect 0 +check SameFP2 for 3 but 1 State expect 0 + + +pred FindSuccessor[s: State] { + all n: s.active | let nd = (s.data)[n] | all i: Id { + nd.find_successor[i] = ((s.data)[nd.find_predecessor[i]]).next + }} + + +// should be able to check that closest_p_f, etc returns +// only active nodes if FingersCorrect. + + +pred ShowMe1Node { + #Node = 1 + all s : State | NextCorrect[s] + State.active = Node +} + +run ShowMe1Node for 2 but 1 State, 1 Node expect 1 + +pred ShowMe1 { + #Node = 2 + #State = 1 + all s : State | NextCorrect[s] + State.active = Node +} + + +pred ShowMe2 { + #Node = 3 + #State = 1 + all s : State | NextCorrect[s] && FingersCorrect[s] + State.active = Node + //all n: NodeData | one n.finger[Id] +} + + +assert OK1 { + #Node = 3 && + #State = 1 && + (all s : State | NextCorrect[s] && FingersCorrect[s]) && + State.active = Node +} + + +run ShowMe1 for 3 expect 1 +run ShowMe2 for 3 expect 1 + +assert InjectiveIds {all i, j: Id | i!=j => i.next != j.next} +check InjectiveIds for 5 expect 0 + + +assert FindSuccessorWorks { + all s: State, i: Id | + let nd = s.active.(s.data) | + let succ = nd.find_successor [i] | + FingersCorrect [s] // && s.active = Node + => (no n': s.active | less_than [i, n'.id, succ.id]) + } +check FindSuccessorWorks for 3 but 1 State expect 1 diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/examples/case_studies/chord2.als b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/case_studies/chord2.als new file mode 100644 index 00000000..ea7c3429 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/case_studies/chord2.als @@ -0,0 +1,274 @@ +module examples/case_studies/chord2 + +/* + * Models the chord distributed hash table lookup protocol. + * + * For a detailed description, see: + * http://www.pdos.lcs.mit.edu/papers/chord:sigcomm01/ + */ + +open util/relation as rel + +sig Id {next: Id} +fact {all i: Id | Id in i.*next} + +/** + * true iff i precedes j in the order starting at from + */ +pred less_than [from, i,j: Id] { + let next' = Id<:next - (Id->from) | j in i.^next' // if from=j, returns true if # nodes > 1 + } +pred less_than_eq [from, i,j: Id] { + let next' = Id<:next - (Id->from) | j in i.*next' + } + +sig Node {id: Id} +fact {all m,n: Node | m!=n => m.id != n.id} + +sig NodeData { + prev, next: Node, + finger: Id -> lone Node, + closest_preceding_finger: Id -> one Node, + find_predecessor: Id -> one Node, + find_successor: Id -> one Node + } + +sig State { + active: set Node, + data: active -> one NodeData + } + +/** + * node n's next node is defined to be the m where n's finger table maps the id + * that follows n.id to m + * next holds the first entry of the finger table + */ +fact {all s: State | all n: s.active | n.(s.data).next = n.(s.data).finger[n.id.next]} + +pred NextCorrect [s: State] { + all n: s.active { + -- no intervening node (ie, close enough) + no n': s.active - n | less_than [n.id, n'.id, n.(s.data).next.id] + -- can reach all other active nodes (ie, far enough) + -- need this because can't rule out case of next being node itself (because of 1-node ring) + -- s.active in n.*(s.data.next) + n.(s.data).next != n || #s.active = 1 + } + } + +/* +-- abortive attempt at simplifying next condition +fun NextCorrect" (s: State) { + all n: s.active | let nx = s.data.next { + s.active in n.*nx + less_than (n.id, n.id, n.nx.id) + } + } +*/ + +pred NextCorrect' [s: State] { +-- next seems to be correct for 1,2,3 nodes + all n: s.active | let nd = (s.data)[n] { + let next' = Id<:next - (Id -> nd.next.id) { + no n' : s.active { n'.id in n.id.^next' } + }} + } + +assert Same1 {all s: State | NextCorrect[s] => NextCorrect'[s]} +//check Same1 for 3 but 1 State -- valid +assert Same2 {all s: State | s.active = Node => (NextCorrect'[s] => NextCorrect[s])} +//check Same2 for 3 but 1 State -- invalid if active condition removed + +-- assert NextInFinger {all s: State | all n: s.active | some n.s.data.finger[n.id.next] } + +/** + * says that finger entry maps an id to a node so that there are no intervening nodes + * between the id and the node + */ +pred FingersCorrect [s: State] { + all nd: s.active.(s.data) | all start:nd.finger.univ | + nd.finger[start] in s.active && + (no n' : s.active | less_than [start, n'.id, nd.finger[start].id]) + } + +pred FingersCorrect' [s: State] { + all n: s.active | let nd = (s.data)[n] | all start: Node.~(nd.finger) { + nd.finger[start] in s.active && + (let next' = Id<:next - (nd.finger[start].id -> Id) { + no n' : s.active - nd.finger[start] { + n'.id in start.*next' + }})} + } + +assert SameFC {all s: State | FingersCorrect [s] iff FingersCorrect'[s]} +//check SameFC for 3 but 1 State + +pred ShowMeFC { + all s : State | s.active = Node && FingersCorrect[s] +} + +//run ShowMeFC for 2 but 1 State + +/* +fun ClosestPrecedingFinger(s: State) { + all n: s.active | let nd = n.s.data | + all i: Id | let cpf = nd.closest_preceding_finger[i] { + no n': nd.finger[Id] | less_than (cpf.id, n'.id, i) + cpf in nd.finger[Id] + n + less_than (n.id, cpf.id, i) + } + } +*/ + +pred ClosestPrecedingFinger_SAVE [s: State] { + all n: s.active | let nd = n.(s.data) | + all i: Id | let cpf = nd.closest_preceding_finger[i] { + no n': (nd.finger[Id] + n) - cpf | less_than [cpf.id, n'.id, i] + cpf in nd.finger[Id] + n + cpf.id != i || # s.active = 1 + //less_than (n.id, cpf.id, i) + } + } + +pred CPFBody [s: State, n: Node, nd: NodeData, i: Id, cpf: Node] { + no n': (nd.finger[Id] + n) - cpf | less_than [cpf.id, n'.id, i] + cpf in nd.finger[Id] + n + cpf.id != i || # s.active = 1 + } +pred ClosestPrecedingFinger[s: State] { + all n: s.active | let nd = n.(s.data) | + all i: Id | + some cpf: Node | CPFBody [s,n,nd,i,cpf] => CPFBody [s,n,nd,i,nd.closest_preceding_finger[i]] + } + +pred ClosestPrecedingFinger'[s: State] { + all n: s.active | let nd = (s.data)[n] | all i: Id { + let next' = Id<:next - (Id -> i) { + nd.next.id in n.id.^next' => + // nd.closest_preceding_finger[i] = nd.next, + (some n1: nd.finger[Id] { + nd.closest_preceding_finger[i] = n1 + //n1 in nd.finger[Id] + n1.id in n.id.^next' + no n2: nd.finger[Id] | n2.id in n1.id.^next' + }) else + nd.closest_preceding_finger[i] = n + }} + } + +assert SameCPF {all s: State | FingersCorrect[s] => (ClosestPrecedingFinger [s] iff ClosestPrecedingFinger' [s])} +assert SameCPF1 {all s: State | FingersCorrect[s] => (ClosestPrecedingFinger [s] => ClosestPrecedingFinger' [s])} +assert SameCPF2 { + all s: State | ((s.active = Node && FingersCorrect[s] && ClosestPrecedingFinger' [s]) + => ClosestPrecedingFinger [s]) } +//check SameCPF for 3 but 1 State +//check SameCPF1 for 2 but 1 State +//check SameCPF2 for 3 but 1 State + +pred ShowMeCPF { + all s : State | s.active = Node && FingersCorrect[s] && + // not ClosestPrecedingFinger(s) && ClosestPrecedingFinger'(s) + ClosestPrecedingFinger[s] + //all s : State | all nd : s.active.s.data | nd.finger[Id] = Node + # Node = 2 + # State = 1 +} + +//run ShowMeCPF for 2 but 1 State + +pred FindPredecessor[s: State] { + all n: s.active | let nd = n.(s.data) | all i: Id { + nd.find_predecessor[i] = + ((less_than_eq [n.id, i, nd.next.id] && (n.id != i || # s.active = 1)) + => n + else (nd.closest_preceding_finger[i].(s.data).find_predecessor)[i]) + } + } +-- problem : could return node that's inactive ??? + +assert FPisActive { + all s: State | FingersCorrect[s] && ClosestPrecedingFinger[s] && FindPredecessor[s] + => (all n: s.active | all nd: n.(s.data) | nd.find_predecessor[Id] in s.active) +} + +//check FPisActive for 3 but 1 State + +pred FindPredecessor'[s: State] { + all n: s.active | let nd = (s.data)[n] | all i: Id { + let next' = Id<:next - (nd.next.id -> Id) { + one s.active or i in n.id.^next' => + nd.find_predecessor[i] = n else + nd.find_predecessor[i] = + ((s.data)[nd.closest_preceding_finger[i]]).find_predecessor[i] + }} + } + +assert SameFP {all s: State | FingersCorrect[s] && s.active = Node + => (FindPredecessor [s] iff FindPredecessor' [s])} +assert SameFP1 { + all s: State | FingersCorrect[s] && s.active = Node + => (FindPredecessor [s] => FindPredecessor' [s])} +assert SameFP2 { + all s: State | FingersCorrect[s] && s.active = Node + => (FindPredecessor' [s] => FindPredecessor [s])} +//check SameFP for 3 but 1 State +//check SameFP1 for 3 but 1 State +//check SameFP2 for 3 but 1 State + +pred FindSuccessor[s: State] { + all n: s.active | let nd = (s.data)[n] | all i: Id { + nd.find_successor[i] = ((s.data)[nd.find_predecessor[i]]).next + }} + +fact { all s : State { + ClosestPrecedingFinger[s] + FindPredecessor[s] + FindSuccessor[s] + }} + +// should be able to //check that closest_p_f, etc returns +// only active nodes if FingersCorrect. + +pred ShowMe1Node { + #Node = 1 + all s : State | NextCorrect[s] + State.active = Node +} + +//run ShowMe1Node for 2 but 1 State, 1 Node +-- does the expected correct thing for 1 node. + +pred ShowMe1 { + #Node = 2 + #State = 1 + all s : State | NextCorrect[s] + State.active = Node +} + +pred ShowMe2 { + #Node = 3 + #State = 1 + all s : State | NextCorrect[s] && FingersCorrect[s] + State.active = Node + //all n: NodeData | one n.finger[Id] +} + +assert OK1 { + #Node = 3 && + #State = 1 && + (all s : State | NextCorrect[s] && FingersCorrect[s]) && + State.active = Node +} + +assert FindSuccessorWorks { + all s: State, i: Id | + let nd = s.active.(s.data) | + let succ = nd.find_successor [i] | + FingersCorrect [s] + => { + no n': s.active | less_than [i, n'.id, succ.id] + succ in s.active + } + } + +check FindSuccessorWorks for 4 but 1 State, 3 Node, 3 NodeData expect 0 diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/examples/case_studies/chordbugmodel.als b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/case_studies/chordbugmodel.als new file mode 100644 index 00000000..de6c36bb --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/case_studies/chordbugmodel.als @@ -0,0 +1,232 @@ +module examples/case_studies/chord + +/* + * Models the chord distributed hash table lookup protocol. + * + * For a detailed description, see: + * http://www.pdos.lcs.mit.edu/papers/chord:sigcomm01/ + */ + +sig Id {next: Id} +fact {all i: Id | Id in i.*next} + +pred less_than [from, i,j: Id] { + let next' = Id<:next - (Id->from) | j in i.^next' +} + +pred less_than_eq [from, i,j: Id] { + let next' = Id<:next - (Id->from) | j in i.*next' +} + +sig Node {id: Id} +fact {all m,n: Node | m!=n => m.id != n.id} + +sig NodeData { + next: Node, + finger: Id -> lone Node, + closest_preceding_finger: Id -> one Node, + find_successor: Id -> one Node +} + +sig State { + active: set Node, + data: active -> one NodeData +} + +fact { + all s: State | all n: s.active | + n.(s.data).next = n.(s.data).finger[n.id.next] +} + +pred NextCorrect [s: State] { + all n: s.active | let succ = n.(s.data).next { + no n': s.active - n | less_than [n.id, n'.id, succ.id] + succ != n || #s.active = 1 + succ in s.active + } +} + +pred FingersCorrect [s: State] { + all nd: s.active.(s.data) | all start: (nd.finger).Node | + nd.finger[start] in s.active && + (no n' : s.active | less_than [start, n'.id, nd.finger[start].id]) +} + +pred save_ClosestPrecedingFinger [s: State] { + all n: s.active | let nd = n.(s.data) | + all i: Id | let cpf = nd.closest_preceding_finger[i] { + no n': (nd.finger[Id] + n) - cpf | less_than [cpf.id, n'.id, i] + cpf in nd.finger[Id] + n + cpf.id != i || # s.active = 1 + } +} + +pred save_FindSuccessor[s: State] { + all n: s.active | let nd = n.(s.data) | all i: Id { + nd.find_successor[i] = + (((less_than_eq [n.id, i, nd.next.id] && n.id != i) || # s.active = 1) + => nd.next + else + (nd.closest_preceding_finger[i].(s.data).find_successor)[i]) + } +} + +pred IrrelevantFact1 { + all s : State { + ClosestPrecedingFinger[s] + FindSuccessor[s] + } +} + +pred ShowMe1Node { + #Node = 1 + all s : State | NextCorrect[s] && FingersCorrect[s] + State.active = Node +} + +run ShowMe1Node for 2 but 1 State, 1 Node expect 1 + +pred ShowMeGood { + #Id = 4 + all s : State | NextCorrect[s] && FingersCorrect[s] + State.active = Node +} + +run ShowMeGood for 4 but 1 State, 2 Node expect 1 + +pred FindSuccessorIsCorrect[s: State] { + all i: Id | all n: s.active | + let succ = (n.(s.data)).find_successor [i] { + succ in s.active + no n': s.active | less_than [i, n'.id, succ.id] + } +} + +pred ShowMeCorrectSuccessorEg { + #Node = 3 + State.active = Node + all s: State | FingersCorrect[s] && FindSuccessorIsCorrect[s] +} + +run ShowMeCorrectSuccessorEg for 3 but 1 State expect 1 + +pred ShowMe3 { + #Id = 5 + #Node = 3 + #State = 1 + all s : State | NextCorrect[s] && !FingersCorrect[s] + State.active = Node +} + +run ShowMe3 for 5 but 1 State expect 1 + +pred FindSuccessorWorks { + IrrelevantFact1 + ! ( + all s: State | FingersCorrect[s] + => FindSuccessorIsCorrect[s] + ) +} + +assert StrongerFindSuccessorWorks { + all s: State | NextCorrect[s] => FindSuccessorIsCorrect[s] +} + +run FindSuccessorWorks for 4 but 1 State expect 0 +check StrongerFindSuccessorWorks for 4 but 1 State expect 1 + +/* +\section Variations + +In the pseudocode presented in [\cite{chord1}, +\cite{chord2}], there is some ambiguity as to what the +expression \tt<(n, n.successor]> means in boundary cases +where there is exactly one node and \tt. +The intention of the authors is that the set includes +\tt. We consider variations of the alloy model with the +bug where the set \tt<(n, n]> does not include \tt, and +observe how it affects the \tt and +the \tt routines. + +\subsection faulty \tt + +Suppose we change \tt as follows: + +\code +*/ + +pred ClosestPrecedingFinger [s: State] { + all n: s.active | let nd = n.(s.data) | + all i: Id | let cpf = nd.closest_preceding_finger[i] { + no n': (nd.finger[Id] + n) - cpf | less_than [cpf.id, n'.id, i] + cpf in nd.finger[Id] + n + cpf.id != i + } +} + +/* +The only change here is in the last line +\cite{cpf-variation}, where we removed the clause \tt< || # +s.active=1>. The assertion \tt will +still hold for scope up to 4, but \tt will fail +to generate an example! This is an example of a +over-constraint, where the inconsistency only shows up when +there is exactly one node. What happens here is that the +model requires that a closest preceding finger node has a +distinct identifier from the input identifier, but this +cannot happen if there is exactly one node and if the input +identifer equals that of the node. + +\subsection faulty \tt + +Consider the following pseudocode segment from [\cite{chord2}]: + +n.find_successor(id) + if (id in (n, n.successor]) + return n.successor; + else + n' = closest_preceding_finger(id); + return n'.find_successor(id); + +In the buggy scenario with a single node, the \tt loop +always terminates at \cite{if-condition1}, leading to an +infinite loop. + +Consider the corresponding change to \tt as follows: + +*/ + +pred FindSuccessor[s: State] { + all n: s.active | let nd = n.(s.data) | all i: Id { + nd.find_successor[i] = + ((less_than_eq [n.id, i, nd.next.id] && n.id != i) + => nd.next + else + (nd.closest_preceding_finger[i].(s.data).find_successor)[i]) + } +} + +/* +The only change here is in the fourth line +\cite{sf-variation}, where we removed the clause \tt< || # +s.active = 1>. For the same reason, the \tt loop +in this case always proceeds to the \tt clause, +and since \tt always returns +\tt (the only node in the network), we end up +with a tautological statement: + +\code + nd.find_successor[i] = n.s.data.find_successor)[i] + +This means that there is no additional constraint placed on +\tt, other than that its return type is +\tt. Now, if there is no distinction between active +and inactive nodes, that is, we have exactly one active node +in the network and no inactive ones, \tt +will return the right answer due to the type constraint, +therefore obscuring the bug. On the other hand, since we +have introduced inactive nodes, the assertion +\tt now fails with exactly one active +node and some inactive node(s), with \tt +returning an inactive node. +*/ diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/examples/case_studies/com.als b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/case_studies/com.als new file mode 100644 index 00000000..8cf6f08c --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/case_studies/com.als @@ -0,0 +1,96 @@ +module examples/case_studies/com + +/* + * Model of Microsoft Component Object Model (COM) query + * interface and aggregation mechanism. + * + * For a detailed description, see: + * http://sdg.lcs.mit.edu/~dnj/publications/com-fse00.pdf + * + * author: Daniel Jackson + */ + +open util/relation as rel + +sig IID {} + +sig Interface { + qi : IID -> lone Interface, + iids : set IID, + // next two lines should use domain() or range() functions + iidsKnown : IID, + reaches : Interface +}{ + iidsKnown = dom[qi] + reaches = ran[qi] +} + +sig Component { + interfaces : set Interface, + iids : set IID, // can't do iids = interfaces.Interface$iids + first, identity : interfaces, + eqs: set Component, + aggregates : set Component +} + +fact defineEqs { + all c1, c2: Component | + c1->c2 in eqs <=> c1.identity = c2.identity +} + +fact IdentityAxiom { + some unknown : IID | all c : Component | + all i : c.interfaces | unknown.(i.qi) = c.identity +} + +fact ComponentProps { + all c : Component { + c.iids = c.interfaces.iids + all i : c.interfaces | all x : IID | x.(i.qi) in c.interfaces + } +} + +sig LegalInterface extends Interface { } +fact { all i : LegalInterface | all x : i.iidsKnown | x in x.(i.qi).iids} + +sig LegalComponent extends Component { } +fact { LegalComponent.interfaces in LegalInterface } + +fact Reflexivity { all i : LegalInterface | i.iids in i.iidsKnown } +fact Symmetry { all i, j : LegalInterface | j in i.reaches => i.iids in j.iidsKnown } +fact Transitivity { all i, j : LegalInterface | j in i.reaches => j.iidsKnown in i.iidsKnown } + +fact Aggregation { + no c : Component | c in c.^aggregates + all outer : Component | all inner : outer.aggregates | + (some inner.interfaces & outer.interfaces) + && (some o: outer.interfaces | all i: inner.interfaces - inner.first | all x: Component | (x.iids).(i.qi) = (x.iids).(o.qi)) + } + +assert Theorem1 { + all c: LegalComponent | all i: c.interfaces | i.iidsKnown = c.iids + } + +assert Theorem2 { + all outer: Component | all inner : outer.aggregates | + inner in LegalComponent => inner.iids in outer.iids + } + +assert Theorem3 { + all outer: Component | all inner : outer.aggregates | inner in outer.eqs + } + +assert Theorem4a { + all c1: Component, c2: LegalComponent | + some (c1.interfaces & c2.interfaces) => c2.iids in c1.iids + } + +assert Theorem4b { + all c1, c2: Component | some (c1.interfaces & c2.interfaces) => c1 in c2.eqs + } + +check Theorem1 for 3 expect 0 +check Theorem2 for 3 expect 0 +check Theorem3 for 3 expect 0 +check Theorem4a for 3 expect 0 +check Theorem4b for 3 expect 0 diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/examples/case_studies/firewire.als b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/case_studies/firewire.als new file mode 100644 index 00000000..5de1afaf --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/case_studies/firewire.als @@ -0,0 +1,384 @@ +module examples/case_studies/firewire + +/* + * A model of leader election in the Firewire protocol. + * + * Adapted from: + * [DG+01] M.C.A. Devillers, W.O.D. GriAEoen, J.M.T Romijn, and F.W. Vaandrager. + * Verification of a leader election protocol -- formal methods applied to IEEE + * 1394. Technical Report CSI-R9728, Computing Science Institute, University of + * Nijmegen, December 1997. Also, Formal Methods in System Design, 2001. + * + * This model describes a leader election protocol used in Firewire, an IEEE + * standard for connecting consumer electronic devices. The model is a + * straightforward translation into Alloy of a model [DG+01] developed in Lynch's + * IO Automata which has been analyzed using PVS, a theorem prover, but which, as + * far as we know, has not been subjected to fully automatic analysis. We are able + * to express the key correctness property -- that exactly one leader is elected + * -- more directly, as a trace property rather than a refinement property, and + * can check it without the need for the 15 invariants used in the more + * traditional proof. And the analysis does not hardwire + * a particular topology, so would be tricky to do with a standard model checker. + * + * The network is assumed to consist of a collection of nodes connected by + * links. Each link between a pair of nodes is matched by a link in the other + * direction. Viewing a link and its dual as a single, undirected edge, the + * network as a whole is assumed to form a tree. The purpose of the algorithm is + * to construct such a tree; in the model, this is achieved by labelling some + * subset of the links as parent links (each pointing from a node to its parent), + * and by marking a single node as the root. + * + * The algorithm, described in detail elsewhere [DG+01], works briefly as + * follows. When a node detects that all of its incoming links (or all but one) + * has been marked as a parent link, it sends a message on each outgoing link, + * either an acknowledgment (indicating its willingness to act as parent), or a + * request (indicating its desire to be a child), according to whether the dual of + * the outgoing link has been marked or not. Leaf nodes (with only one incoming + * link) may thus initiate the algorithm by sending requests to their adjacent + * nodes. Performing this action changes a node's status from {\em waiting} to + * {\em active}. A node that is still waiting, and which receives a message on a + * link, may label that link a parent link. Once active, a node that receives an + * acknowledgment on a link may also label the link, but if it receives a request, + * instead changes its node status to {\em contending}. The resolving of + * contentions is modelled simplistically by a single action that arbitrarily + * labels one of the two links a pair of contending nodes. Finally, a node all of + * whose incoming links are parent links designates itself a root. + * + * The specification is given below. Each signature introduces a basic type + * and some relations whose first column has that type: + * + * \begin{itemize} + * + * \item {\em Msg} represents the type of messages. {\em Req} is the request + * message and {\em Ack} is the acknowledgment message; these are actually + * declared as singleton (keyword {\em static}) subsets of {\em Msg}, the set of + * all messages, that form a partition (keyword {\em part}). + * + * \item {\em Node} represents the nodes of the network. The relations {\em to} + * and {\em from} associate each node with a set of incoming and outgoing links + * respectively. + * + * \item {\em Link} represents the links. The relations {\em target} and {\em + * source} map a link to its end nodes; {\em reverse} maps a link to its dual. The + * facts in the signatures {\em Node} and {\em Link} ensure that all these + * relations are consistent with one another: that the incoming links of a node + * are those whose target is that node, etc. + * + * \item {\em Op} introduces a set of names for the operations of the + * protocol. This is merely for convenience; it allows us to ask for an execution + * in which named operations occur, or example. + * + * \item {\em State} represents the global states. Each state has a partition of + * the nodes into one of four statuses: {\em waiting} to participate, {\em active} + * (having sent messages on outgoing links), {\em contending} (having sent a + * request on a link and received a request on its dual), and {\em elected} + * (having designated itself as a root). A set of links are labelled as parent + * links. There is a message queue associated with each link. Finally, the state + * is associated with the operation that produced it. + * + * \item {\em Queue} represents the message queues. Each queue has a slot that + * optionally contains a message; the relation {\em slot} is a partial function + * from queues to messages. In our first attempt at a model, we represented a + * queue as a sequence (a partial function from a prefix of the integers to + * messages), but having checked that no queue ever contained more than one + * message, we simplified the model. The {\em overflow} field is included just in + * case this was a mistake; a write to a queue that already contains a message + * puts an arbitrary value there, which is easily detected. + * + * \end{itemize} + * + * The {\em facts} record the assumptions about the topology. The one named {\em + * Topology} says that there is some partial function on nodes and some root such + * that (1) every node is reachable from the root ({\tt *r} being the reflexive + * transitive closure of the relation {\tt r}); (2) there are no cycles (expressed + * by saying that the transitive closure has no intersection with the identity + * relation on nodes); and (3) the relation obtained by following the {\em source} + * relation backwards (from a node to the link for which it is a source), and then + * the {\em target} relation forwards (from the link to its target) is this + * relation, plus its transpose (so that each tree edge becomes two + * links). Although the quantifier appears to be higher-order, it will be + * skolemized away by the analyzer. + * + * The {\em functions} of the model are parameterized formulas. The function {\em + * Trans} relates a pre-state {\tt s} to a post-state {\tt s'}. It has a case for + * each operation. Look at the clause for the operation {\em WriteReqOrAck}, for + * example. If this operation is deemed to have occurred, each of the constraints + * in the curly braces must hold. The first says that the labelling of links as + * parent links is unchanged. The second constraint (the quantified formula) + * constrains with respect to the node at which the operation occurs. The + * subformulas, from first to last, say that the node belongs to the waiting set + * before and the active set afterwards; that there is at most one ({\em sole}) + * link that is incoming but not a parent link in the pre-state; that there are no + * changes to node status except at this node; that a message is queued onto each + * outgoing link; and that queues on all other links are unchanged. + * + * An 'invoked' function is simply short for the formula in its body with the + * formal arguments replaced by the actual expressions. {\em WriteQueue}, for + * example, says that if the queue's slot is not filled in the pre-state, then the + * new queue in the post-state (given the local name {\tt q}) contains the message + * {\tt m} in its slot, and has no message in its overflow. Otherwise, some + * message is placed arbitrarily in the overflow, and the slot is + * unconstrained. In {\em WriteReqOrAck}, the arguments {\tt s} and {\tt s'} are + * bound to the {\tt s} and {\tt s'} of {\em Trans}; {\tt x} is bound to one of + * the outgoing links from the set {\tt n.from}; and {\tt msg} is bound either to + * the acknowledgment or request message. + * + * The function {\em Execution} constrains the set of states. It makes use of a + * library module that defines a polymorphic ordering relation. The expression + * {\tt Ord[State]} gives an ordering on all states. The two formulas of the + * function say that {\tt Initialization} holds in the first state, and that any + * pair of adjacent states is related by {\tt Trans}. The function {\em NoRepeats} + * adds the constraints that there are no equivalent states in the trace, and that + * no stuttering occurs. + * + * The three assertions are theorems for which the analyzer will search for + * counterexamples. They assert respectively that: in every state of the trace, + * there is at most one node that has been elected; that there is some state in + * which a node has been elected; and that no queue overflows. + * + * The rest of the model is a collection of commands executed to find instances of + * the functions or counterexamples to the theorems. We started by presenting a + * variety of functions as a sanity check; here, only one is given, that asks for + * an execution involving 2 nodes, 4 links, 4 queues and a trace of 6 states. The + * standard semantics of these {\em scope} declarations in Alloy is that the + * numbers represent an upper bound, so an instance may involve fewer than 4 + * queues, for example. The ordering module (not shown here), however, for + * technical reasons, constrains the ordered set to match its scope, so a trace + * with fewer than 6 states will not be acceptable. + * + * We then established some bounds on the diameter of the state machine for + * various topology bounds. For 2 nodes and 2 links, for example, there are no + * non-repeating traces of length 4; checking traces of length 3 is thus + * sufficient in this case. The number of queues was limited to 5, to accommodate + * the empty queue, a queue containing an {\tt Ack} or {\tt Req}, and each of + * these with overflow. For 3 nodes and 6 links, a trace length of 8 suffices. + * + * We then checked that for these various topology bounds, the queues never + * overflow. Finally, we checked the correctness properties, taken advantage of + * the earlier results that justify the short traces and queues. We are thus able + * to verify the properties for all topologies involving the given number of nodes + * and links, without any assumptions about trace length, queue size or the + * particular topological structure. + * + * author: Daniel Jackson + * visualization: Robert Seater + */ + +open util/ordering[State] as ord + +abstract sig Msg {} +one sig Req, Ack extends Msg {} + +sig Node {to, from: set Link} { + to = {x: Link | x.target = this} + from = {x: Link | x.source = this} + } + +sig Link {target, source: Node, reverse: Link} { + reverse.@source = target + reverse.@target = source + } + +/** + * at most one link between a pair of nodes in a given direction + */ +fact {no x,y: Link | x!=y && x.source = y.source && x.target = y.target} + +/** + * topology is tree-like: acyclic when viewed as an undirected graph + */ +fact Topology { +some tree: Node lone -> Node, root: Node { + Node in root.*tree + no ^tree & iden & Node->Node + tree + ~tree = ~source.target + } +} + +sig Op {} +one sig Init, AssignParent, ReadReqOrAck, Elect, WriteReqOrAck, +ResolveContention, Stutter extends Op {} + +sig State { + disj waiting, active, contending, elected: set Node, + parentLinks: set Link, + queue: Link -> one Queue, + op: Op -- the operation that produced the state + } { + waiting + active + contending + elected = Node +} + +pred SameState [s, s': State] { + s.waiting = s'.waiting + s.active = s'.active + s.contending = s'.contending + s.elected = s'.elected + s.parentLinks = s'.parentLinks + all x: Link | SameQueue [s.queue[x], s'.queue[x]] + } + +pred Trans [s, s': State] { + s'.op != Init + s'.op = Stutter => SameState [s, s'] + s'.op = AssignParent => { + some x: Link { + x.target in s.waiting & s'.waiting + NoChangeExceptAt [s, s', x.target] + ! IsEmptyQueue [s, x] + s'.parentLinks = s.parentLinks + x + ReadQueue [s, s', x] + }} + s'.op = ReadReqOrAck => { + s'.parentLinks = s.parentLinks + some x: Link { + x.target in s.(active + contending) & (PeekQueue [s, x, Ack] => s'.contending else s'.active) + NoChangeExceptAt [s, s', x.target] + ! IsEmptyQueue [s, x] + ReadQueue [s', s, x] + }} + s'.op = Elect => { + s'.parentLinks = s.parentLinks + some n: Node { + n in s.active & s'.elected + NoChangeExceptAt [s, s', n] + n.to in s.parentLinks + QueuesUnchanged [s, s', Link] + }} + s'.op = WriteReqOrAck => { + -- note how this requires access to child ptr + s'.parentLinks = s.parentLinks + some n: Node { + n in s.waiting & s'.active + lone n.to - s.parentLinks + NoChangeExceptAt [s, s', n] + all x: n.from | + let msg = (x.reverse in s.parentLinks => Ack else Req) | + WriteQueue [s, s', x, msg] + QueuesUnchanged [s, s', Link - n.from] + }} + s'.op = ResolveContention => { + some x: Link { + let contenders = x.(source + target) { + contenders in s.contending & s'.active + NoChangeExceptAt [s, s', contenders] + } + s'.parentLinks = s.parentLinks + x + } + QueuesUnchanged [s, s', Link] + } +} + +pred NoChangeExceptAt [s, s': State, nodes: set Node] { + let ns = Node - nodes { + ns & s.waiting = ns & s'.waiting + ns & s.active = ns & s'.active + ns & s.contending = ns & s'.contending + ns & s.elected = ns & s'.elected + }} + +sig Queue {slot: lone Msg, overflow: lone Msg} + +pred SameQueue [q, q': Queue] { + q.slot = q'.slot && q.overflow = q'.overflow + } + +pred ReadQueue [s, s': State, x: Link] { +-- let q = s'.queue[x] | no q.(slot + overflow) + no s'.queue[x].(slot + overflow) + all x': Link - x | s'.queue[x'] = s.queue[x'] + } + +pred PeekQueue [s: State, x: Link, m: Msg] { + m = s.queue[x].slot + } + +pred WriteQueue [s, s': State, x: Link, m: Msg] { + let q = s'.queue[x] | + no s.queue[x].slot => + ( q.slot = m && no q.overflow) else + some q.overflow + } + +pred QueuesUnchanged [s, s': State, xs: set Link] { + all x: xs | s'.queue[x] = s.queue[x] + } + +pred IsEmptyQueue [s: State, x: Link] { + no s.queue[x].(slot + overflow) +-- let q = s.queue[x] | no q.(slot + overflow) + } + +pred Initialization [s: State] { + s.op = Init + Node in s.waiting + no s.parentLinks + all x: Link | IsEmptyQueue [s, x] + } + +pred Execution { + Initialization [ord/first] + all s: State - ord/last | let s' = ord/next[s] | Trans [s, s'] + } + +pred ElectionHappens { + Execution + some s: State | some s.elected + some s: State | no s.elected +} + +pred NoRepeats { + Execution + no s, s': State | s!=s' && SameState [s, s'] + no s: State | s.op = Stutter + } + +pred NoShortCuts { + all s: State | -- remove this to speed up analysis - Ord[State].last - OrdPrev (Ord[State].last) | + ! Trans [s, ord/next[ord/next[s]]] + } + +assert AtMostOneElected { + Execution => (all s: State | lone s.elected) + } + +assert OneEventuallyElected { + Execution => (some s: State | some s.elected) + } + +assert NoOverflow { + Execution => (all s: State, x: Link | no s.queue[x].overflow) + } + +run Execution for 7 Op, 2 Msg, + 2 Node, 4 Link, 4 Queue, 6 State expect 1 + +run ElectionHappens for 7 Op, 2 Msg, + exactly 3 Node, 6 Link, 3 Queue, 7 State expect 1 + +-- solution for 3 State but not for 4 State +run NoRepeats for 7 Op, 2 Msg, + 2 Node, 2 Link, 2 Queue, 4 State expect 0 + +-- solution for 8 but not 9 State +run NoRepeats for 7 Op, 2 Msg, + 3 Node, 6 Link, 6 Queue, 8 State expect 0 + +-- only 5 queues needed: just count +-- no solution: establishes at most 3 queues needed +check NoOverflow for 7 Op, 2 Msg, + 3 Node, 6 Link, 5 Queue, 9 State expect 0 + +check AtMostOneElected for 7 Op, 2 Msg, + 3 Node, 6 Link, 3 Queue, 9 State expect 0 + +check OneEventuallyElected for 7 Op, 2 Msg, + 3 Node, 6 Link, 3 Queue, 9 State expect 1 + + + +// DEFINED VARIABLES +// Defined variables are uncalled, no-argument functions. +// They are helpful for getting good visualization. +fun queued: State -> Link -> Msg { + {s: State, L: Link, m: Msg | m in L.(s.queue).slot} +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/examples/case_studies/firewire.thm b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/case_studies/firewire.thm new file mode 100644 index 00000000..04793a40 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/case_studies/firewire.thm @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/examples/case_studies/ins.als b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/case_studies/ins.als new file mode 100644 index 00000000..e5596229 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/case_studies/ins.als @@ -0,0 +1,115 @@ +module examples/case_studies/ins + +/* + * Models an Intentional Naming System (INS), a scheme for + * dynamic resource discovery in a dynamic environment. + * + * For a detailed description, see: + * http://sdg.lcs.mit.edu/pubs/2000/INS_ASE00.pdf + * + * author: Sarfraz Khurshid + */ + +open util/relation as rel + +sig Attribute {} +sig Value {} +sig Record {} + +one sig Wildcard extends Value {} + +sig AVTree { + values: set Value, + attributes: set Attribute, + root: values - Wildcard, + av: attributes one -> some (values - root), + va: (values - Wildcard) one -> attributes +}{ + // values (and attributes) of tree are those reachable from root + values = root.*(va.av) +} + +sig Query extends AVTree {} {all a:attributes | one a.av} + +sig DB extends AVTree { + records : set Record, + recs: (values - root) some -> records, + lookup : Query -> (values -> records) +}{ + Wildcard !in values +} + +fact AddInvariants { + all db: DB { + all v: db.values | no v.(db.recs) & v.^(~(db.av).~(db.va)).(db.recs) + all a: db.attributes | all disj v1, v2: a.(db.av) | + (some rr: *((db.va).(db.av)).(db.recs) | no v1.rr & v2.rr) + } +} + +pred Get [db: DB, r: Record, q: Query] { + q.values = r.~(db.recs).*(~(db.av).~(db.va)) + q.attributes = q.values.~(db.av) + q.root = db.root + all a : attributes| a.~(q.va) = a.~(db.va) + all v : values | v.~(q.av) = v.~(db.av) +} + +pred Conforms [db: DB, q: Query, r: Record] { + some p: Query { + db.Get[r, p] + q.va in p.va + (q.av - Attribute -> Wildcard) in p.av + } +} + +pred indSubset[db : DB, q: Query, r: set Record, v: Value] { + all a : v.(q.va) | + (a.(q.av) in a.(db.av) => r in (a.(q.av)).(q.(db.lookup))) && + (a.(q.av) = Wildcard => r in a.(db.av).*((db.va).(db.av)).(db.recs)) +} + +pred Lookup[db: DB, q: Query, found: set Record] { + all v: Value | not v.(q.va) in v.(db.va) => no v.(q.(db.lookup)) + all v: Value | all a : v.(q.va) | + a.(q.av) != Wildcard && not a.(q.av) in a.(db.av) => no v.(q.(db.lookup)) + all v: Value - Wildcard | + no v.(q.va) => v.(q.(db.lookup)) = v.*((db.va).(db.av)).(db.recs) + all v: Value | + some v.(q.va) => indSubset[db, q, v.(q.(db.lookup)), v] && + (no r: Record - v.(q.(db.lookup)) | indSubset[db, q, v.(q.(db.lookup)) + r, v]) + found = db.root.(q.(db.lookup)) +} + +assert CorrectLookup { + all db: DB | all q : Query | all r : Record | + Conforms [db,q,r] <=> db.Lookup[q, r] +} + +pred Add [me: DB, adv: Query, r: Record, db: DB] { + // restricted version - only advertisements with fresh attributes and values added + no me.attributes & adv.attributes + me.values & adv.values = me.root + me.root = adv.root + Wildcard !in adv.values + r !in me.records + db.values = me.values + adv.values + db.attributes = me.attributes + adv.attributes + db.root = me.root + db.av = me.av + adv.av + db.va = me.va + adv.va + db.recs = me.recs + ((db.values - dom[db.va]) -> r) +} + +pred RemoveWildCard[me: Query, q: Query] { + q.values = me.values - Wildcard + q.attributes = me.attributes - Wildcard.~(me.av) + q.root = me.root + q.av = me.av - Attribute -> Wildcard + q.va = me.va - Value -> Wildcard.~(me.av) +} + +assert MissingAttributeAsWildcard { + all db : DB, q, q' : Query, found: set Record | + db.Lookup[q, found] && q.RemoveWildCard[q'] => db.Lookup[q', found] +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/examples/case_studies/iolus.als b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/case_studies/iolus.als new file mode 100644 index 00000000..cc27ba39 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/case_studies/iolus.als @@ -0,0 +1,320 @@ +module examples/case_studies/iolus + +/* + * This is a model of Iolus, a scheme for secure multicasting. + * In this scheme, nodes multicast messages to other nodes + * within a group whose membership changes dynamically. The + * group is partitioned into subgroups, arranged in a tree, + * each with its own Key Distribution Server (KDS). + * + * For a detailed description, see: + * Mana Taghdiri, "Lightweight Modelling and Automatic Analysis + * of Multicast Key Management Schemes", Masters Thesis, Dept. + * of EECS, MIT, Dec. 2002. + * http://sdg.lcs.mit.edu/pubs/theses/taghdiri_masters.pdf + * + * author: Mana Taghdiri + */ + +open util/ordering[Tick] as ord + +sig Tick {} + +/** + * It can be abstract, since the fact below says Key=GroupKey + */ +abstract sig Key {} + +/** + * It can be abstract, since the fact below says Message=DataMessage + */ +abstract sig Message { + sender : Member, + sentTime : Tick, + key : Key +} + +/** + * It can be abstract, since the fact below says KDS=GSA + */ +abstract sig KDS { + keys : Tick -> Key, + members : Tick -> Member +}{ + Monotonic[keys] + all t : Tick | let t' = ord/prev[t] { + all m : members[t]-members[t'] | Join[m, t, this] + all m : members[t']-members[t] | Leave[m, t] + } +} + +/** + * It can be abstract, since the fact below says "Member=Client" + */ +abstract sig Member { + ownedKeys : Tick -> Key, + receivedMessages : Tick -> Message +}{ + Monotonic[ownedKeys] + Monotonic[receivedMessages] +} + +fact MemberBehavior { + Init[ord/first] + all m : Member, t : Tick - ord/first | + (some msg : Message | + SendMessage[m, t, msg] || ReceiveMessage[m, t, msg]) || + (some kds : KDS | Join[m, t, kds]) || + Leave[m, t] || MemberInactive[m, t] +} + +pred Monotonic[r : Tick -> univ] { + all t : Tick | ord/prev[t].r in t.r +} + +---------------------------------------------- +sig GroupKey extends Key { + generator : GSA, + generatedTime : Tick +}{ + some c : Client | + (Join[c, generatedTime, c.server] || Leave[c, generatedTime]) && + c.server = generator +} + +sig DataMessage extends Message { + gsaID : GSA, + retransmitTime : Tick } +{ SendMessage[sender, sentTime, this] || + (some msg' : DataMessage | + Remulticast[gsaID, msg', retransmitTime, this]) } + +sig GSA extends KDS { + parent : lone GSA } +{ keys[Tick].generator = this + all t : Tick, k : keys[t] - keys[ord/prev[t]] | + k.generatedTime = t } + +sig Client extends Member { + server : GSA } +{ all t : Tick, k : ownedKeys[t] - ownedKeys[ord/prev[t]] | + k.generator = server && k.generatedTime = t } + +fact IolusProperties { + no k, k' : GroupKey | k!=k' && k.generator = k'.generator && k.generatedTime = k'.generatedTime + all g : GSA, msg : DataMessage, t : Tick | RemulticastConditions[g, msg, t] => + (some msg': DataMessage | Remulticast[g, msg, t, msg']) +} + +fact GSATree { + let root = {g : GSA | no g.parent} { + one root + GSA in root.*~parent }} + +fact { + Member = Client + KDS = GSA + Message = DataMessage + Key = GroupKey + no m, m' : DataMessage { + m!=m' + m.sender = m'.sender + m.sentTime = m'.sentTime + m.key = m'.key + m.gsaID = m'.gsaID + m.retransmitTime = m'.retransmitTime } +} + +---------------------------------------------- +pred Init[t : Tick] { + no Member.receivedMessages[t] + no Member.ownedKeys[t] + no KDS.keys[t] + no KDS.members[t] } + +pred Join[m : Member, t : Tick, kds : KDS] { + kds = m.server + JoinRequest[m, kds, t] + NoChange[m.receivedMessages, t] +} +pred JoinRequest[c : Client, gsa : GSA, t : Tick] { + c !in gsa.members[ord/prev[t]] + KeyUpdate[gsa, t] + c in gsa.members[t] } + +pred Leave[m : Member, t : Tick] { + LeaveRequest[m, m.server, t] + NoChange[m.receivedMessages, t] } + +pred LeaveRequest[c : Client, gsa : GSA, t : Tick] { + c in gsa.members[ord/prev[t]] + KeyUpdate[gsa, t] + c !in gsa.members[t] } + +pred SendMessage[m : Member, t : Tick, msg : Message] { + SendRequest[m, m.server, t, msg] + m.receivedMessages[t] = m.receivedMessages[ord/prev[t]] + msg + ConstantMembership[m, t] } + +pred SendRequest[c : Client, gsa : GSA, t : Tick, msg : DataMessage] { + c in gsa.members[t] + msg.sender = c + msg.sentTime = t + NewestKey[gsa.keys[t], msg.key] + msg.gsaID = gsa + msg.retransmitTime = t + (some gsa.parent.members[t]) => + (some msg' : DataMessage | Remulticast[gsa, msg, t, msg']) } + +pred ReceiveMessage[m : Member, t : Tick, msg : Message] { + ReceiveConditions[m, t, msg] + m.receivedMessages[t] = m.receivedMessages[ord/prev[t]] + msg } + +pred MemberInactive[m : Member, t : Tick] { + NoChange[m.receivedMessages, t] --does not constrain owned keys + ConstantMembership[m, t] } + +pred ReceiveConditions[m : Member, t : Tick, msg : Message] { + ConstantMembership[m, t] + msg !in m.receivedMessages[ord/prev[t]] + msg.retransmitTime in ord/prevs[t] + msg.key in m.ownedKeys[t] } + +pred CanReceive[m : Member, t : Tick, msg : Message] { + some msg' : DataMessage { + msg'.sentTime = msg.sentTime + msg'.sender = msg.sender + msg' in m.receivedMessages[ord/prev[t]] || ReceiveConditions[m, t, msg'] }} + +pred IsMember[m : Member, t : Tick] { + some kds : KDS | m in kds.members[t] +} + +------------------------------------------- +pred RemulticastConditions[g : GSA, msg : DataMessage, t : Tick] { + msg.retransmitTime in ord/prevs[t] + msg.key in g.keys[t] + g.parent.keys[t] + some g.parent + g - msg.gsaID } + +pred Remulticast[g : GSA, msg : DataMessage, t : Tick, msg': lone DataMessage] { + RemulticastConditions[g, msg, t] + let g' = g.parent + g - msg.gsaID | NewestKey[g'.keys[msg.sentTime], msg'.key] + msg'.sender = msg.sender + msg'.sentTime = msg.sentTime + msg'.retransmitTime = t + msg'.gsaID = g +} + +pred KeyUpdate[g : GSA, t : Tick] { + some k : Key { + GeneratedKey[g, t, k] + all c : Client | c in g.members[t] <=> k in c.ownedKeys[t] + k in g.keys[t] }} + +pred NewestKey[keys : set GroupKey, newest: lone GroupKey] { + some keys <=> some newest + newest in keys + no ord/nexts[newest.generatedTime] & keys.generatedTime } + +pred GeneratedKey[g : GSA, t : Tick, key : GroupKey] { + key.generator = g + key.generatedTime = t +} + +pred ConstantMembership[c : Client, t : Tick] { + IsMember[c, t] <=> IsMember[c, ord/prev[t]] } + + +pred NoChange[r : Tick -> univ, t : Tick] { + r[ord/prev[t]] = r[t] +} + +-------------------------------------------- +assert Acyclic { + all g : GSA | g !in g.^parent } + +//check Acyclic for 6 -- one min + +assert Connected { + all g, g' : GSA | g in g'.*(parent + ~parent) } + +//check Connected for 6 + +assert TimeProceeds { + no msg : DataMessage | msg.retransmitTime in ord/prevs[msg.sentTime] } + +//check TimeProceeds for 6 + +pred LoopFree { + no t, t' : Tick { + t!=t' + all k : KDS | k.members[t] = k.members[t'] -- no constraint on keys + all m : Member | m.receivedMessages[t] = m.receivedMessages[t'] + all m : DataMessage | m.retransmitTime = t => + (some m' : DataMessage { + m'.retransmitTime = t' + m.sender = m'.sender + m.sentTime = m'.sentTime + m.gsaID = m'.gsaID + m.key = m'.key }) + }} + +//fact NoLoop { LoopFree() } -- Property-specific diameter + +------------------------------------------------ +assert loop { + !LoopFree } +//check loop for 13 but 2 Member, 1 KDS, 1 Message + +assert NonLinearTopology { + (no g : GSA | #g.~parent > 1) || + !(some m, m' : DataMessage | Remulticast[m.gsaID, m', m.retransmitTime, m] + && !SendMessage[m.sender, m.sentTime, m] + && (some c : Client | m in c.receivedMessages[ord/nexts[m.retransmitTime]]))} + +//check NonLinearTopology for 5 but 3 KDS, 3 Member, 2 Message -- > good scenario + +assert NotOutside { + no msg : DataMessage | !IsMember[msg.sender, msg.sentTime] } + +//check NotOutside for 5 + +assert Trivial { + 0 = 1 } + +//check Trivial for 2 but 1 KDS + +assert x { + !(LoopFree && some DataMessage && + (some t : Tick | some m, m' : Member | + m!=m' && IsMember[m, t] && IsMember[m', t] && t != ord/next[ord/first])) +} + +//check x for 3 but 2 Member, 2 KDS, 2 Message + ------------------------------------------- +assert OutsiderCantRead { + no msg : Message, m : Member, t : Tick { + IsMember[msg.sender, msg.sentTime] + !IsMember[m, msg.sentTime] + CanReceive[m, t, msg] + } +} +assert OutsiderCantSend { + no msg : Message, m : Member, t : Tick { + !IsMember[msg.sender, msg.sentTime] + IsMember[m, t] + msg !in m.receivedMessages[ord/prev[t]] + CanReceive[m, t, msg] + } +} +assert InsiderCanRead { + all msg : Message, m : Member | + some t : Tick - ord/last | all t' : ord/nexts[t] | + (IsMember[msg.sender, msg.sentTime] && + IsMember[m, msg.sentTime]) => CanReceive[m, t', msg] +} + +check OutsiderCantRead for 5 but 3 Member expect 0 +//check OutsiderCantSend for 5 but 3 Member -- 5 min +//check InsiderCanRead for 9 but 2 Member, 1 KDS, 1 Message +//check InsiderCanRead for 10 but 2 Member, 2 KDS, 2 Message -- not able to check diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/examples/case_studies/sync.als b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/case_studies/sync.als new file mode 100644 index 00000000..b79c5b5d --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/case_studies/sync.als @@ -0,0 +1,134 @@ +module examples/case_studies/sync + +/* + * Model of a generic file synchronizer. + * + * Adapted from: + * Reference: S. Balasubramaniam and Benjamin C. Pierce, + * "What is a File Synchronizer", Indiana University CSCI + * Technical Report #507, April 22, 1998 + * + * For a detailed description, see: + * http://sdg.lcs.mit.edu/pubs/theses/tnolte_masters.pdf + * + * author: Tina Nolte + */ + +private open util/graph[Name] as graph + +/** + * The Name atom represents the hierarchy of all name sequences + * used in the model. A Name atom represents the name, and the path + * in the sequence of names to the root excluding the RootName. + */ +sig Name { + children: set Name +} + +fact { graph/tree[children] } + +one sig RootName extends Name { } + +fact { Name in RootName.*children } + +// We assume that the empty path always + +sig FileContents { } +one sig Dir extends FileContents { } + +pred IsValidFS[fs: Name -> lone FileContents] { + all n: Name - RootName | { + // files don't have children + n.fs != Dir => no (n.^children).fs + // if a path maps to something non-nil, all prefixes do also + some n.fs => some (n.~children).fs + } + // the root of a file system must be a directory + RootName.fs = Dir +} + +pred IsValidDirty[dirty: set Name] { + all n: dirty | n.~children in dirty + RootName in dirty => some dirty - RootName +} + +pred DirtiesValid[A, B: Name -> lone FileContents, Adirty, Bdirty: set Name] { + some O: Name -> lone FileContents | { + IsValidFS[O] + { n: Name | n.O != n.A } in Adirty + { n: Name | n.O != n.B } in Bdirty + } +} + +fun RestrictFS[fs: Name -> lone FileContents, p: Name]: Name -> lone FileContents { + fs & (p.*children -> FileContents) +} + +fun RestrictFSComplement[fs: Name -> lone FileContents, p: Name]: Name -> lone FileContents { + fs & ((Name - p.*children) -> FileContents) +} + +/** + * The following function test whether a particular synchronizer + * operation satisfies the "reasonableness" conditions. + * The arguments are: + * O - the original filesystem. + * A,B - separately modified copies + * Adirty, Bdirty - sets of paths modified in A and B, respectively, from O. + * + * A',B' - results of synchronizer operation + */ +pred SyncSpec[A, B, A', B': Name -> lone FileContents, Adirty, Bdirty: set Name] { + { + IsValidFS[A] + IsValidFS[B] + IsValidDirty[Adirty] + IsValidDirty[Bdirty] + } => { + { + all p: Name | IsRelevantPath[p, A, B] => { + SyncSpecForPath[p, A, B, A', B', Adirty, Bdirty] + } + } + IsValidFS[A'] + IsValidFS[B'] + } +} + +pred SyncSpecForPath[p: Name, A, B, A', B': Name -> lone FileContents, Adirty, Bdirty: set Name] { + (p.A = p.B => (p.A' = p.A && p.B' = p.B)) + (p !in Adirty => (RestrictFS[A', p] = RestrictFS[B, p] && RestrictFS[B', p] = RestrictFS[B, p])) + (p !in Bdirty => (RestrictFS[B', p] = RestrictFS[A, p] && RestrictFS[A', p] = RestrictFS[A, p])) + ((p in Adirty && p in Bdirty && p.A != p.B) => (RestrictFS[A',p] = RestrictFS[A,p] && RestrictFS[B',p] = RestrictFS[B,p])) +} + +pred IsRelevantPath[p: Name, A, B: Name -> lone FileContents] { + p = RootName || { + (p.~children).A = Dir + (p.~children).B = Dir + } +} + +pred SyncSpecExample[A, B, A', B': Name -> lone FileContents, Adirty, Bdirty: set Name] { + IsValidFS[A] + IsValidFS[B] + IsValidDirty[Adirty] + IsValidDirty[Bdirty] + SyncSpec[A, B, A', B', Adirty, Bdirty] + A != A' +} + +//run SyncSpecExample for 3 + +pred SyncSpecNotUnique { + some A, B, A1', B1', A2', B2': Name -> lone FileContents, Adirty, Bdirty: set Name | { + IsValidFS[A] && IsValidFS[B] + IsValidDirty[Adirty] && IsValidDirty[Bdirty] + //DirtiesValid(A, B, Adirty, Bdirty) + (A1' != A2' || B1' != B2') + SyncSpec[A, B, A1', B1', Adirty, Bdirty] + SyncSpec[A, B, A2', B2', Adirty, Bdirty] + } +} + +run SyncSpecNotUnique for 5 expect 0 diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/examples/case_studies/syncimpl.als b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/case_studies/syncimpl.als new file mode 100644 index 00000000..fe20377e --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/case_studies/syncimpl.als @@ -0,0 +1,107 @@ +module examples/case_studies/syncimpl + +/* + * Model of a file synchronizer reconciliation algorithm. + * + * Adapted from: + * Reference: S. Balasubramaniam and Benjamin C. Pierce, + * "What is a File Synchronizer", Indiana University CSCI + * Technical Report #507, April 22, 1998 + * + * For a detailed description, see: + * http://sdg.lcs.mit.edu/pubs/theses/tnolte_masters.pdf + * + * author: Tina Nolte + */ + +open examples/case_studies/sync as sync +open util/ordering[sync/Name] as ord +open util/relation as rel + +// Model the reconciliation algorithm + +sig ReconName extends Name { + Ain, Bin, Aout, Bout: Name->FileContents, + p_children: set Name, + first_p_child, last_p_child: lone Name, + prev_p_child: (p_children - first_p_child) -> p_children +} + +fact { + all x: ReconName { + x.p_children = ChildrenAB[x.Ain, x.Bin, x] + x.first_p_child = { pc: x.p_children | x.p_children in (pc + nexts[pc]) } + x.last_p_child = { pc: x.p_children | x.p_children in (pc + prevs[pc]) } + all p_child: x.p_children - x.first_p_child | { + let earlierChildren = prevs[p_child] & x.p_children | + p_child . (x.prev_p_child) = { earlierChild: earlierChildren | earlierChildren in (earlierChild + @prevs[earlierChild]) } + } + } +} + +fact { ReconName = Name } + +fun ChildrenAB[A, B: Name -> lone FileContents, p: Name]: set Name { + p.children & (dom[A] + dom[B]) +} + +pred reconHelper[Adirty, Bdirty: set Name] { + all p: Name { + let A = p.Ain, B = p.Bin, A' = p.Aout, B' = p.Bout | { + some p.(A+B) => { + (p !in Adirty && p !in Bdirty) => (A' = A && B' = B) else { + (p.A = Dir && p.B = Dir) => { + no p_children => { + p.Aout = p.Ain + p.Bout = p.Bin + } else { + p.first_p_child.Ain = p.Ain + p.first_p_child.Bin = p.Bin + p.Aout = p.last_p_child.Aout + p.Bout = p.last_p_child.Bout + all pchild: p.p_children - p.first_p_child | { + pchild.Ain = (pchild.(p.prev_p_child)).Aout + pchild.Bin = (pchild.(p.prev_p_child)).Bout + } + } // some p_children + } else { // !(p.A = Dir && p.B = Dir) + p !in Adirty => { + A' = RestrictFS[B, p] + RestrictFSComplement[A, p] + B' = B + } else { + p !in Bdirty => { + A' = A + B' = RestrictFS[A, p] + RestrictFSComplement[B, p] + } else { + A' = A + B' = B + } + } // not "p !in Adirty" + } // not case 2 i.e. not both are dirs + } // not both clean + } // some p.(A+B) + } // let A =, B=, A'=, B'= + } // all p: Name +} // reconHelper() + +pred recon[A, B, A', B': Name -> lone FileContents, Adirty, Bdirty: set Name] { + A = ReconName.Ain + B = ReconName.Bin + A' = ReconName.Aout + B' = ReconName.Bout + reconHelper[Adirty, Bdirty] +} + +assert Correctness { + all A, B, A', B': Name -> lone FileContents, Adirty, Bdirty: set Name | { + { + DirtiesValid[A, B, Adirty, Bdirty] + recon[A, B, A', B', Adirty, Bdirty] + //no Adirty + Bdirty + } + => + SyncSpec[A, B, A', B', Adirty, Bdirty] + } +} + +check Correctness for 4 but 2 FileContents expect 0 diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/examples/puzzles/farmer.als b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/puzzles/farmer.als new file mode 100644 index 00000000..d3636486 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/puzzles/farmer.als @@ -0,0 +1,92 @@ +module examples/puzzles/farmer + +/* + * The classic river crossing puzzle. A farmer is carrying a fox, a + * chicken, and a sack of grain. He must cross a river using a boat + * that can only hold the farmer and at most one other thing. If the + * farmer leaves the fox alone with the chicken, the fox will eat the + * chicken; and if he leaves the chicken alone with the grain, the + * chicken will eat the grain. How can the farmer bring everything + * to the far side of the river intact? + * + * authors: Greg Dennis, Rob Seater + * + * Acknowledgements to Derek Rayside and his students for finding and + * fixing a bug in the "crossRiver" predicate. + */ + +open util/ordering[State] as ord + +/** + * The farmer and all his possessions will be represented as Objects. + * Some objects eat other objects when the Farmer's not around. + */ +abstract sig Object { eats: set Object } +one sig Farmer, Fox, Chicken, Grain extends Object {} + +/** + * Define what eats what when the Farmer' not around. + * Fox eats the chicken and the chicken eats the grain. + */ +fact eating { eats = Fox->Chicken + Chicken->Grain } + +/** + * The near and far relations contain the objects held on each + * side of the river in a given state, respectively. + */ +sig State { + near: set Object, + far: set Object +} + +/** + * In the initial state, all objects are on the near side. + */ +fact initialState { + let s0 = ord/first | + s0.near = Object && no s0.far +} + +/** + * Constrains at most one item to move from 'from' to 'to'. + * Also constrains which objects get eaten. + */ +pred crossRiver [from, from', to, to': set Object] { + // either the Farmer takes no items + (from' = from - Farmer - from'.eats and + to' = to + Farmer) or + // or the Farmer takes one item + (one x : from - Farmer | { + from' = from - Farmer - x - from'.eats + to' = to + Farmer + x }) +} + +/** + * crossRiver transitions between states + */ +fact stateTransition { + all s: State, s': ord/next[s] { + Farmer in s.near => + crossRiver[s.near, s'.near, s.far, s'.far] else + crossRiver[s.far, s'.far, s.near, s'.near] + } +} + +/** + * the farmer moves everything to the far side of the river. + */ +pred solvePuzzle { + ord/last.far = Object +} + +run solvePuzzle for 8 State expect 1 + +/** + * no Object can be in two places at once + * this is implied by both definitions of crossRiver + */ +assert NoQuantumObjects { + no s : State | some x : Object | x in s.near and x in s.far +} + +check NoQuantumObjects for 8 State expect 0 \ No newline at end of file diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/examples/puzzles/farmer.thm b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/puzzles/farmer.thm new file mode 100644 index 00000000..41d7ada7 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/puzzles/farmer.thm @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/examples/puzzles/handshake.als b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/puzzles/handshake.als new file mode 100644 index 00000000..8fce6178 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/puzzles/handshake.als @@ -0,0 +1,64 @@ +module examples/puzzles/handshake + +/* + * Alloy model of the Halmos handshake problem + * + * Hilary and Jocelyn are married. They invite four couples who are friends for dinner. When + * they arrive, they shake hands with each other. Nobody shakes hands with him or herself + * or with his or her spouse. After there has been some handshaking, Jocelyn jumps up on + * a chair and says "Stop shaking hands!", and then asks how many hands each person has + * shaken. All the answers are different. How many hands has Hilary shaken? + * + * The Alloy model represents the problem as a set of constraints. Properties of the spouse + * relationship and of handshaking in general are given as facts. The particular situation + * is cast as a function. + * + * There are 9 people answering, and all answers are different. Nobody can shake more than + * 8 hands. So answers must be 0..8. The one (p8 say) who answered 8 has shaken everybody's + * hand except for his or her own, and his or her spouse's. Now consider the person who shook + * 0 hands (p0 say). The persons p0 and p8 are distinct. If they are not married, then p8 cannot + * have shaken 8 hands, because he or she did not shake the hand of p0 or of his or her spouse. + * So p8's spouse to p0. Now imagine Jocelyn asking the question again, with p0 and p8 out of + * the room, and excluding hand shakes with them. Since p8 shook hands with everyone else + * except p0 and p8, everyone gives an answer one smaller than they did before, giving 0..6. + * The argument now applies recursively. So Hilary is left alone, having shaken 4 hands. + * + * author: Daniel Jackson, 11/15/01 + */ + +sig Person {spouse: Person, shaken: set Person} +one sig Jocelyn, Hilary extends Person {} + +fact ShakingProtocol { + // nobody shakes own or spouse's hand + all p: Person | no (p + p.spouse) & p.shaken + // if p shakes q, q shakes p + all p, q: Person | p in q.shaken => q in p.shaken + } + +fact Spouses { + all p, q: Person | p!=q => { + // if q is p's spouse, p is q's spouse + p.spouse = q => q.spouse = p + // no spouse sharing + p.spouse != q.spouse + } + all p: Person { + // a person is his or her spouse's spouse + p.spouse.spouse = p + // nobody is his or her own spouse + p != p.spouse + } + } + +pred Puzzle { + // everyone but Jocelyn has shaken a different number of hands + all p,q: Person - Jocelyn | p!=q => #p.shaken != #q.shaken + // Hilary's spouse is Jocelyn + Hilary.spouse = Jocelyn + } + +P10: run Puzzle for exactly 10 Person, 5 int expect 1 +P12: run Puzzle for exactly 12 Person, 5 int expect 1 +P14: run Puzzle for exactly 14 Person, 5 int expect 1 +P16: run Puzzle for exactly 16 Person, 6 int expect 1 diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/examples/puzzles/handshake.thm b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/puzzles/handshake.thm new file mode 100644 index 00000000..0bc070b1 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/puzzles/handshake.thm @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/examples/puzzles/hanoi.als b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/puzzles/hanoi.als new file mode 100644 index 00000000..f936fa5f --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/puzzles/hanoi.als @@ -0,0 +1,140 @@ +module examples/puzzles/hanoi + +/* + * Towers of Hanoi model + * + * Description of problem from http://www.cut-the-knot.com/recurrence/hanoi.html + * + * The Tower of Hanoi puzzle was invented by the French mathematician Edouard Lucas + * in 1883. We are given a tower of eight disks, initially stacked in decreasing size on + * one of three pegs. The objective is to transfer the entire tower to one of the other + * pegs, moving only one disk at a time and never a larger one onto a smaller. + * + * The Alloy model below is written so that a solution to the model is a complete + * sequence of valid moves solving an instance of the problem. We define constraints + * for the initial state (all discs on left stake), the final state (all discs on right stake), + * and each pair of adjacent states (the top disc is moved from one stake to another, + * not putting larger discs on smaller discs), and let Alloy Analyzer solve for the + * sequence of states satisfying these constraints. Since each adjacent pair of states is + * constrained to be related by a single move, it is easy to see the sequence of moves + * once you have the sequence of states. + * + * For n discs, 2^n states are needed for a solution + * (including the initial state and the final state). + * + * Performance: currently, the problem can be solved for up to 5 discs; this takes + * several minutes with the Chaff solver. + * + * author: Ilya Shlyakhter + */ + +open util/ordering[State] as states +open util/ordering[Stake] as stakes +open util/ordering[Disc] as discs + +sig Stake { } + +sig Disc { } + +/** + * sig State: the complete state of the system -- + * which disc is on which stake. An solution is a + * sequence of states. + */ +sig State { + on: Disc -> one Stake // _each_ disc is on _exactly one_ stake + // note that we simply record the set of discs on each stake -- + // the implicit assumption is that on each stake the discs + // on that stake are ordered by size with smallest disc on top + // and largest on bottom, as the problem requires. +} + +/** + * compute the set of discs on the given stake in this state. + * ~(this.on) map the stake to the set of discs on that stake. + */ +fun discsOnStake[st: State, stake: Stake]: set Disc { + stake.~(st.on) +} + +/** + * compute the top disc on the given stake, or the empty set + * if the stake is empty + */ +fun topDisc[st: State, stake: Stake]: lone Disc { + { d: st.discsOnStake[stake] | st.discsOnStake[stake] in discs/nexts[d] + d } +} + +/** + * Describes the operation of moving the top disc from stake fromStake + * to stake toStake. This function is defined implicitly but is + * nevertheless deterministic, i.e. the result state is completely + * determined by the initial state and fromStake and toStake; hence + * the "det" modifier above. (It's important to use the "det" modifier + * to tell the Alloy Analyzer that the function is in fact deterministic.) + */ +pred Move [st: State, fromStake, toStake: Stake, s': State] { + let d = st.topDisc[fromStake] | { + // all discs on toStake must be larger than d, + // so that we can put d on top of them + st.discsOnStake[toStake] in discs/nexts[d] + // after, the fromStake has the discs it had before, minus d + s'.discsOnStake[fromStake] = st.discsOnStake[fromStake] - d + // after, the toStake has the discs it had before, plus d + s'.discsOnStake[toStake] = st.discsOnStake[toStake] + d + // the remaining stake afterwards has exactly the discs it had before + let otherStake = Stake - fromStake - toStake | + s'.discsOnStake[otherStake] = st.discsOnStake[otherStake] + } +} + +/** + * there is a leftStake that has all the discs at the beginning, + * and a rightStake that has all the discs at the end + */ +pred Game1 { + Disc in states/first.discsOnStake[stakes/first] + some finalState: State | Disc in finalState.discsOnStake[stakes/last] + + // each adjacent pair of states are related by a valid move of one disc + all preState: State - states/last | + let postState = states/next[preState] | + some fromStake: Stake | { + // must have at least one disk on fromStake to be able to move + // a disc from fromStake to toStake + some preState.discsOnStake[fromStake] + // post- results from pre- by making one disc move + some toStake: Stake | preState.Move[fromStake, toStake, postState] + } +} + +/** + * there is a leftStake that has all the discs at the beginning, + * and a rightStake that has all the discs at the end + */ +pred Game2 { + Disc in states/first.discsOnStake[stakes/first] + some finalState: State | Disc in finalState.discsOnStake[stakes/last] + + // each adjacent pair of states are related by a valid move of one disc + all preState: State - states/last | + let postState = states/next[preState] | + some fromStake: Stake | + let d = preState.topDisc[fromStake] | { + // must have at least one disk on fromStake to be able to move + // a disc from fromStake to toStake + some preState.discsOnStake[fromStake] + postState.discsOnStake[fromStake] = preState.discsOnStake[fromStake] - d + some toStake: Stake | { + // post- results from pre- by making one disc move + preState.discsOnStake[toStake] in discs/nexts[d] + postState.discsOnStake[toStake] = preState.discsOnStake[toStake] + d + // the remaining stake afterwards has exactly the discs it had before + let otherStake = Stake - fromStake - toStake | + postState.discsOnStake[otherStake] = preState.discsOnStake[otherStake] + } + } + } + +run Game1 for 1 but 3 Stake, 5 Disc, 32 State expect 1 +run Game2 for 1 but 3 Stake, 3 Disc, 8 State expect 1 diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/examples/puzzles/hanoi.thm b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/puzzles/hanoi.thm new file mode 100644 index 00000000..f56b5722 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/puzzles/hanoi.thm @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/examples/systems/file_system.als b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/systems/file_system.als new file mode 100644 index 00000000..60fd959b --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/systems/file_system.als @@ -0,0 +1,59 @@ +module examples/systems/file_system + +/* + * Model of a generic file system. + */ + +abstract sig Object {} + +sig Name {} + +sig File extends Object {} { some d: Dir | this in d.entries.contents } + +sig Dir extends Object { + entries: set DirEntry, + parent: lone Dir +} { + parent = this.~@contents.~@entries + all e1, e2 : entries | e1.name = e2.name => e1 = e2 + this !in this.^@parent + this != Root => Root in this.^@parent +} + +one sig Root extends Dir {} { no parent } + +lone sig Cur extends Dir {} + +sig DirEntry { + name: Name, + contents: Object +} { + one this.~entries +} + + +/** + * all directories besides root have one parent + */ +pred OneParent_buggyVersion { + all d: Dir - Root | one d.parent +} + +/** + * all directories besides root have one parent + */ +pred OneParent_correctVersion { + all d: Dir - Root | (one d.parent && one contents.d) +} + +/** + * Only files may be linked (that is, have more than one entry) + * That is, all directories are the contents of at most one directory entry + */ +pred NoDirAliases { + all o: Dir | lone o.~contents +} + +check { OneParent_buggyVersion => NoDirAliases } for 5 expect 1 + +check { OneParent_correctVersion => NoDirAliases } for 5 expect 0 diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/examples/systems/file_system.thm b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/systems/file_system.thm new file mode 100644 index 00000000..4b740ad3 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/systems/file_system.thm @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/examples/systems/javatypes_soundness.als b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/systems/javatypes_soundness.als new file mode 100644 index 00000000..6bf50ed7 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/systems/javatypes_soundness.als @@ -0,0 +1,140 @@ +module examples/systems/javatypes + +/* + * Model of the Java type system. The TypeSoundness assertion + * claims that if a Java program type checks successfully, + * then a field will cannot be assigned an incorrect type. + * + * author: Daniel Jackson + */ + +open util/graph[Type] as graph + +abstract sig Type { + xtends: set Type + } +sig Interface extends Type {} + { xtends in Interface } +sig Class extends Type { + implements: set Interface, + fields: set Field + } { lone xtends && xtends in Class } +-- optional: best omitted to allow private etc +-- {xtends.@fields in fields} +sig Field { + declType: Type + } + +fact { + graph/dag[xtends] + } + +abstract sig Value {} +one sig Null extends Value {} +sig Object extends Value { + type: Class, + slot: Field lone -> lone Slot + } {slot.Slot = type.fields} +sig Slot {} + +abstract sig Statement {} +sig Assignment extends Statement { + var: Variable, + expr: Expr + } +sig Setter extends Statement { + field: Field, + lexpr, rexpr: Expr + } + +abstract sig Expr { + type: Type, + subexprs: set Expr + } {subexprs = this + this.^@expr} +sig Variable extends Expr { + declType: Type + } {type = declType} +sig Constructor extends Expr { + class: Class + } +sig Getter extends Expr { + field: Field, + expr: Expr + } {type = field.declType} + +sig State { + objects: set Object, + reaches: Object -> Object, + vars: set Variable, + holds: (Slot + Variable) -> lone Value, + val: Expr -> lone Value + } { + all o: Object | o.reaches = holds[o.slot[Field]] & Object + holds.Value & Variable = vars + objects = holds[vars].^reaches + all e: Expr | let v = val[e] { + e in Variable => v = holds[e] + e in Getter => v = holds[(val[e.expr]).slot[e.field]] + e in Constructor => v in Object and v.type = e.type } + } + +pred RuntimeTypesOK [s: State] { + all o: s.objects, f: o.type.fields | + let v = s.holds [o.slot [f]] | HasType [v, f.declType] + all var: s.vars | + let v = s.holds [var] | HasType [v, var.declType] + } + +pred HasType [v: Value, t: Type] { + v in Null or Subtype [v.type, t] + } + +pred Subtype [t, t': Type] { + t in Class => + (let supers = (t & Class).*(Class<:xtends) | + t' in (supers + supers.implements.*(Interface<:xtends))) + t in Interface => t' in (t & Interface).*(Interface<:xtends) + } + +pred TypeChecksSetter [stmt: Setter] { + all g: Getter & stmt.(lexpr+rexpr).subexprs | g.field in g.expr.type.fields + stmt.field in stmt.lexpr.type.fields + Subtype [stmt.rexpr.type, stmt.field.declType] + } + +pred ExecuteSetter [s, s': State, stmt: Setter] { + stmt.(rexpr+lexpr).subexprs & Variable in s.vars + s'.objects = s.objects and s'.vars = s.vars + let rval = s.val [stmt.rexpr], lval = s.val [stmt.lexpr] { + no lval & Null + s'.holds = s.holds ++ (lval.slot[stmt.field] -> rval) + } + } + +assert TypeSoundness { + all s, s': State, stmt: Setter | + {RuntimeTypesOK[s] + ExecuteSetter [s, s', stmt] + TypeChecksSetter[stmt] + } => RuntimeTypesOK[s'] + } + +fact {all o, o': Object | some o.slot[Field] & o'.slot[Field] => o = o'} +fact {all g: Getter | no g & g.^subexprs} + +fact ScopeFact { + #Assignment =< 1 + #Class =< 5 + #Interface =< 5 +} + +check TypeSoundness for 3 expect 0 + +check TypeSoundness for 2 State, 1 Assignment, +1 Statement, 5 Interface, 5 Class, 1 Null, +7 Object, 12 Expr, 3 Field, 3 Slot expect 0 + +// very slow +// check TypeSoundness for 2 State, 1 Statement, +// 10 Type, 8 Value, 12 Expr, +// 3 Field, 3 Slot expect 0 diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/examples/systems/lists.als b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/systems/lists.als new file mode 100644 index 00000000..aabd5f63 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/systems/lists.als @@ -0,0 +1,48 @@ +/* + * a simple list module + * which demonstrates how to create predicates and fields that mirror each other + * thus allowing recursive constraints (even though recursive predicates are not + * currently supported by Alloy) + * author: Robert Seater + */ + +module list + +sig Thing {} +fact NoStrayThings {Thing in List.car} + +abstract sig List { + equivTo: set List, + prefixes: set List + } +sig NonEmptyList extends List { + car: one Thing, + cdr: one List + } +sig EmptyList extends List {} + +pred isFinite [L:List] {some e: EmptyList | e in L.*cdr} +fact finite {all L: List | isFinite[L]} + +fact Equivalence { + all a,b: List | (a in b.equivTo) <=> ((a.car = b.car and b.cdr in a.cdr.equivTo) and (#a.*cdr = #b.*cdr)) + } +assert reflexive {all L: List | L in L.equivTo} +check reflexive for 6 expect 0 +assert symmetric {all a,b: List | a in b.equivTo <=> b in a.equivTo} +check symmetric for 6 expect 0 +assert empties {all a,b: EmptyList | a in b.equivTo} +check empties for 6 expect 0 + +fact prefix { //a is a prefix of b + all e: EmptyList, L:List | e in L.prefixes + all a,b: NonEmptyList | (a in b.prefixes) <=> (a.car = b.car + and a.cdr in b.cdr.prefixes + and #a.*cdr < #b.*cdr) +} + +pred show { + some a, b: NonEmptyList | a!=b && b in a.prefixes + } +run show for 4 expect 1 + diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/examples/systems/lists.thm b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/systems/lists.thm new file mode 100644 index 00000000..6da43857 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/systems/lists.thm @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/examples/systems/marksweepgc.als b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/systems/marksweepgc.als new file mode 100644 index 00000000..b8081e3f --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/systems/marksweepgc.als @@ -0,0 +1,83 @@ +module examples/systems/marksweepgc + +/* + * Model of mark and sweep garbage collection. + */ + +// a node in the heap +sig Node {} + +sig HeapState { + left, right : Node -> lone Node, + marked : set Node, + freeList : lone Node +} + +pred clearMarks[hs, hs' : HeapState] { + // clear marked set + no hs'.marked + // left and right fields are unchanged + hs'.left = hs.left + hs'.right = hs.right +} + +/** + * simulate the recursion of the mark() function using transitive closure + */ +fun reachable[hs: HeapState, n: Node] : set Node { + n + n.^(hs.left + hs.right) +} + +pred mark[hs: HeapState, from : Node, hs': HeapState] { + hs'.marked = hs.reachable[from] + hs'.left = hs.left + hs'.right = hs.right +} + +/** + * complete hack to simulate behavior of code to set freeList + */ +pred setFreeList[hs, hs': HeapState] { + // especially hackish + hs'.freeList.*(hs'.left) in (Node - hs.marked) + all n: Node | + (n !in hs.marked) => { + no hs'.right[n] + hs'.left[n] in (hs'.freeList.*(hs'.left)) + n in hs'.freeList.*(hs'.left) + } else { + hs'.left[n] = hs.left[n] + hs'.right[n] = hs.right[n] + } + hs'.marked = hs.marked +} + +pred GC[hs: HeapState, root : Node, hs': HeapState] { + some hs1, hs2: HeapState | + hs.clearMarks[hs1] && hs1.mark[root, hs2] && hs2.setFreeList[hs'] +} + +assert Soundness1 { + all h, h' : HeapState, root : Node | + h.GC[root, h'] => + (all live : h.reachable[root] | { + h'.left[live] = h.left[live] + h'.right[live] = h.right[live] + }) +} + +assert Soundness2 { + all h, h' : HeapState, root : Node | + h.GC[root, h'] => + no h'.reachable[root] & h'.reachable[h'.freeList] +} + +assert Completeness { + all h, h' : HeapState, root : Node | + h.GC[root, h'] => + (Node - h'.reachable[root]) in h'.reachable[h'.freeList] +} + +check Soundness1 for 3 expect 0 +check Soundness2 for 3 expect 0 +check Completeness for 3 expect 0 diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/examples/systems/views.als b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/systems/views.als new file mode 100644 index 00000000..3a5ab82b --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/systems/views.als @@ -0,0 +1,217 @@ +module examples/systems/views + +/* + * Model of views in object-oriented programming. + * + * Two object references, called the view and the backing, + * are related by a view mechanism when changes to the + * backing are automatically propagated to the view. Note + * that the state of a view need not be a projection of the + * state of the backing; the keySet method of Map, for + * example, produces two view relationships, and for the + * one in which the map is modified by changes to the key + * set, the value of the new map cannot be determined from + * the key set. Note that in the iterator view mechanism, + * the iterator is by this definition the backing object, + * since changes are propagated from iterator to collection + * and not vice versa. Oddly, a reference may be a view of + * more than one backing: there can be two iterators on the + * same collection, eg. A reference cannot be a view under + * more than one view type. + * + * A reference is made dirty when it is a backing for a view + * with which it is no longer related by the view invariant. + * This usually happens when a view is modified, either + * directly or via another backing. For example, changing a + * collection directly when it has an iterator invalidates + * it, as does changing the collection through one iterator + * when there are others. + * + * More work is needed if we want to model more closely the + * failure of an iterator when its collection is invalidated. + * + * As a terminological convention, when there are two + * complementary view relationships, we will give them types + * t and t'. For example, KeySetView propagates from map to + * set, and KeySetView' propagates from set to map. + * + * author: Daniel Jackson + */ + +open util/ordering[State] as so +open util/relation as rel + +sig Ref {} +sig Object {} + +-- t->b->v in views when v is view of type t of backing b +-- dirty contains refs that have been invalidated +sig State { + refs: set Ref, + obj: refs -> one Object, + views: ViewType -> refs -> refs, + dirty: set refs +-- , anyviews: Ref -> Ref -- for visualization + } +-- {anyviews = ViewType.views} + +sig Map extends Object { + keys: set Ref, + map: keys -> one Ref + }{all s: State | keys + Ref.map in s.refs} +sig MapRef extends Ref {} +fact {State.obj[MapRef] in Map} + +sig Iterator extends Object { + left, done: set Ref, + lastRef: lone done + }{all s: State | done + left + lastRef in s.refs} +sig IteratorRef extends Ref {} +fact {State.obj[IteratorRef] in Iterator} + +sig Set extends Object { + elts: set Ref + }{all s: State | elts in s.refs} +sig SetRef extends Ref {} +fact {State.obj[SetRef] in Set} + +abstract sig ViewType {} +one sig KeySetView, KeySetView', IteratorView extends ViewType {} +fact ViewTypes { + State.views[KeySetView] in MapRef -> SetRef + State.views[KeySetView'] in SetRef -> MapRef + State.views[IteratorView] in IteratorRef -> SetRef + all s: State | s.views[KeySetView] = ~(s.views[KeySetView']) + } + +/** + * mods is refs modified directly or by view mechanism + * doesn't handle possibility of modifying an object and its view at once? + * should we limit frame conds to non-dirty refs? + */ +pred modifies [pre, post: State, rs: set Ref] { + let vr = pre.views[ViewType], mods = rs.*vr { + all r: pre.refs - mods | pre.obj[r] = post.obj[r] + all b: mods, v: pre.refs, t: ViewType | + b->v in pre.views[t] => viewFrame [t, pre.obj[v], post.obj[v], post.obj[b]] + post.dirty = pre.dirty + + {b: pre.refs | some v: Ref, t: ViewType | + b->v in pre.views[t] && !viewFrame [t, pre.obj[v], post.obj[v], post.obj[b]] + } + } + } + +pred allocates [pre, post: State, rs: set Ref] { + no rs & pre.refs + post.refs = pre.refs + rs + } + +/** + * models frame condition that limits change to view object from v to v' when backing object changes to b' + */ +pred viewFrame [t: ViewType, v, v', b': Object] { + t in KeySetView => v'.elts = dom [b'.map] + t in KeySetView' => b'.elts = dom [v'.map] + t in KeySetView' => (b'.elts) <: (v.map) = (b'.elts) <: (v'.map) + t in IteratorView => v'.elts = b'.left + b'.done + } + +pred MapRef.keySet [pre, post: State, setRefs: SetRef] { + post.obj[setRefs].elts = dom [pre.obj[this].map] + modifies [pre, post, none] + allocates [pre, post, setRefs] + post.views = pre.views + KeySetView->this->setRefs + KeySetView'->setRefs->this + } + +pred MapRef.put [pre, post: State, k, v: Ref] { + post.obj[this].map = pre.obj[this].map ++ k->v + modifies [pre, post, this] + allocates [pre, post, none] + post.views = pre.views + } + +pred SetRef.iterator [pre, post: State, iterRef: IteratorRef] { + let i = post.obj[iterRef] { + i.left = pre.obj[this].elts + no i.done + i.lastRef + } + modifies [pre,post,none] + allocates [pre, post, iterRef] + post.views = pre.views + IteratorView->iterRef->this + } + +pred IteratorRef.remove [pre, post: State] { + let i = pre.obj[this], i' = post.obj[this] { + i'.left = i.left + i'.done = i.done - i.lastRef + no i'.lastRef + } + modifies [pre,post,this] + allocates [pre, post, none] + pre.views = post.views + } + +pred IteratorRef.next [pre, post: State, ref: Ref] { + let i = pre.obj[this], i' = post.obj[this] { + ref in i.left + i'.left = i.left - ref + i'.done = i.done + ref + i'.lastRef = ref + } + modifies [pre, post, this] + allocates [pre, post, none] + pre.views = post.views + } + +pred IteratorRef.hasNext [s: State] { + some s.obj[this].left + } + +assert zippishOK { + all + ks, vs: SetRef, + m: MapRef, + ki, vi: IteratorRef, + k, v: Ref | + let s0=so/first, + s1=so/next[s0], + s2=so/next[s1], + s3=so/next[s2], + s4=so/next[s3], + s5=so/next[s4], + s6=so/next[s5], + s7=so/next[s6] | + ({ + precondition [s0, ks, vs, m] + no s0.dirty + ks.iterator [s0, s1, ki] + vs.iterator [s1, s2, vi] + ki.hasNext [s2] + vi.hasNext [s2] + ki.this/next [s2, s3, k] + vi.this/next [s3, s4, v] + m.put [s4, s5, k, v] + ki.remove [s5, s6] + vi.remove [s6, s7] + } => no State.dirty) + } + +pred precondition [pre: State, ks, vs, m: Ref] { + // all these conditions and other errors discovered in scope of 6 but 8,3 + // in initial state, must have view invariants hold + (all t: ViewType, b, v: pre.refs | + b->v in pre.views[t] => viewFrame [t, pre.obj[v], pre.obj[v], pre.obj[b]]) + // sets are not aliases +-- ks != vs + // sets are not views of map +-- no (ks+vs)->m & ViewType.pre.views + // no iterator currently on either set +-- no Ref->(ks+vs) & ViewType.pre.views + } + +check zippishOK for 6 but 8 State, 3 ViewType expect 1 + +/** + * experiment with controlling heap size + */ +fact {all s: State | #s.obj < 5} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/examples/toys/birthday.als b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/toys/birthday.als new file mode 100644 index 00000000..092097f0 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/toys/birthday.als @@ -0,0 +1,68 @@ +module examples/toys/birthday + +/* + * Birthday Book + * + * A classic Z example to explain the basic form of an Alloy model. For the original, + * see J.M. Spivey, The Z Notation, Second Edition, Prentice Hall, 1992. + * + * A birthday book has two fields: known, a set of names (of persons whose birthdays are known), + * and date, a function from known names to dates. The operation AddBirthday adds an association + * between a name and a date; it uses the relational override operator (++), so any existing + * mapping from the name to a date is replaced. DelBirthday removes the entry for a given name. + * FindBirthday obtains the date d for a name n. The argument d is declared to be optional (that is, + * a singleton or empty set), so if there is no entry for n, d will be empty. Remind gives the set + * of names whose birthdays fall on a particular day. + * + * The assertion AddWorks says that if you add an entry, then look it up, you get back what you + * just entered. DelIsUndo says that doing DelBirthday after AddBirthday undoes it, as if the add + * had never happened. The first of these assertions is valid; the second isn't. + * + * The function BusyDay shows a case in which Remind produces more than one card. + * + * author: Daniel Jackson, 11/14/01 + */ + +sig Name {} +sig Date {} +sig BirthdayBook {known: set Name, date: known -> one Date} + +pred AddBirthday [bb, bb': BirthdayBook, n: Name, d: Date] { + bb'.date = bb.date ++ (n->d) + } + +pred DelBirthday [bb, bb': BirthdayBook, n: Name] { + bb'.date = bb.date - (n->Date) + } + +pred FindBirthday [bb: BirthdayBook, n: Name, d: lone Date] { + d = bb.date[n] + } + +pred Remind [bb: BirthdayBook, today: Date, cards: set Name] { + cards = (bb.date).today + } + +pred InitBirthdayBook [bb: BirthdayBook] { + no bb.known + } + +assert AddWorks { + all bb, bb': BirthdayBook, n: Name, d: Date, d': lone Date | + AddBirthday [bb,bb',n,d] && FindBirthday [bb',n,d'] => d = d' + } + +assert DelIsUndo { + all bb1,bb2,bb3: BirthdayBook, n: Name, d: Date| + AddBirthday [bb1,bb2,n,d] && DelBirthday [bb2,bb3,n] + => bb1.date = bb3.date + } + +check AddWorks for 3 but 2 BirthdayBook expect 0 +check DelIsUndo for 3 but 2 BirthdayBook expect 1 + +pred BusyDay [bb: BirthdayBook, d: Date]{ + some cards: set Name | Remind [bb,d,cards] && !lone cards + } + +run BusyDay for 3 but 1 BirthdayBook expect 1 diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/examples/toys/birthday.thm b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/toys/birthday.thm new file mode 100644 index 00000000..3c3b050b --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/toys/birthday.thm @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/examples/toys/ceilingsAndFloors.als b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/toys/ceilingsAndFloors.als new file mode 100644 index 00000000..e02fe438 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/toys/ceilingsAndFloors.als @@ -0,0 +1,46 @@ +module models/examples/toys/CeilingsAndFloors + +/* + * In his 1973 song, Paul Simon said "One Man's Ceiling Is Another Man's Floor". + * Does it follow that "One Man's Floor Is Another Man's Ceiling"? + * + * To see why not, check the assertion BelowToo. + * + * Perhaps simply preventing man's own floor from being his ceiling is enough, + * as is done in the Geometry constraint. BelowToo' shows that there are still + * cases where Geometry holds but the implication does not, although now + * the smallest solution has 3 Men and 3 Platforms instead of just 2 of each. + * + * What if we instead prevent floors and ceilings from being shared, + * as is done in the NoSharing constraint? The assertion BelowToo'' + * has no counterexamples, demonstrating that the implication now + * holds for all small examples. + * + * model author: Daniel Jackson (11/2001) + * modified by Robert Seater (11/2004) + */ + +sig Platform {} +sig Man {ceiling, floor: Platform} + +fact PaulSimon {all m: Man | some n: Man | n.Above[m]} + +pred Above[m, n: Man] {m.floor = n.ceiling} + +assert BelowToo { all m: Man | some n: Man | m.Above[n] } + +check BelowToo for 2 expect 1 + +pred Geometry {no m: Man | m.floor = m.ceiling} + +assert BelowToo' { Geometry => (all m: Man | some n: Man | m.Above[n]) } +check BelowToo' for 2 expect 0 +check BelowToo' for 3 expect 1 + +pred NoSharing { + no m,n: Man | m!=n && (m.floor = n.floor || m.ceiling = n.ceiling) +} + +assert BelowToo'' { NoSharing => (all m: Man | some n: Man | m.Above[n]) } +check BelowToo'' for 6 expect 0 +check BelowToo'' for 10 expect 0 diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/examples/toys/ceilingsAndFloors.thm b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/toys/ceilingsAndFloors.thm new file mode 100644 index 00000000..b58a5b0d --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/toys/ceilingsAndFloors.thm @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/examples/toys/genealogy.als b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/toys/genealogy.als new file mode 100644 index 00000000..2a9e2134 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/toys/genealogy.als @@ -0,0 +1,74 @@ +module examples/toys/genealogy + +/* + * Toy model of genealogical relationships + * + * The purpose of this model is to introduce basic concepts in Alloy. + * The signature Person introduces a set of persons; this is paritioned into + * two subsets, Man and Woman. The subsignature Adam declares a set of men + * with one element -- that is, a scalar. Similarly, Eve declares a single + * woman. + * + * The Person signature declares two fields: a person has one or zero spouses + * and a set of parents. + * + * The facts should be self-explanatory. Note that the constraint that + * spouse is a symmetric relation (that is, p is a spouse of q if q is a spouse + * of p) is written by equating the field, viewed as a relation, to its + * transpose. Since signatures have their own namespaces, and the same field + * name can refer to different fields in different relations, it is necessary + * to indicate which signature the field belongs to. This is not necessary when + * dereferencing a field, because the appropriate field is automatically + * determined by the type of the referencing expression. + * + * The command has no solutions. Given only 5 persons, it's not possible + * to have a couple distinct from Adam and Eve without incest. To understand + * the model, try weakening the constraints by commenting lines out (just + * put two hyphens at the start of a line) and rerunning the command. + * + * author: Daniel Jackson, 11/13/01 + */ + +abstract sig Person {spouse: lone Person, parents: set Person} +sig Man, Woman extends Person {} +one sig Eve extends Woman {} +one sig Adam extends Man {} + +fact Biology { + -- nobody is his or her own ancestor + no p: Person | p in p.^parents + } + +fact Bible { + -- every person except Adam and Eve has a mother and father + all p: Person - (Adam + Eve) | one mother: Woman, father: Man | + p.parents = mother + father + -- Adam and Eve have no parents + no (Adam + Eve).parents + -- Adam's spouse is Eve + Adam.spouse = Eve + } + +fact SocialNorms { + -- nobody is his or her own spouse + no p: Person | p.spouse = p + -- spouse is symmetric + spouse = ~spouse + -- a man's spouse is a woman and vice versa + Man.spouse in Woman && Woman.spouse in Man + } + +fact NoIncest { + -- can't marry a sibling + no p: Person | some p.spouse.parents & p.parents + -- can't marry a parent + no p: Person | some p.spouse & p.parents + } + +pred Show { + some p: Person - (Adam + Eve) | some p.spouse + } +run Show for 6 expect 1 + + + diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/examples/toys/genealogy.thm b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/toys/genealogy.thm new file mode 100644 index 00000000..fbeb5cad --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/toys/genealogy.thm @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/examples/toys/grandpa.als b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/toys/grandpa.als new file mode 100644 index 00000000..099cd0d5 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/toys/grandpa.als @@ -0,0 +1,88 @@ +module grandpa + +/* + * An Alloy model of the song "I Am My Own Grandpa" + * by Dwight B. Latham and Moe Jaffe + * + * The challenge is to produce a man who is his own grandfather + * without resorting to incest or time travel. Executing the predicate + * "ownGrandpa" will demonstrate how such a thing can occur. + * + * The full song lyrics, which describe an isomorophic solution, + * are included at the end of this file. + * + * model author: Daniel Jackson + */ + +abstract sig Person { + father: lone Man, + mother: lone Woman + } + +sig Man extends Person { wife: lone Woman } + +sig Woman extends Person { husband: lone Man } + +fact Biology { no p: Person | p in p.^(mother+father) } + +fact Terminology { wife = ~husband } + +fact SocialConvention { + no wife & *(mother+father).mother + no husband & *(mother+father).father + } + +fun grandpas [p: Person]: set Person { + let parent = mother + father + father.wife + mother.husband | + p.parent.parent & Man + } + +pred ownGrandpa [m: Man] { m in grandpas[m] } + +run ownGrandpa for 4 Person expect 1 + +/* defined variables: + * + * spouse = husband+wife + */ + +/* +I Am My Own Grandpa +by Dwight B. Latham and Moe Jaffe + +Many many years ago, when I was twenty-three, +I was married to a widow as pretty as can be, +This widow had a grown-up daughter who had hair of red, +My father fell in love with her and soon the two were wed. + + I'm my own grandpa, I'm my own grandpa. + It sounds funny, I know, but it really is so + I'm my own grandpa. + +This made my dad my son-in-law and changed my very life, +For my daughter was my mother, for she was my father's wife. +To complicate the matter, even though it brought me joy, +I soon became the father of a bouncing baby boy. + +My little baby thus became a brother-in-law to dad, +And so became my uncle, though it made me very sad, +For if he was my uncle then that also made him brother +To the widow's grown-up daughter, who of course was my step-mother. + +Father's wife then had a son who kept them on the run. +And he became my grandchild for he was my daughter's son. +My wife is now my mother's mother and it makes me blue, +Because although she is my wife, she's my grandmother, too. + +Oh, if my wife's my grandmother then I am her grandchild. +And every time I think of it, it nearly drives me wild. +For now I have become the strangest case you ever saw +As the husband of my grandmother, I am my own grandpa. + + I'm my own grandpa, I'm my own grandpa. + It sounds funny, I know, but it really is so + I'm my own grandpa. + I'm my own grandpa, I'm my own grandpa. + It sounds funny, I know, but it really is so + I'm my own grandpa. +*/ diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/examples/toys/grandpa.thm b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/toys/grandpa.thm new file mode 100644 index 00000000..781b6c9f --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/toys/grandpa.thm @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/examples/toys/javatypes.als b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/toys/javatypes.als new file mode 100644 index 00000000..2e60d64d --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/toys/javatypes.als @@ -0,0 +1,46 @@ +module examples/toys/javatypes + +/* + * A simple model of typing in Java. + * + * This model describes the basic notions of typing in Java. + * It ignores primitive types and null references. Each type has + * some set of subtypes. Types are partitioned into class and + * interface types. Object is a particular class. + * + * The fact TypeHierarchy says that every type is a direct or + * indirect subtype of Object; that no type is a direct or indirect + * of itself; and every type is a subtype of at most one class. + * + * An object instance has a type (its creation type) that is a class. + * A variable may hold an instance, and has a declared type. The + * fact TypeSoundness says that all instances held by a variable + * have types that are direct or indirect subtypes of the variable's + * declared type. + * + * The function Show specifies a case in which there is a class + * distinct from Object; there is some interface; and some variable + * has a declared type that is an interface. + * + * author: Daniel Jackson, 11/13/01 + */ + +abstract sig Type {subtypes: set Type} +sig Class, Interface extends Type {} +one sig Object extends Class {} +fact TypeHierarchy { + Type in Object.*subtypes + no t: Type | t in t.^subtypes + all t: Type | lone t.~subtypes & Class + } +sig Instance {type: Class} +sig Variable {holds: lone Instance, type: Type} +fact TypeSoundness { + all v: Variable | v.holds.type in v.type.*subtypes + } +pred Show { + some Class - Object + some Interface + some Variable.type & Interface + } +run Show for 3 expect 1 diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/examples/toys/life.als b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/toys/life.als new file mode 100644 index 00000000..5ee1b159 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/toys/life.als @@ -0,0 +1,94 @@ +module examples/toys/life + +/* + * John Conway's Game of Life + * + * For a detailed description, see: + * http://www.math.com/students/wonders/life/life.html + * + * authors: Bill Thies, Manu Sridharan + */ + +open util/ordering[State] as ord + +sig Point { + right: lone Point, + below: lone Point +} + +fact Acyclic { + all p: Point | p !in p.^(right + below) +} + +one sig Root extends Point {} + +fact InnerSquaresCommute { + all p: Point { + p.below.right = p.right.below + some p.below && some p.right => some p.below.right + } +} + +fact TopRow { + all p: Point - Root | no p.~below => # p.*below = # Root.*below +} + +fact Connected { + Root.*(right + below) = Point +} + +pred Square { + # Root.*right = # Root.*below +} + +run Square for 6 Point, 3 State expect 1 + +pred Rectangle {} + +sig State { + live : set Point +} + +fun Neighbors[p : Point] : set Point { + p.right + p.right.below + p.below + + p.below.~right + p.~right + + p.~right.~below + p.~below + + p.~below.right +} + +fun LiveNeighborsInState[p : Point, s : State] : set Point { + Neighbors[p] & s.live +} + +pred Trans[pre, post: State, p : Point] { + let preLive = LiveNeighborsInState[p,pre] | + // dead cell w/ 3 live neighbors becomes live + (p !in pre.live && # preLive = 3) => + p in post.live + else ( + // live cell w/ 2 or 3 live neighbors stays alive + (p in pre.live && (# preLive = 2 || # preLive = 3)) => + p in post.live else p !in post.live + ) +} + +fact ValidTrans { + all pre : State - ord/last | + let post = ord/next[pre] | + all p : Point | + Trans[pre,post,p] +} + +pred Show {} + +// slow +run Show for exactly 12 Point, 3 State expect 1 + +// a small but interesting example +pred interesting { + some State.live + some Point - State.live + some right + some below +} +run interesting for exactly 6 Point, 3 State expect 1 \ No newline at end of file diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/examples/toys/life.thm b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/toys/life.thm new file mode 100644 index 00000000..3a015d56 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/toys/life.thm @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/examples/toys/numbering.als b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/toys/numbering.als new file mode 100644 index 00000000..f8c9b5f3 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/toys/numbering.als @@ -0,0 +1,129 @@ +module examples/toys/numbering + +/* + * Alloy model of paragraph numbering + * + * This model addresses some issues that arose in the design of a text tagging tool. The + * tool receives as input a text stream in which paragraphs are tagged with style names, + * along with a style sheet that indicates how paragraphs of a given style are to be numbered. + * In practice, the style sheet includes such details as what symbols to use for numbering (eg, + * roman numericals, letters of the alphabet, etc), but these details are uninteresting. + * + * In the simplest case, the styles are organized into chains. For example, there may be a + * single chain, chapter-section-subsection, so that chapters are numbered 1, 2, 3, etc, + * sections are numbered 1.1, 1.2, 1.3, etc, and subsections are numbered 1.1.1, 1.1.2, + * etc, each paragraph being numbered according to a number associated with its own + * style, and a number for each ancestor. + * + * Some styles, however, should be numbered independently of one another, but still + * according to the same ancestors. For example, we might also have a figure style + * that is numbered, like section, according to its chapter, with figures and sections in + * some arbitrary interleaving, the numbering of one not affecting the other. + * + * So in our style hierarchy, a style can have more than one "child". A more tricky complication + * allows multiple parents. We might want to have an appendix style, for example, with a + * different numbering from the chapter style, but would want section and subsection to work + * within appendix exactly as they would work within chapter. So the first section in an + * appendix numbered A might be numbered A.1, but if placed in a chapter numbered 1, + * it would be numbered 1.1 instead. + * + * To account for this, styles are organized into replacement classes. Chapter and appendix, + * for example, are replacements of one another. When a chapter style is encountered, it + * is as if the style hierarchy contains only chapter, with children section, figure and so on; + * when appendix is encountered subsequently, chapter is replaced, and figure and section + * become children of appendix. We'll call the set of styles active in the tree at a given time + * the "context". + * + * The first part focuses on the replacement mechanism. It characterizes a well-formed + * style sheet (with the fact StyleSheet), and a well-formed state (with the fact Forest). An + * operation addStyleToContext describes how the context is altered, and includes a + * precondition requiring that, for example, a child is not encountered before its parents + * (a document can't start with subsection, eg). The assertion PreservesForest checks that the + * operation preserves the well-formedness of the state; it was analyzing this that helped + * determine an appropriate precondition and the appropriate constraints in the invariant. + * + * The second part adds the numbering of styles. Note the idiom of declaring a subsignature + * and then equating it to the supersignature, thus essentially retrofitting the new fields to the + * old signature. A second operation describes how numbers are assigned; the conjunction of the + * two operations is what happens when a style is encountered. The assertion AddNeverReduces + * checks that when a style is encountered the number associated with each style in the context is + * not decreased. The first assertion is valid; the second isn't. + * + * author: Daniel Jackson, 11/15/01 + */ + +open util/relation as rel + +sig Style { + replaces, parents: set Style + } + +fact StyleSheet { + equivalence [replaces, Style] + acyclic [parents, Style] + all x: Style { + x.replaces.parents.replaces = x.parents + all y,z: x.parents | y in z.replaces + } + } + +sig State { + context: set Style, + ancestors: Style -> Style + } + +fact DefineAncestors { + all s: State, x: Style | s.ancestors [x] = x.*parents & s.context + } + +pred Forest [s: State] { + all x: s.context | + some root: s.ancestors[x] { + no root.parents + all y: s.ancestors[x] - root | one y.parents & s.context + } + all x: Style | lone x.replaces & s.context + } + +pred AddStyleToContext [s, s': State, style: Style] { + all x: style.^parents | some x.replaces & s.context + s'.context = s.context - style.replaces + style + } + +assert PreserveForest { + all s,s': State, z: Style | + Forest[s] && AddStyleToContext [s,s',z] => Forest[s'] + } + +check PreserveForest for 4 expect 0 + +sig Value {next: Value} +sig NumberedStyle extends Style { + initial: Value + } +sig NumberedState extends State { + value: Style -> one Value + } +fact {Style = NumberedStyle} +fact {State = NumberedState} + +pred AddStyleToNumbering [s, s': State, style: Style] { + s'.value[style] = (style in s.context => s.value[style].next else style.initial) + s'.context = s.context - style.replaces + style + all x: Style - style | + s'.value[x] = (style in x.^parents => x.initial else s.value[x]) + } + +pred AddStyle [s, s': State, style: Style] { + AddStyleToContext [s,s',style] + AddStyleToNumbering [s,s',style] + } + +assert AddNeverReduces { + all s,s': State, z: Style | + Forest[s] && AddStyle [s,s',z] => + (all y: s'.context | s'.value[y] in s.value[y].*next) + } + +check AddNeverReduces for 5 expect 1 + diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/examples/toys/railway.als b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/toys/railway.als new file mode 100644 index 00000000..549bdb92 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/toys/railway.als @@ -0,0 +1,64 @@ +module examples/toys/railway + +/* + * A simple model of a railway system. Trains sit on segments of tracks + * and segments overlap one another. It shows a that simple gate policy + * does not ensure train safety. + * + * author: Daniel Jackson + */ + +sig Seg {next, overlaps: set Seg} +fact {all s: Seg | s in s.overlaps} +fact {all s1, s2: Seg | s1 in s2.overlaps => s2 in s1.overlaps} + +sig Train {} +sig GateState {closed: set Seg} +sig TrainState {on: Train -> lone Seg, occupied: set Seg} +fact {all x: TrainState | + x.occupied = {s: Seg | some t: Train | t.(x.on) = s} + } + +pred Safe [x: TrainState] {all s: Seg | lone s.overlaps.~(x.on)} + +pred MayMove [g: GateState, x: TrainState, ts: set Train] { + no ts.(x.on) & g.closed + } + +pred TrainsMove [x, x': TrainState, ts: set Train] { + all t: ts | t.(x'.on) in t.(x.on).next + all t: Train - ts | t.(x'.on) = t.(x.on) + } + +pred GatePolicy [g: GateState, x: TrainState] { + x.occupied.overlaps.~next in g.closed + all s1, s2: Seg | some s1.next.overlaps & s2.next => lone (s1+s2) - g.closed +} + +assert PolicyWorks { + all x, x': TrainState, g: GateState, ts: set Train | + {MayMove [g, x, ts] + TrainsMove [x, x', ts] + Safe [x] + GatePolicy [g, x] + } => Safe [x'] + } + +-- has counterexample in scope of 4 +check PolicyWorks for 2 Train, 1 GateState, 2 TrainState, 4 Seg expect 1 + +pred TrainsMoveLegal [x, x': TrainState, g: GateState, ts: set Train] { + TrainsMove [x, x', ts] + MayMove [g, x, ts] + GatePolicy [g, x] + } +run TrainsMoveLegal for 3 expect 1 + + + +// DEFINED VARIABLES +// Defined variables are uncalled, no-argument functions. +// They are helpful for getting good visualization. +fun contains [] : TrainState -> Seg -> Train { + {state: TrainState, seg: Seg, train: Train | seg = train.(state.on)} +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/examples/toys/railway.thm b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/toys/railway.thm new file mode 100644 index 00000000..40baef57 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/toys/railway.thm @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/examples/toys/trivial.als b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/toys/trivial.als new file mode 100644 index 00000000..71f19d3d --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/toys/trivial.als @@ -0,0 +1,8 @@ +//a trivial model whose command has no solution +module trivial + +sig S {} + +fact { 1=2 } + +run {some S} expect 0 diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/examples/tutorial/farmer.als b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/tutorial/farmer.als new file mode 100644 index 00000000..94ea0d5d --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/examples/tutorial/farmer.als @@ -0,0 +1,92 @@ +module examples/tutorial/farmer + +/* + * The classic river crossing puzzle. A farmer is carrying a fox, a + * chicken, and a sack of grain. He must cross a river using a boat + * that can only hold the farmer and at most one other thing. If the + * farmer leaves the fox alone with the chicken, the fox will eat the + * chicken; and if he leaves the chicken alone with the grain, the + * chicken will eat the grain. How can the farmer bring everything + * to the far side of the river intact? + * + * authors: Greg Dennis, Rob Seater + * + * Acknowledgements to Derek Rayside and his students for finding and + * fixing a bug in the "crossRiver" predicate. + */ + +open util/ordering[State] as ord + +/** + * The farmer and all his possessions will be represented as Objects. + * Some objects eat other objects when the Farmer's not around. + */ +abstract sig Object { eats: set Object } +one sig Farmer, Fox, Chicken, Grain extends Object {} + +/** + * Define what eats what when the Farmer' not around. + * Fox eats the chicken and the chicken eats the grain. + */ +fact eating { eats = Fox->Chicken + Chicken->Grain } + +/** + * The near and far relations contain the objects held on each + * side of the river in a given state, respectively. + */ +sig State { + near: set Object, + far: set Object +} + +/** + * In the initial state, all objects are on the near side. + */ +fact initialState { + let s0 = ord/first | + s0.near = Object && no s0.far +} + +/** + * Constrains at most one item to move from 'from' to 'to'. + * Also constrains which objects get eaten. + */ +pred crossRiver [from, from', to, to': set Object] { + // either the Farmer takes no items + (from' = from - Farmer - from'.eats and + to' = to + Farmer) or + // or the Farmer takes one item + (one x : from - Farmer | { + from' = from - Farmer - x - from'.eats + to' = to + Farmer + x }) +} + +/** + * crossRiver transitions between states + */ +fact stateTransition { + all s: State, s': ord/next[s] { + Farmer in s.near => + crossRiver[s.near, s'.near, s.far, s'.far] else + crossRiver[s.far, s'.far, s.near, s'.near] + } +} + +/** + * the farmer moves everything to the far side of the river. + */ +pred solvePuzzle { + ord/last.far = Object +} + +run solvePuzzle for 8 State expect 1 + +/** + * no Object can be in two places at once + * this is implied by both definitions of crossRiver + */ +assert NoQuantumObjects { + no s : State | some x : Object | x in s.near and x in s.far +} + +check NoQuantumObjects for 8 State expect 0 \ No newline at end of file diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/util/boolean.als b/Source/eu.modelwriter.alloyanalyzer/src/models/util/boolean.als new file mode 100644 index 00000000..61d8744e --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/util/boolean.als @@ -0,0 +1,43 @@ +module util/boolean + +/* + * Creates a Bool type with two singleton subtypes: True + * and False. Provides common boolean operations. + * + * author: Greg Dennis + */ + +abstract sig Bool {} +one sig True, False extends Bool {} + +pred isTrue[b: Bool] { b in True } + +pred isFalse[b: Bool] { b in False } + +fun Not[b: Bool] : Bool { + Bool - b +} + +fun And[b1, b2: Bool] : Bool { + subset_[b1 + b2, True] +} + +fun Or[b1, b2: Bool] : Bool { + subset_[True, b1 + b2] +} + +fun Xor[b1, b2: Bool] : Bool { + subset_[Bool, b1 + b2] +} + +fun Nand[b1, b2: Bool] : Bool { + subset_[False, b1 + b2] +} + +fun Nor[b1, b2: Bool] : Bool { + subset_[b1 + b2, False] +} + +fun subset_[s1, s2: set Bool] : Bool { + (s1 in s2) => True else False +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/util/graph.als b/Source/eu.modelwriter.alloyanalyzer/src/models/util/graph.als new file mode 100644 index 00000000..b722ac13 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/util/graph.als @@ -0,0 +1,78 @@ +module util/graph[node] + +/* + * Utilities for some common operations and contraints + * on graphs. + * + * author: Greg Dennis + */ + +open util/relation as rel + +/** graph in undirected */ +pred undirected [r: node->node] { + symmetric[r] +} + +/** graph has no self-loops */ +pred noSelfLoops[r: node->node] { + irreflexive[r] +} + +/** graph is weakly connected */ +pred weaklyConnected[r: node->node] { + all n1, n2: node | n1 in n2.*(r + ~r) // Changed from ^ to * to permit singleton +} + +/** graph is strongly connected */ +pred stronglyConnected[r: node->node] { + all n1, n2: node | n1 in n2.*r // Changed from ^ to * to permit singleton +} + +/** graph is rooted at root */ +pred rootedAt[r: node->node, root: node] { + node in root.*r +} + +/** graph is a ring */ +pred ring [r: node->node] { + all n: node | one n.r && rootedAt[r, n] +} + +/** graph is a dag */ +pred dag [r: node->node] { + acyclic[r, node] +} + +/** graph is a forest */ +pred forest [r: node->node] { + dag[r] + all n: node | lone r.n +} + +/** graph is a tree */ +pred tree [r: node->node] { + forest[r] + lone root: node | no r.root +} + +/** graph is a tree rooted at root */ +pred treeRootedAt[r: node->node, root: node] { + forest[r] + rootedAt[r, root] +} + +/** returns the roots of the graph */ +fun roots [r: node->node] : set node { + node - node.^r +} + +/** returns the leaves of the grpah */ +fun leaves [r: node->node] : set node { + node - node.^~r +} + +/** returns the inner nodes (non-leaves) of the graph */ +fun innerNodes [r: node->node] : set node { + node - leaves[r] +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/util/integer.als b/Source/eu.modelwriter.alloyanalyzer/src/models/util/integer.als new file mode 100644 index 00000000..488c25e2 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/util/integer.als @@ -0,0 +1,112 @@ +module util/integer + +/* + * A collection of utility functions for using Integers in Alloy. + * Note that integer overflows are silently truncated to the current bitwidth + * using the 2's complement arithmetic, unless the "forbid overfows" option is + * turned on, in which case only models that don't have any overflows are + * analyzed. + */ + +fun add [n1, n2: Int] : Int { this/plus[n1, n2] } +fun plus [n1, n2: Int] : Int { n1 fun/add n2 } + +fun sub [n1, n2: Int] : Int { this/minus[n1, n2] } +fun minus [n1, n2: Int] : Int { n1 fun/sub n2 } + +fun mul [n1, n2: Int] : Int { n1 fun/mul n2 } + +/** + * Performs the division with "round to zero" semantics, except the following 3 cases + * 1) if a is 0, then it returns 0 + * 2) else if b is 0, then it returns 1 if a is negative and -1 if a is positive + * 3) else if a is the smallest negative integer, and b is -1, then it returns a + */ +fun div [n1, n2: Int] : Int { n1 fun/div n2 } + +/** answer is defined to be the unique integer that satisfies "a = ((a/b)*b) + remainder" */ +fun rem [n1, n2: Int] : Int { n1 fun/rem n2 } + +/** negate */ +fun negate [n: Int] : Int { 0 fun/sub n } + +/** equal to */ +pred eq [n1, n2: Int] { int[n1] = int[n2] } + +/** greater than */ +pred gt [n1, n2: Int] { n1 > n2 } + +/** less then */ +pred lt [n1, n2: Int] { n1 < n2 } + +/** greater than or equal */ +pred gte [n1, n2: Int] { n1 >= n2 } + +/** less than or equal */ +pred lte [n1, n2: Int] { n1 <= n2 } + +/** integer is zero */ +pred zero [n: Int] { n = 0 } + +/** positive */ +pred pos [n: Int] { n > 0 } + +/** negative */ +pred neg [n: Int] { n < 0 } + +/** non-positive */ +pred nonpos [n: Int] { n <= 0 } + +/** non-negative */ +pred nonneg [n: Int] { n >= 0 } + +/** signum (aka sign or sgn) */ +fun signum [n: Int] : Int { n<0 => (0 fun/sub 1) else (n>0 => 1 else 0) } + +/** + * returns the ith element (zero-based) from the set s + * in the ordering of 'next', which is a linear ordering + * relation like that provided by util/ordering + */ +fun int2elem[i: Int, next: univ->univ, s: set univ] : lone s { + {e: s | #^next.e = int i } +} + +/** + * returns the index of the element (zero-based) in the + * ordering of next, which is a linear ordering relation + * like that provided by util/ordering + */ +fun elem2int[e: univ, next: univ->univ] : lone Int { + Int[#^next.e] +} + +/** returns the largest integer in the current bitwidth */ +fun max:one Int { fun/max } + +/** returns the smallest integer in the current bitwidth */ +fun min:one Int { fun/min } + +/** maps each integer (except max) to the integer after it */ +fun next:Int->Int { fun/next } + +/** maps each integer (except min) to the integer before it */ +fun prev:Int->Int { ~next } + +/** given a set of integers, return the largest element */ +fun max [es: set Int]: lone Int { es - es.^prev } + +/** given a set of integers, return the smallest element */ +fun min [es: set Int]: lone Int { es - es.^next } + +/** given an integer, return all integers prior to it */ +fun prevs [e: Int]: set Int { e.^prev } + +/** given an integer, return all integers following it */ +fun nexts [e: Int]: set Int { e.^next } + +/** returns the larger of the two integers */ +fun larger [e1, e2: Int]: Int { let a=int[e1], b=int[e2] | (a b else a) } + +/** returns the smaller of the two integers */ +fun smaller [e1, e2: Int]: Int { let a=int[e1], b=int[e2] | (a a else b) } diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/util/natural.als b/Source/eu.modelwriter.alloyanalyzer/src/models/util/natural.als new file mode 100644 index 00000000..5d9f6246 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/util/natural.als @@ -0,0 +1,78 @@ +module util/natural + +/* + * Utility function and predicates for using the set of + * nonnegative integers (0, 1, 2, . . .). The number of + * naturals present in an analysis will be equal to the + * scope on Natural. Specifically, if the scope on Natural + * is N, then the naturals 0 through N-1 will be present. + * + * Note that the functions that return Naturals, such as + * 'add' and 'div', may return an empty set if no such + * Natural exists for that integer value. + * + * To write an Alloy model that makes use of negative + * integers, use the util/integer module instead. + * + * @author Greg Dennis + */ + +private open util/ordering[Natural] as ord +private open util/integer as integer + +sig Natural {} + +/** the integer zero */ +one sig Zero in Natural {} + +/** the integer one will be the empty set if the scope on Natural is less than two */ +lone sig One in Natural {} + +fact { + first in Zero + next[first] in One +} + +/** returns n + 1 */ +fun inc [n: Natural] : lone Natural { ord/next[n] } + +/** returns n - 1 */ +fun dec [n: Natural] : lone Natural { ord/prev[n] } + +/** returns n1 + n2 */ +fun add [n1, n2: Natural] : lone Natural { + {n: Natural | #ord/prevs[n] = #ord/prevs[n1] + #ord/prevs[n2]} +} + +/** returns n1 - n2 */ +fun sub [n1, n2: Natural] : lone Natural { + {n: Natural | #ord/prevs[n1] = #ord/prevs[n2] + #ord/prevs[n]} +} + +/** returns n1 * n2 */ +fun mul [n1, n2: Natural] : lone Natural { + {n: Natural | #ord/prevs[n] = #(ord/prevs[n1]->ord/prevs[n2])} +} + +/** returns n1 / n2 */ +fun div [n1, n2: Natural] : lone Natural { + {n: Natural | #ord/prevs[n1] = #(ord/prevs[n2]->ord/prevs[n])} +} + +/** returns true iff n1 is greater than n2 */ +pred gt [n1, n2: Natural] { ord/gt [n1, n2] } + +/** returns true iff n1 is less than n2 */ +pred lt [n1, n2: Natural] { ord/lt [n1, n2] } + +/** returns true iff n1 is greater than or equal to n2 */ +pred gte [n1, n2: Natural] { ord/gte[n1, n2] } + +/** returns true iff n1 is less than or equal to n2 */ +pred lte [n1, n2: Natural] { ord/lte[n1, n2] } + +/** returns the maximum integer in ns */ +fun max [ns: set Natural] : lone Natural { ord/max[ns] } + +/** returns the minimum integer in ns */ +fun min [ns: set Natural] : lone Natural { ord/min[ns] } diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/util/ordering.als b/Source/eu.modelwriter.alloyanalyzer/src/models/util/ordering.als new file mode 100644 index 00000000..e26d7bd0 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/util/ordering.als @@ -0,0 +1,106 @@ +module util/ordering[exactly elem] + +/* + * Creates a single linear ordering over the atoms in elem. It also constrains all + * the atoms to exist that are permitted by the scope on elem. That is, if the scope + * on a signature S is 5, opening util/ordering[S] will force S to have 5 elements + * and create a linear ordering over those five elements. The predicates and + * functions below provide access to properties of the linear ordering, such as + * which element is first in the ordering, or whether a given element precedes + * another. You cannotcreate multiple linear orderings over the same signature with + * this model. If you that functionality, try using the util/sequence module instead. + * + * Technical comment: + * An important constraint: elem must contain all atoms permitted by the scope. + * This is to let the analyzer optimize the analysis by setting all fields of each + * instantiation of Ord to predefined values: e.g. by setting 'last' to the highest + * atom of elem and by setting 'next' to {,,...}, where n is + * the scope of elem. Without this constraint, it might not be true that Ord.last is + * a subset of elem, and that the domain and range of Ord.next lie inside elem. + * + * author: Ilya Shlyakhter + * revisions: Daniel jackson + */ + +private one sig Ord { + First: set elem, + Next: elem -> elem +} { + pred/totalOrder[elem,First,Next] +} + +/** first */ +fun first: one elem { Ord.First } + +/** last */ +fun last: one elem { elem - (next.elem) } + +/** return a mapping from each element to its predecessor */ +fun prev : elem->elem { ~(Ord.Next) } + +/** return a mapping from each element to its successor */ +fun next : elem->elem { Ord.Next } + +/** return elements prior to e in the ordering */ +fun prevs [e: elem]: set elem { e.^(~(Ord.Next)) } + +/** return elements following e in the ordering */ +fun nexts [e: elem]: set elem { e.^(Ord.Next) } + +/** e1 is less than e2 in the ordering */ +pred lt [e1, e2: elem] { e1 in prevs[e2] } + +/** e1 is greater than e2 in the ordering */ +pred gt [e1, e2: elem] { e1 in nexts[e2] } + +/** e1 is less than or equal to e2 in the ordering */ +pred lte [e1, e2: elem] { e1=e2 || lt [e1,e2] } + +/** e1 is greater than or equal to e2 in the ordering */ +pred gte [e1, e2: elem] { e1=e2 || gt [e1,e2] } + +/** returns the larger of the two elements in the ordering */ +fun larger [e1, e2: elem]: elem { lt[e1,e2] => e2 else e1 } + +/** returns the smaller of the two elements in the ordering */ +fun smaller [e1, e2: elem]: elem { lt[e1,e2] => e1 else e2 } + +/** + * returns the largest element in es + * or the empty set if es is empty + */ +fun max [es: set elem]: lone elem { es - es.^(~(Ord.Next)) } + +/** + * returns the smallest element in es + * or the empty set if es is empty + */ +fun min [es: set elem]: lone elem { es - es.^(Ord.Next) } + +assert correct { + let mynext = Ord.Next | + let myprev = ~mynext | { + ( all b:elem | (lone b.next) && (lone b.prev) && (b !in b.^mynext) ) + ( (no first.prev) && (no last.next) ) + ( all b:elem | (b!=first && b!=last) => (one b.prev && one b.next) ) + ( !one elem => (one first && one last && first!=last && one first.next && one last.prev) ) + ( one elem => (first=elem && last=elem && no myprev && no mynext) ) + ( myprev=~mynext ) + ( elem = first.*mynext ) + (all disj a,b:elem | a in b.^mynext or a in b.^myprev) + (no disj a,b:elem | a in b.^mynext and a in b.^myprev) + (all disj a,b,c:elem | (b in a.^mynext and c in b.^mynext) =>(c in a.^mynext)) + (all disj a,b,c:elem | (b in a.^myprev and c in b.^myprev) =>(c in a.^myprev)) + } +} +run {} for exactly 0 elem expect 0 +run {} for exactly 1 elem expect 1 +run {} for exactly 2 elem expect 1 +run {} for exactly 3 elem expect 1 +run {} for exactly 4 elem expect 1 +check correct for exactly 0 elem +check correct for exactly 1 elem +check correct for exactly 2 elem +check correct for exactly 3 elem +check correct for exactly 4 elem +check correct for exactly 5 elem diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/util/relation.als b/Source/eu.modelwriter.alloyanalyzer/src/models/util/relation.als new file mode 100644 index 00000000..d648a77c --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/util/relation.als @@ -0,0 +1,101 @@ +module util/relation + +/* + * Utilities for some common operations and constraints + * on binary relations. The keyword 'univ' represents the + * top-level type, which all other types implicitly extend. + * Therefore, all the functions and predicates in this model + * may be applied to binary relations of any type. + * + * author: Greg Dennis + */ + +/** returns the domain of a binary relation */ +fun dom [r: univ->univ] : set (r.univ) { r.univ } + +/** returns the range of a binary relation */ +fun ran [r: univ->univ] : set (univ.r) { univ.r } + +/** r is total over the domain s */ +pred total [r: univ->univ, s: set univ] { + all x: s | some x.r +} + +/** r is a partial function over the domain s */ +pred functional [r: univ->univ, s: set univ] { + all x: s | lone x.r +} + +/** r is a total function over the domain s */ +pred function [r: univ->univ, s: set univ] { + all x: s | one x.r +} + +/** r is surjective over the codomain s */ +pred surjective [r: univ->univ, s: set univ] { + all x: s | some r.x +} + +/** r is injective */ +pred injective [r: univ->univ, s: set univ] { + all x: s | lone r.x +} + +/** r is bijective over the codomain s */ +pred bijective[r: univ->univ, s: set univ] { + all x: s | one r.x +} + +/** r is a bijection over the domain d and the codomain c */ +pred bijection[r: univ->univ, d, c: set univ] { + function[r, d] && bijective[r, c] +} + +/** r is reflexive over the set s */ +pred reflexive [r: univ -> univ, s: set univ] {s<:iden in r} + +/** r is irreflexive */ +pred irreflexive [r: univ -> univ] {no iden & r} + +/** r is symmetric */ +pred symmetric [r: univ -> univ] {~r in r} + +/** r is anti-symmetric */ +pred antisymmetric [r: univ -> univ] {~r & r in iden} + +/** r is transitive */ +pred transitive [r: univ -> univ] {r.r in r} + +/** r is acyclic over the set s */ +pred acyclic[r: univ->univ, s: set univ] { + all x: s | x !in x.^r +} + +/** r is complete over the set s */ +pred complete[r: univ->univ, s: univ] { + all x,y:s | (x!=y => x->y in (r + ~r)) +} + +/** r is a preorder (or a quasi-order) over the set s */ +pred preorder [r: univ -> univ, s: set univ] { + reflexive[r, s] + transitive[r] +} + +/** r is an equivalence relation over the set s */ +pred equivalence [r: univ->univ, s: set univ] { + preorder[r, s] + symmetric[r] +} + +/** r is a partial order over the set s */ +pred partialOrder [r: univ -> univ, s: set univ] { + preorder[r, s] + antisymmetric[r] +} + +/** r is a total order over the set s */ +pred totalOrder [r: univ -> univ, s: set univ] { + partialOrder[r, s] + complete[r, s] +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/util/seqrel.als b/Source/eu.modelwriter.alloyanalyzer/src/models/util/seqrel.als new file mode 100644 index 00000000..1fe3802d --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/util/seqrel.als @@ -0,0 +1,108 @@ +module util/seqrel[elem] + +/* + * A sequence utility for modeling sequences as just a + * relation as opposed to reifying them into sequence + * atoms like the util/sequence module does. + * + * @author Greg Dennis + */ + +open util/integer +open util/ordering[SeqIdx] as ord + +sig SeqIdx {} + +/** sequence covers a prefix of SeqIdx */ +pred isSeq[s: SeqIdx -> elem] { + s in SeqIdx -> lone elem + s.inds - ord/next[s.inds] in ord/first +} + +/** returns all the elements in this sequence */ +fun elems [s: SeqIdx -> elem]: set elem { SeqIdx.s } + +/** returns the first element in the sequence */ +fun first [s: SeqIdx -> elem]: lone elem { s[ord/first] } + +/** returns the last element in the sequence */ +fun last [s: SeqIdx -> elem]: lone elem { s[lastIdx[s]] } + +/** returns the cdr of the sequence */ +fun rest [s: SeqIdx -> elem] : SeqIdx -> elem { + (ord/next).s +} + +/** returns all but the last element of the sequence */ +fun butlast [s: SeqIdx -> elem] : SeqIdx -> elem { + (SeqIdx - lastIdx[s]) <: s +} + +/** true if the sequence is empty */ +pred isEmpty [s: SeqIdx -> elem] { no s } + +/** true if this sequence has duplicates */ +pred hasDups [s: SeqIdx -> elem] { # elems[s] < # inds[s] } + +/** returns all the indices occupied by this sequence */ +fun inds [s: SeqIdx -> elem]: set SeqIdx { s.elem } + +/** returns last index occupied by this sequence */ +fun lastIdx [s: SeqIdx -> elem]: lone SeqIdx { ord/max[inds[s]] } + +/** + * returns the index after the last index + * if this sequence is empty, returns the first index, + * if this sequence is full, returns empty set + */ +fun afterLastIdx [s: SeqIdx -> elem] : lone SeqIdx { + ord/min[SeqIdx - inds[s]] +} + +/** returns first index at which given element appears or the empty set if it doesn't */ +fun idxOf [s: SeqIdx -> elem, e: elem] : lone SeqIdx { ord/min[indsOf[s, e]] } + +/** returns last index at which given element appears or the empty set if it doesn't */ +fun lastIdxOf [s: SeqIdx -> elem, e: elem] : lone SeqIdx { ord/max[indsOf[s, e]] } + +/** returns set of indices at which given element appears or the empty set if it doesn't */ +fun indsOf [s: SeqIdx -> elem, e: elem] : set SeqIdx { s.e } + +/** + * return the result of appending e to the end of s + * just returns s if s exhausted SeqIdx + */ +fun add [s: SeqIdx -> elem, e: elem] : SeqIdx -> elem { + setAt[s, afterLastIdx[s], e] +} + +/** returns the result of setting the value at index i in sequence to e */ +fun setAt [s: SeqIdx -> elem, i: SeqIdx, e: elem] : SeqIdx -> elem { + s ++ i -> e +} + +/** returns the result of inserting value e at index i */ +fun insert [s: SeqIdx -> elem, i: SeqIdx, e: elem] : SeqIdx -> elem { + (ord/prevs[i] <: s) + (i->e) + (~(ord/next)).((ord/nexts[i] + i) <: s) +} + +/** returns the result of deleting the value at index i */ +fun delete[s: SeqIdx -> elem, i: SeqIdx] : SeqIdx -> elem { + (ord/prevs[i] <: s) + (ord/next).(ord/nexts[i] <: s) +} + +/** appended is the result of appending s2 to s1 */ +fun append [s1, s2: SeqIdx -> elem] : SeqIdx -> elem { + let shift = {i', i: SeqIdx | #ord/prevs[i'] = add[#ord/prevs[i], add[#ord/prevs[lastIdx[s1]], 1]] } | + s1 + shift.s2 +} + +/** returns the subsequence of s between from and to, inclusive */ +fun subseq [s: SeqIdx -> elem, from, to: SeqIdx] : SeqIdx -> elem { + let shift = {i', i: SeqIdx | #ord/prevs[i'] = sub[#ord/prevs[i], #ord/prevs[from]] } | + shift.((SeqIdx - ord/nexts[to]) <: s) +} + +fun firstIdx: SeqIdx { ord/first } + +fun finalIdx: SeqIdx { ord/last } \ No newline at end of file diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/util/sequence.als b/Source/eu.modelwriter.alloyanalyzer/src/models/util/sequence.als new file mode 100644 index 00000000..524e3cf7 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/util/sequence.als @@ -0,0 +1,166 @@ +module util/sequence[elem] + +/* + * Creates sequences (or lists) of elements. The ordered signature SeqIdx + * represents the indexes at which elements can be located, and a sequence + * is modeled as a mapping from indexes to elements. Empty sequences are + * allowed, and a sequence may have a single element appear multiple times. + * Maximum length of a sequence is determined by the scope of SeqIdx. + * + * Sequences always cover an initial segment of SeqIdx. That is, every + * sequence (except the empty sequence) begins at the first SeqIdx and does + * not have gaps in indexes that it covers. In other words, if a sequence has + * its last element at index i, then the sequence has elements at all the + * indexes that precede i. + * + * Oftentimes, a model will need to require that all sequences that could + * exist, do exist. Calling the allExist predicate will ensure that that there is + * a Seq atom for each possible sequences of elements with length less than + * or equal to the scope of SeqIdx. + * + * The functions and predicates at the bottom of this module provide all + * functionality of util/ordering on SeqIdx. + * + * revisions: Greg Dennis + */ + +open util/ordering[SeqIdx] as ord + +sig SeqIdx {} + +sig Seq { + seqElems: SeqIdx -> lone elem +} +{ + // Ensure that elems covers only an initial segment of SeqIdx, + // equal to the length of the signature + all i: SeqIdx - ord/first | some i.seqElems => some ord/prev[i].seqElems +} + +/** no two sequences are identical */ +fact canonicalizeSeqs { + no s1, s2: Seq | s1!=s2 && s1.seqElems=s2.seqElems +} + +/** invoke if you want none of the sequences to have duplicates */ +pred noDuplicates { + all s: Seq | !s.hasDups +} + +/** invoke if you want all sequences within scope to exist */ +pred allExist { + (some s: Seq | s.isEmpty) && + (all s: Seq | SeqIdx !in s.inds => (all e: elem | some s': Seq | s.add[e, s'])) +} + +/** invoke if you want all sequences within scope with no duplicates */ +pred allExistNoDuplicates { + some s: Seq | s.isEmpty + all s: Seq { + !s.hasDups + SeqIdx !in s.inds => (all e: elem - s.elems | some s': Seq | s.add[e, s']) + } +} + +/** returns element at the given index */ +fun at [s: Seq, i: SeqIdx]: lone elem { i.(s.seqElems) } + +/** returns all the elements in this sequence */ +fun elems [s: Seq]: set elem { SeqIdx.(s.seqElems) } + +/** returns the first element in the sequence */ +fun first [s:Seq]: lone elem { s.at[ord/first] } + +/** returns the last element in the sequence */ +fun last [s:Seq]: lone elem { s.at[s.lastIdx] } + +/** + * true if the argument is the "cdr" of this sequence + * false if this sequence is empty + */ +pred rest [s, r: Seq] { + !s.isEmpty + all i: SeqIdx | r.at[i] = s.at[ord/next[i]] +} + +/** true if the sequence is empty */ +pred isEmpty [s:Seq] { no s.elems } + +/** true if this sequence has duplicates */ +pred hasDups [s:Seq] { # elems[s] < # inds[s] } + +/** returns all the indices occupied by this sequence */ +fun inds [s:Seq] : set SeqIdx { elem.~(s.seqElems) } + +/** returns last index occupied by this sequence */ +fun lastIdx [s:Seq] : lone SeqIdx { ord/max[s.inds] } + +/** + * returns the index after the last index + * if this sequence is empty, returns the first index, + * if this sequence is full, returns empty set + */ +fun afterLastIdx [s:Seq] : lone SeqIdx { + ord/min[SeqIdx - s.inds] +} + +/** returns first index at which given element appears or the empty set if it doesn't */ +fun idxOf [s: Seq, e: elem] : lone SeqIdx { ord/min[s.indsOf[e]] } + +/** returns last index at which given element appears or the empty set if it doesn't */ +fun lastIdxOf [s: Seq, e: elem] : lone SeqIdx { ord/max[s.indsOf[e]] } + +/** returns set of indices at which given element appears or the empty set if it doesn't */ +fun indsOf [s: Seq, e: elem] : set SeqIdx { (s.seqElems).e } + +/** true if this starts with prefix */ +pred startsWith [s, prefix: Seq] { + all i: prefix.inds | s.at[i] = prefix.at[i] +} + +/** added is the result of appending e to the end of s */ +pred add [s: Seq, e: elem, added: Seq] { + added.startsWith[s] + added.seqElems[s.afterLastIdx] = e + #added.inds = #s.inds.add[1] +} + +/** setted is the result of setting value at index i to e */ +pred setAt [s: Seq, idx: SeqIdx, e: elem, setted: Seq] { + setted.seqElems = s.seqElems ++ idx->e +} + +/** inserts is the result of inserting value e at index i */ +pred insert [s: Seq, idx: SeqIdx, e: elem, inserted: Seq] { + inserted.at[idx] = e + all i: ord/prevs[idx] | inserted.at[i] = s.at[i] + all i: ord/nexts[idx] | inserted.at[i] = s.at[ord/prev[i]] + #inserted.inds = #s.inds.add[1] +} + +/** copies source into dest starting at destStart */ +pred copy [source, dest: Seq, destStart: SeqIdx] { + all sourceIdx : source.inds | some destIdx: SeqIdx { + ord/gte[destIdx, destStart] + dest.at[destIdx] = source.at[sourceIdx] + #ord/prevs[sourceIdx] = #(ord/prevs[destIdx] - ord/prevs[destStart]) + } +} + +/** appended is the result of appending s2 to s1 */ +pred append [s1, s2, appended: Seq] { + appended.startsWith[s1] + copy[s2, appended, s1.afterLastIdx] + #appended.inds = #s1.inds.add[#s2.inds] +} + +/** sub is the subsequence of s between from and to, inclusive */ +pred subseq [s, sub: Seq, from, to: SeqIdx] { + ord/lte[from, to] + copy[sub, s, from] + #sub.inds = #(to + ord/prevs[to] - ord/prevs[from]) +} + +fun firstIdx: SeqIdx { ord/first } + +fun finalIdx: SeqIdx { ord/last } diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/util/sequniv.als b/Source/eu.modelwriter.alloyanalyzer/src/models/util/sequniv.als new file mode 100644 index 00000000..efa8bf42 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/util/sequniv.als @@ -0,0 +1,137 @@ +module util/sequniv + +open util/integer as ui + +/* + * NOTE: Do not include this module manually. + * Instead, use the "seq" keyword which will automatically + * import this module with the correct additional constraints as needed. + */ + +/* + * A sequence utility for modeling sequences as just a + * relation as opposed to reifying them into sequence + * atoms like the util/sequence module does. + * + * Precondition: each input sequence must range over a prefix + * of seq/Int. + * + * Postcondition: we guarantee the returned sequence + * also ranges over a prefix of seq/Int. + * + * @author Greg Dennis + */ + +/** sequence covers a prefix of seq/Int */ +pred isSeq[s: Int -> univ] { + s in seq/Int -> lone univ + s.inds - ui/next[s.inds] in 0 +} + +/** returns all the elements in this sequence */ +fun elems [s: Int -> univ]: set (Int.s) { seq/Int . s } + +/** + * returns the first element in the sequence + * (Returns the empty set if the sequence is empty) + */ +fun first [s: Int -> univ]: lone (Int.s) { s[0] } + +/** + * returns the last element in the sequence + * (Returns the empty set if the sequence is empty) + */ +fun last [s: Int -> univ]: lone (Int.s) { s[lastIdx[s]] } + +/** + * returns the cdr of the sequence + * (Returns the empty sequence if the sequence has 1 or fewer element) + */ +fun rest [s: Int -> univ] : s { seq/Int <: ((ui/next).s) } + +/** returns all but the last element of the sequence */ +fun butlast [s: Int -> univ] : s { + (seq/Int - lastIdx[s]) <: s +} + +/** true if the sequence is empty */ +pred isEmpty [s: Int -> univ] { no s } + +/** true if this sequence has duplicates */ +pred hasDups [s: Int -> univ] { # elems[s] < # inds[s] } + +/** returns all the indices occupied by this sequence */ +fun inds [s: Int -> univ]: set Int { s.univ } + +/** + * returns last index occupied by this sequence + * (Returns the empty set if the sequence is empty) + */ +fun lastIdx [s: Int -> univ]: lone Int { ui/max[inds[s]] } + +/** + * returns the index after the last index + * if this sequence is empty, returns 0 + * if this sequence is full, returns empty set + */ +fun afterLastIdx [s: Int -> univ] : lone Int { ui/min[seq/Int - inds[s]] } + +/** returns first index at which given element appears or the empty set if it doesn't */ +fun idxOf [s: Int -> univ, e: univ] : lone Int { ui/min[indsOf[s, e]] } + +/** returns last index at which given element appears or the empty set if it doesn't */ +fun lastIdxOf [s: Int -> univ, e: univ] : lone Int { ui/max[indsOf[s, e]] } + +/** returns set of indices at which given element appears or the empty set if it doesn't */ +fun indsOf [s: Int -> univ, e: univ] : set Int { s.e } + +/** + * return the result of appending e to the end of s + * (returns s if s exhausted seq/Int) + */ +fun add [s: Int -> univ, e: univ] : s + (seq/Int->e) { + setAt[s, afterLastIdx[s], e] +} + +/** + * returns the result of setting the value at index i in sequence to e + * Precondition: 0 <= i < #s + */ +fun setAt [s: Int -> univ, i: Int, e: univ] : s + (seq/Int->e) { + s ++ i -> e +} + +/** + * returns the result of inserting value e at index i + * (if sequence was full, the original last element will be removed first) + * Precondition: 0 <= i <= #s + */ +fun insert [s: Int -> univ, i: Int, e: univ] : s + (seq/Int->e) { + seq/Int <: ((ui/prevs[i] <: s) + (i->e) + ui/prev.((ui/nexts[i] + i) <: s)) +} + +/** + * returns the result of deleting the value at index i + * Precondition: 0 <= i < #s + */ +fun delete[s: Int -> univ, i: Int] : s { + (ui/prevs[i] <: s) + (ui/next).(ui/nexts[i] <: s) +} + +/** + * appended is the result of appending s2 to s1 + * (If the resulting sequence is too long, it will be truncated) + */ +fun append [s1, s2: Int -> univ] : s1+s2 { + let shift = {i', i: seq/Int | int[i'] = ui/add[int[i], ui/add[int[lastIdx[s1]], 1]] } | + no s1 => s2 else (s1 + shift.s2) +} + +/** + * returns the subsequence of s between from and to, inclusive + * Precondition: 0 <= from <= to < #s + */ +fun subseq [s: Int -> univ, from, to: Int] : s { + let shift = {i', i: seq/Int | int[i'] = ui/sub[int[i], int[from]] } | + shift.((seq/Int - ui/nexts[to]) <: s) +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/util/ternary.als b/Source/eu.modelwriter.alloyanalyzer/src/models/util/ternary.als new file mode 100644 index 00000000..6dbc0881 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/util/ternary.als @@ -0,0 +1,50 @@ +module util/ternary + +/* + * Utilities for some common operations and constraints + * on ternary relations. The keyword 'univ' represents the + * top-level type, which all other types implicitly extend. + * Therefore, all the functions and predicates in this model + * may be applied to ternary relations of any type. + * + * author: Greg Dennis + */ + +/** returns the domain of a ternary relation */ +fun dom [r: univ->univ->univ] : set ((r.univ).univ) { (r.univ).univ } + +/** returns the range of a ternary relation */ +fun ran [r: univ->univ->univ] : set (univ.(univ.r)) { univ.(univ.r) } + +/** returns the "middle range" of a ternary relation */ +fun mid [r: univ->univ->univ] : set (univ.(r.univ)) { univ.(r.univ) } + +/** returns the first two columns of a ternary relation */ +fun select12 [r: univ->univ->univ] : r.univ { + r.univ +} + +/** returns the first and last columns of a ternary relation */ +fun select13 [r: univ->univ->univ] : ((r.univ).univ) -> (univ.(univ.r)) { + {x: (r.univ).univ, z: univ.(univ.r) | some (x.r).z} +} + +/** returns the last two columns of a ternary relation */ +fun select23 [r: univ->univ->univ] : univ.r { + univ.r +} + +/** flips the first two columns of a ternary relation */ +fun flip12 [r: univ->univ->univ] : (univ.(r.univ))->((r.univ).univ)->(univ.(univ.r)) { + {x: univ.(r.univ), y: (r.univ).univ, z: univ.(univ.r) | y->x->z in r} +} + +/** flips the first and last columns of a ternary relation */ +fun flip13 [r: univ->univ->univ] : (univ.(univ.r))->(univ.(r.univ))->((r.univ).univ) { + {x: univ.(univ.r), y: univ.(r.univ), z: (r.univ).univ | z->y->x in r} +} + +/** flips the last two columns of a ternary relation */ +fun flip23 [r: univ->univ->univ] : ((r.univ).univ)->(univ.(univ.r))->(univ.(r.univ)) { + {x: (r.univ).univ, y: univ.(univ.r), z: univ.(r.univ) | x->z->y in r} +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/models/util/time.als b/Source/eu.modelwriter.alloyanalyzer/src/models/util/time.als new file mode 100644 index 00000000..b6f9d8ca --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/models/util/time.als @@ -0,0 +1,53 @@ +open util/ordering[Time] + +sig Time { } + +let dynamic[x] = x one-> Time + +let dynamicSet[x] = x -> Time + +let then [a, b, t, t'] { + some x:Time | a[t,x] && b[x,t'] +} + +let while = while3 + +let while9 [cond, body, t, t'] { + some x:Time | (cond[t] => body[t,x] else t=x) && while8[cond,body,x,t'] +} + +let while8 [cond, body, t, t'] { + some x:Time | (cond[t] => body[t,x] else t=x) && while7[cond,body,x,t'] +} + +let while7 [cond, body, t, t'] { + some x:Time | (cond[t] => body[t,x] else t=x) && while6[cond,body,x,t'] +} + +let while6 [cond, body, t, t'] { + some x:Time | (cond[t] => body[t,x] else t=x) && while5[cond,body,x,t'] +} + +let while5 [cond, body, t, t'] { + some x:Time | (cond[t] => body[t,x] else t=x) && while4[cond,body,x,t'] +} + +let while4 [cond, body, t, t'] { + some x:Time | (cond[t] => body[t,x] else t=x) && while3[cond,body,x,t'] +} + +let while3 [cond, body, t, t'] { + some x:Time | (cond[t] => body[t,x] else t=x) && while2[cond,body,x,t'] +} + +let while2 [cond, body, t, t'] { + some x:Time | (cond[t] => body[t,x] else t=x) && while1[cond,body,x,t'] +} + +let while1 [cond, body, t, t'] { + some x:Time | (cond[t] => body[t,x] else t=x) && while0[cond,body,x,t'] +} + +let while0 [cond, body, t, t'] { + !cond[t] && t=t' +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/org/sat4j/messages.properties b/Source/eu.modelwriter.alloyanalyzer/src/org/sat4j/messages.properties new file mode 100644 index 00000000..2b3f0f00 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/org/sat4j/messages.properties @@ -0,0 +1,8 @@ +Lanceur.wrong.framework=Wrong framework: try minisat or ubcsat +MoreThanSAT.0=Satisfiable \! +MoreThanSAT.1=BackBone: +MoreThanSAT.2=Counting solutions... +MoreThanSAT.3=Number of solutions: +MoreThanSAT.4=Unsatisfiable\! +MoreThanSAT.5=Unsatisfiable (trivial)\! +MoreThanSAT.6=Timeout, sorry\! diff --git a/Source/eu.modelwriter.alloyanalyzer/src/overview.html b/Source/eu.modelwriter.alloyanalyzer/src/overview.html new file mode 100644 index 00000000..0fd29581 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/overview.html @@ -0,0 +1,33 @@ + +SAT4J: a SATisfiability library for Java. + +
+/*******************************************************************************
+* SAT4J: a SATisfiability library for Java Copyright (C) 2004-2008 Daniel Le Berre
+*
+* All rights reserved. This program and the accompanying materials
+* are made available under the terms of the Eclipse Public License v1.0
+* which accompanies this distribution, and is available at
+* http://www.eclipse.org/legal/epl-v10.html
+*
+* Alternatively, the contents of this file may be used under the terms of
+* either the GNU Lesser General Public License Version 2.1 or later (the
+* "LGPL"), in which case the provisions of the LGPL are applicable instead
+* of those above. If you wish to allow use of your version of this file only
+* under the terms of the LGPL, and not to allow others to use your version of
+* this file under the terms of the EPL, indicate your decision by deleting
+* the provisions above and replace them with the notice and other provisions
+* required by the LGPL. If you do not delete the provisions above, a recipient
+* may use your version of this file under the terms of the EPL or the LGPL.
+* 
+* Based on the original MiniSat specification from:
+* 
+* An extensible SAT solver. Niklas Een and Niklas Sorensson. Proceedings of the
+* Sixth International Conference on Theory and Applications of Satisfiability
+* Testing, LNCS 2919, pp 502-518, 2003.
+*
+* See www.minisat.se for the original solver in C++.
+* 
+*******************************************************************************/
+
+ \ No newline at end of file diff --git a/Source/eu.modelwriter.alloyanalyzer/src/sat4j.version b/Source/eu.modelwriter.alloyanalyzer/src/sat4j.version new file mode 100644 index 00000000..a27f8f26 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/sat4j.version @@ -0,0 +1 @@ +2.3.2.v20120709 diff --git a/Source/eu.modelwriter.alloyanalyzer/src/target/META-INF/MANIFEST.MF b/Source/eu.modelwriter.alloyanalyzer/src/target/META-INF/MANIFEST.MF new file mode 100644 index 00000000..f14a9926 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/target/META-INF/MANIFEST.MF @@ -0,0 +1,31 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: %bundleName +Bundle-SymbolicName: org.sat4j.core +Bundle-Version: 2.3.2.v20120709 +Export-Package: org.sat4j;version="2.3.2.v20120709", + org.sat4j.core;version="2.3.2.v20120709", + org.sat4j.minisat;version="2.3.2.v20120709", + org.sat4j.minisat.constraints;version="2.3.2.v20120709", + org.sat4j.minisat.constraints.card;version="2.3.2.v20120709", + org.sat4j.minisat.constraints.cnf;version="2.3.2.v20120709", + org.sat4j.minisat.core;version="2.3.2.v20120709", + org.sat4j.minisat.learning;version="2.3.2.v20120709", + org.sat4j.minisat.orders;version="2.3.2.v20120709", + org.sat4j.minisat.restarts;version="2.3.2.v20120709", + org.sat4j.opt;version="2.3.2.v20120709", + org.sat4j.reader;version="2.3.2.v20120709", + org.sat4j.specs;version="2.3.2.v20120709", + org.sat4j.tools;version="2.3.2.v20120709", + org.sat4j.tools.xplain;version="2.3.2.v20120709" +Bundle-Vendor: %providerName +Bundle-Localization: plugin +Built-By: Daniel Le Berre +Main-Class: org.sat4j.BasicLauncher +Specification-Title: SAT4J +Specification-Version: NA +Specification-Vendor: Daniel Le Berre +Implementation-Title: SAT4J +Implementation-Version: 2.3.2.v20120709 +Implementation-Vendor: CRIL CNRS UMR 8188 - Universite d'Artois +Bundle-RequiredExecutionEnvironment: J2SE-1.5 diff --git a/Source/eu.modelwriter.alloyanalyzer/src/tmp/EvaluatorExample.java b/Source/eu.modelwriter.alloyanalyzer/src/tmp/EvaluatorExample.java new file mode 100644 index 00000000..24e16a2b --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/tmp/EvaluatorExample.java @@ -0,0 +1,113 @@ +package tmp; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; + +import edu.mit.csail.sdg.alloy4.A4Reporter; +import edu.mit.csail.sdg.alloy4.XMLNode; +import edu.mit.csail.sdg.alloy4compiler.ast.Command; +import edu.mit.csail.sdg.alloy4compiler.ast.Expr; +import edu.mit.csail.sdg.alloy4compiler.ast.Module; +import edu.mit.csail.sdg.alloy4compiler.parser.CompUtil; +import edu.mit.csail.sdg.alloy4compiler.translator.A4Options; +import edu.mit.csail.sdg.alloy4compiler.translator.A4Solution; +import edu.mit.csail.sdg.alloy4compiler.translator.A4SolutionReader; +import edu.mit.csail.sdg.alloy4compiler.translator.TranslateAlloyToKodkod; + +public class EvaluatorExample { + + private static String model = + "sig Point {} \n sig File { r: set Point } \n" + "\n" + "run { } for 1"; + private static String outputfilename = "C:\\Users\\3\\Desktop\\Analyzerrr\\edu\\tmp\\myissue.xml"; + + public static void main(String[] args) throws Exception { + A4Reporter rep = new A4Reporter(); + File tmpAls = File.createTempFile("alloyEvaluator", ".als"); + tmpAls.deleteOnExit(); + flushModelToFile(tmpAls); + { + Module world = CompUtil.parseEverything_fromFile(rep, null, tmpAls.getAbsolutePath()); + A4Options opt = new A4Options(); + opt.originalFilename = tmpAls.getAbsolutePath(); + opt.solver = A4Options.SatSolver.SAT4J; + Command cmd = world.getAllCommands().get(0); + A4Solution sol = TranslateAlloyToKodkod.execute_commandFromBook(rep, + world.getAllReachableSigs(), cmd, opt); + assert sol.satisfiable(); + sol.writeXML(outputfilename); + + // eval with existing A4Solution + Expr e = CompUtil.parseOneExpression_fromString(world, "univ"); + System.out.println(sol.eval(e)); + e = CompUtil.parseOneExpression_fromString(world, "Point"); + System.out.println(sol.eval(e)); + } + // reload everything from files + { + XMLNode xmlNode = new XMLNode(new File(outputfilename)); + String alloySourceFilename = xmlNode.iterator().next().getAttribute("filename"); + Module ansWorld = CompUtil.parseEverything_fromFile(rep, null, alloySourceFilename); + A4Solution ans = A4SolutionReader.read(ansWorld.getAllReachableSigs(), xmlNode); + + Expr e = CompUtil.parseOneExpression_fromString(ansWorld, "univ"); + System.out.println(ans.eval(e)); + e = CompUtil.parseOneExpression_fromString(ansWorld, "Point"); + System.out.println(ans.eval(e)); + } + } + + private static void flushModelToFile(File tmpAls) throws IOException { + BufferedOutputStream bos = null; + try { + bos = new BufferedOutputStream(new FileOutputStream(tmpAls)); + bos.write(model.getBytes()); + bos.flush(); + } finally { + if (bos != null) + bos.close(); + } + } + + + // private static String model = + // "sig Point {} \n" + + // "\n" + + // "run { #Point > 1 } for 3 but 3 Int"; + // + // public static void main(String[] args) throws Exception { + // A4Reporter rep = new A4Reporter(); + // File tmpAls = File.createTempFile("alloyEvaluator", ".als"); + // tmpAls.deleteOnExit(); + // flushModelToFile(tmpAls); + // Module world = CompUtil.parseEverything_fromFile(rep, null, tmpAls.getAbsolutePath()); + // A4Options opt = new A4Options(); + // opt.originalFilename = tmpAls.getAbsolutePath(); + // opt.solver = A4Options.SatSolver.SAT4J; + // Command cmd = world.getAllCommands().get(0); + // A4Solution sol = TranslateAlloyToKodkod.execute_commandFromBook(rep, + // world.getAllReachableSigs(), cmd, opt); + // assert sol.satisfiable(); + // + // // eval: univ + // Expr e = CompUtil.parseOneExpression_fromString(world, "univ"); + // System.out.println(sol.eval(e)); + // + // // eval: Point + // e = CompUtil.parseOneExpression_fromString(world, "Point"); + // System.out.println(sol.eval(e)); + // } + // + // private static void flushModelToFile(File tmpAls) throws IOException { + // BufferedOutputStream bos = null; + // try { + // bos = new BufferedOutputStream(new FileOutputStream(tmpAls)); + // bos.write(model.getBytes()); + // bos.flush(); + // } finally { + // if (bos != null) bos.close(); + // } + // } + +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/tmp/KK.java b/Source/eu.modelwriter.alloyanalyzer/src/tmp/KK.java new file mode 100644 index 00000000..92f910f5 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/tmp/KK.java @@ -0,0 +1,76 @@ +package tmp; +import java.util.LinkedList; +import java.util.List; + +import kodkod.ast.Formula; +import kodkod.ast.IntConstant; +import kodkod.ast.IntExpression; +import kodkod.ast.Relation; +import kodkod.engine.Evaluator; +import kodkod.engine.Solution; +import kodkod.engine.Solver; +import kodkod.engine.config.Options; +import kodkod.engine.satlab.SATFactory; +import kodkod.instance.Bounds; +import kodkod.instance.Instance; +import kodkod.instance.TupleFactory; +import kodkod.instance.TupleSet; +import kodkod.instance.Universe; +import kodkod.util.nodes.PrettyPrinter; + +public final class KK { + + public static void main(String[] args) throws Exception { + + Relation x6 = Relation.unary("R"); + int[] ints = new int[] {0, 1, 2}; + + List atomlist = new LinkedList(); + atomlist.add("R$0"); + atomlist.add("R$1"); + atomlist.add("R$2"); + for (int i : ints) atomlist.add(i); + + Universe universe = new Universe(atomlist); + TupleFactory factory = universe.factory(); + Bounds bounds = new Bounds(universe); + + TupleSet x6_upper = factory.noneOf(1); + x6_upper.add(factory.tuple("R$0")); + x6_upper.add(factory.tuple("R$1")); + x6_upper.add(factory.tuple("R$2")); + bounds.bound(x6, x6_upper); + + for (int i : ints) { + bounds.boundExactly(i, factory.setOf(i)); + } + + Formula x11 = x6.some(); + IntExpression x5 = x6.count(); + Formula x9 = x11.implies(x5.gt(IntConstant.constant(0))); + Formula x7 = x9.not(); + + Solver solver = new Solver(); + solver.options().setSolver(SATFactory.DefaultSAT4J); + solver.options().setBitwidth(2); + solver.options().setFlatten(false); + solver.options().setIntEncoding(Options.IntEncoding.TWOSCOMPLEMENT); + solver.options().setSymmetryBreaking(20); + solver.options().setSkolemDepth(0); + System.out.println("Solving..."); + System.out.println(PrettyPrinter.print(x7, 0)); + System.out.println(bounds); + Solution sol = solver.solve(x7, bounds); + System.out.println(sol.toString()); + + Instance inst = sol.instance(); + Evaluator ev = new Evaluator(inst); + System.out.println(ev.evaluate(x6.some())); + System.out.println(ev.evaluate(x5)); + System.out.println(ev.evaluate(x5.gt(IntConstant.constant(0)))); + System.out.println(ev.evaluate(x6.some().implies(x5.gt(IntConstant.constant(0))).not())); + System.out.println(ev.evaluate(x7)); + + + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/tmp/Ok.java b/Source/eu.modelwriter.alloyanalyzer/src/tmp/Ok.java new file mode 100644 index 00000000..49b97aef --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/tmp/Ok.java @@ -0,0 +1,2229 @@ +package tmp; +import java.util.Arrays; +import java.util.List; +import kodkod.ast.*; +import kodkod.ast.operator.*; +import kodkod.instance.*; +import kodkod.engine.*; +import kodkod.engine.satlab.SATFactory; +import kodkod.engine.config.Options; + +public final class Ok { + + public static void main(String[] args) throws Exception { + + Relation x0 = Relation.unary("Int/min"); + Relation x1 = Relation.unary("Int/zero"); + Relation x2 = Relation.unary("Int/max"); + Relation x3 = Relation.nary("Int/next", 2); + Relation x4 = Relation.unary("seq/Int"); + Relation x5 = Relation.unary("String"); + Relation x6 = Relation.unary("this/A"); + Relation x7 = Relation.unary("this/B"); + Relation x8 = Relation.unary("this/Relation1"); + Relation x9 = Relation.unary("this/Relation2"); + Relation x10 = Relation.unary("this/Relation3"); + Relation x11 = Relation.unary("this/Relation4"); + Relation x12 = Relation.nary("this/Relation1.r", 3); + Relation x13 = Relation.nary("this/Relation2.r", 3); + Relation x14 = Relation.nary("this/Relation3.r", 3); + Relation x15 = Relation.nary("this/Relation4.r", 3); + + List atomlist = Arrays.asList("-1", "-2", "-3", "-4", "-5", "-6", "-7", "-8", "0", + "1", "2", "3", "4", "5", "6", "7", "A$0", "B$0", "Relation1$0", "Relation2$0", + "Relation3$0", "Relation4$0"); + + Universe universe = new Universe(atomlist); + TupleFactory factory = universe.factory(); + Bounds bounds = new Bounds(universe); + + TupleSet x0_upper = factory.noneOf(1); + x0_upper.add(factory.tuple("-8")); + bounds.boundExactly(x0, x0_upper); + + TupleSet x1_upper = factory.noneOf(1); + x1_upper.add(factory.tuple("0")); + bounds.boundExactly(x1, x1_upper); + + TupleSet x2_upper = factory.noneOf(1); + x2_upper.add(factory.tuple("7")); + bounds.boundExactly(x2, x2_upper); + + TupleSet x3_upper = factory.noneOf(2); + x3_upper.add(factory.tuple("-8").product(factory.tuple("-7"))); + x3_upper.add(factory.tuple("-7").product(factory.tuple("-6"))); + x3_upper.add(factory.tuple("-6").product(factory.tuple("-5"))); + x3_upper.add(factory.tuple("-5").product(factory.tuple("-4"))); + x3_upper.add(factory.tuple("-4").product(factory.tuple("-3"))); + x3_upper.add(factory.tuple("-3").product(factory.tuple("-2"))); + x3_upper.add(factory.tuple("-2").product(factory.tuple("-1"))); + x3_upper.add(factory.tuple("-1").product(factory.tuple("0"))); + x3_upper.add(factory.tuple("0").product(factory.tuple("1"))); + x3_upper.add(factory.tuple("1").product(factory.tuple("2"))); + x3_upper.add(factory.tuple("2").product(factory.tuple("3"))); + x3_upper.add(factory.tuple("3").product(factory.tuple("4"))); + x3_upper.add(factory.tuple("4").product(factory.tuple("5"))); + x3_upper.add(factory.tuple("5").product(factory.tuple("6"))); + x3_upper.add(factory.tuple("6").product(factory.tuple("7"))); + bounds.boundExactly(x3, x3_upper); + + TupleSet x4_upper = factory.noneOf(1); + x4_upper.add(factory.tuple("0")); + bounds.boundExactly(x4, x4_upper); + + TupleSet x5_upper = factory.noneOf(1); + bounds.boundExactly(x5, x5_upper); + + TupleSet x6_upper = factory.noneOf(1); + x6_upper.add(factory.tuple("A$0")); + bounds.bound(x6, x6_upper); + + TupleSet x7_upper = factory.noneOf(1); + x7_upper.add(factory.tuple("B$0")); + bounds.bound(x7, x7_upper); + + TupleSet x8_upper = factory.noneOf(1); + x8_upper.add(factory.tuple("Relation1$0")); + bounds.bound(x8, x8_upper); + + TupleSet x9_upper = factory.noneOf(1); + x9_upper.add(factory.tuple("Relation2$0")); + bounds.bound(x9, x9_upper); + + TupleSet x10_upper = factory.noneOf(1); + x10_upper.add(factory.tuple("Relation3$0")); + bounds.bound(x10, x10_upper); + + TupleSet x11_upper = factory.noneOf(1); + x11_upper.add(factory.tuple("Relation4$0")); + bounds.bound(x11, x11_upper); + + TupleSet x12_upper = factory.noneOf(3); + x12_upper.add(factory.tuple("Relation1$0").product(factory.tuple("A$0")).product( + factory.tuple("A$0"))); + x12_upper.add(factory.tuple("Relation1$0").product(factory.tuple("A$0")).product( + factory.tuple("B$0"))); + x12_upper.add(factory.tuple("Relation1$0").product(factory.tuple("B$0")).product( + factory.tuple("A$0"))); + x12_upper.add(factory.tuple("Relation1$0").product(factory.tuple("B$0")).product( + factory.tuple("B$0"))); + bounds.bound(x12, x12_upper); + + TupleSet x13_upper = factory.noneOf(3); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("A$0")).product( + factory.tuple("A$0"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("A$0")).product( + factory.tuple("B$0"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("A$0")).product( + factory.tuple("-8"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("A$0")).product( + factory.tuple("-7"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("A$0")).product( + factory.tuple("-6"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("A$0")).product( + factory.tuple("-5"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("A$0")).product( + factory.tuple("-4"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("A$0")).product( + factory.tuple("-3"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("A$0")).product( + factory.tuple("-2"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("A$0")).product( + factory.tuple("-1"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("A$0")).product( + factory.tuple("0"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("A$0")).product( + factory.tuple("1"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("A$0")).product( + factory.tuple("2"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("A$0")).product( + factory.tuple("3"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("A$0")).product( + factory.tuple("4"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("A$0")).product( + factory.tuple("5"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("A$0")).product( + factory.tuple("6"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("A$0")).product( + factory.tuple("7"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("B$0")).product( + factory.tuple("A$0"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("B$0")).product( + factory.tuple("B$0"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("B$0")).product( + factory.tuple("-8"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("B$0")).product( + factory.tuple("-7"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("B$0")).product( + factory.tuple("-6"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("B$0")).product( + factory.tuple("-5"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("B$0")).product( + factory.tuple("-4"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("B$0")).product( + factory.tuple("-3"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("B$0")).product( + factory.tuple("-2"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("B$0")).product( + factory.tuple("-1"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("B$0")).product( + factory.tuple("0"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("B$0")).product( + factory.tuple("1"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("B$0")).product( + factory.tuple("2"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("B$0")).product( + factory.tuple("3"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("B$0")).product( + factory.tuple("4"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("B$0")).product( + factory.tuple("5"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("B$0")).product( + factory.tuple("6"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("B$0")).product( + factory.tuple("7"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-8")).product( + factory.tuple("A$0"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-8")).product( + factory.tuple("B$0"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-8")).product( + factory.tuple("-8"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-8")).product( + factory.tuple("-7"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-8")).product( + factory.tuple("-6"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-8")).product( + factory.tuple("-5"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-8")).product( + factory.tuple("-4"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-8")).product( + factory.tuple("-3"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-8")).product( + factory.tuple("-2"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-8")).product( + factory.tuple("-1"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-8")).product( + factory.tuple("0"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-8")).product( + factory.tuple("1"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-8")).product( + factory.tuple("2"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-8")).product( + factory.tuple("3"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-8")).product( + factory.tuple("4"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-8")).product( + factory.tuple("5"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-8")).product( + factory.tuple("6"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-8")).product( + factory.tuple("7"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-7")).product( + factory.tuple("A$0"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-7")).product( + factory.tuple("B$0"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-7")).product( + factory.tuple("-8"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-7")).product( + factory.tuple("-7"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-7")).product( + factory.tuple("-6"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-7")).product( + factory.tuple("-5"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-7")).product( + factory.tuple("-4"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-7")).product( + factory.tuple("-3"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-7")).product( + factory.tuple("-2"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-7")).product( + factory.tuple("-1"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-7")).product( + factory.tuple("0"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-7")).product( + factory.tuple("1"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-7")).product( + factory.tuple("2"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-7")).product( + factory.tuple("3"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-7")).product( + factory.tuple("4"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-7")).product( + factory.tuple("5"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-7")).product( + factory.tuple("6"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-7")).product( + factory.tuple("7"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-6")).product( + factory.tuple("A$0"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-6")).product( + factory.tuple("B$0"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-6")).product( + factory.tuple("-8"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-6")).product( + factory.tuple("-7"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-6")).product( + factory.tuple("-6"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-6")).product( + factory.tuple("-5"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-6")).product( + factory.tuple("-4"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-6")).product( + factory.tuple("-3"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-6")).product( + factory.tuple("-2"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-6")).product( + factory.tuple("-1"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-6")).product( + factory.tuple("0"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-6")).product( + factory.tuple("1"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-6")).product( + factory.tuple("2"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-6")).product( + factory.tuple("3"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-6")).product( + factory.tuple("4"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-6")).product( + factory.tuple("5"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-6")).product( + factory.tuple("6"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-6")).product( + factory.tuple("7"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-5")).product( + factory.tuple("A$0"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-5")).product( + factory.tuple("B$0"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-5")).product( + factory.tuple("-8"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-5")).product( + factory.tuple("-7"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-5")).product( + factory.tuple("-6"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-5")).product( + factory.tuple("-5"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-5")).product( + factory.tuple("-4"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-5")).product( + factory.tuple("-3"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-5")).product( + factory.tuple("-2"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-5")).product( + factory.tuple("-1"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-5")).product( + factory.tuple("0"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-5")).product( + factory.tuple("1"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-5")).product( + factory.tuple("2"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-5")).product( + factory.tuple("3"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-5")).product( + factory.tuple("4"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-5")).product( + factory.tuple("5"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-5")).product( + factory.tuple("6"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-5")).product( + factory.tuple("7"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-4")).product( + factory.tuple("A$0"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-4")).product( + factory.tuple("B$0"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-4")).product( + factory.tuple("-8"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-4")).product( + factory.tuple("-7"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-4")).product( + factory.tuple("-6"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-4")).product( + factory.tuple("-5"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-4")).product( + factory.tuple("-4"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-4")).product( + factory.tuple("-3"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-4")).product( + factory.tuple("-2"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-4")).product( + factory.tuple("-1"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-4")).product( + factory.tuple("0"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-4")).product( + factory.tuple("1"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-4")).product( + factory.tuple("2"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-4")).product( + factory.tuple("3"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-4")).product( + factory.tuple("4"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-4")).product( + factory.tuple("5"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-4")).product( + factory.tuple("6"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-4")).product( + factory.tuple("7"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-3")).product( + factory.tuple("A$0"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-3")).product( + factory.tuple("B$0"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-3")).product( + factory.tuple("-8"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-3")).product( + factory.tuple("-7"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-3")).product( + factory.tuple("-6"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-3")).product( + factory.tuple("-5"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-3")).product( + factory.tuple("-4"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-3")).product( + factory.tuple("-3"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-3")).product( + factory.tuple("-2"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-3")).product( + factory.tuple("-1"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-3")).product( + factory.tuple("0"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-3")).product( + factory.tuple("1"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-3")).product( + factory.tuple("2"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-3")).product( + factory.tuple("3"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-3")).product( + factory.tuple("4"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-3")).product( + factory.tuple("5"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-3")).product( + factory.tuple("6"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-3")).product( + factory.tuple("7"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-2")).product( + factory.tuple("A$0"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-2")).product( + factory.tuple("B$0"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-2")).product( + factory.tuple("-8"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-2")).product( + factory.tuple("-7"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-2")).product( + factory.tuple("-6"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-2")).product( + factory.tuple("-5"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-2")).product( + factory.tuple("-4"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-2")).product( + factory.tuple("-3"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-2")).product( + factory.tuple("-2"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-2")).product( + factory.tuple("-1"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-2")).product( + factory.tuple("0"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-2")).product( + factory.tuple("1"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-2")).product( + factory.tuple("2"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-2")).product( + factory.tuple("3"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-2")).product( + factory.tuple("4"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-2")).product( + factory.tuple("5"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-2")).product( + factory.tuple("6"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-2")).product( + factory.tuple("7"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-1")).product( + factory.tuple("A$0"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-1")).product( + factory.tuple("B$0"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-1")).product( + factory.tuple("-8"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-1")).product( + factory.tuple("-7"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-1")).product( + factory.tuple("-6"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-1")).product( + factory.tuple("-5"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-1")).product( + factory.tuple("-4"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-1")).product( + factory.tuple("-3"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-1")).product( + factory.tuple("-2"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-1")).product( + factory.tuple("-1"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-1")).product( + factory.tuple("0"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-1")).product( + factory.tuple("1"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-1")).product( + factory.tuple("2"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-1")).product( + factory.tuple("3"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-1")).product( + factory.tuple("4"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-1")).product( + factory.tuple("5"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-1")).product( + factory.tuple("6"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("-1")).product( + factory.tuple("7"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("0")).product( + factory.tuple("A$0"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("0")).product( + factory.tuple("B$0"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("0")).product( + factory.tuple("-8"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("0")).product( + factory.tuple("-7"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("0")).product( + factory.tuple("-6"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("0")).product( + factory.tuple("-5"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("0")).product( + factory.tuple("-4"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("0")).product( + factory.tuple("-3"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("0")).product( + factory.tuple("-2"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("0")).product( + factory.tuple("-1"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("0")).product( + factory.tuple("0"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("0")).product( + factory.tuple("1"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("0")).product( + factory.tuple("2"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("0")).product( + factory.tuple("3"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("0")).product( + factory.tuple("4"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("0")).product( + factory.tuple("5"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("0")).product( + factory.tuple("6"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("0")).product( + factory.tuple("7"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("1")).product( + factory.tuple("A$0"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("1")).product( + factory.tuple("B$0"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("1")).product( + factory.tuple("-8"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("1")).product( + factory.tuple("-7"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("1")).product( + factory.tuple("-6"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("1")).product( + factory.tuple("-5"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("1")).product( + factory.tuple("-4"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("1")).product( + factory.tuple("-3"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("1")).product( + factory.tuple("-2"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("1")).product( + factory.tuple("-1"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("1")).product( + factory.tuple("0"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("1")).product( + factory.tuple("1"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("1")).product( + factory.tuple("2"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("1")).product( + factory.tuple("3"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("1")).product( + factory.tuple("4"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("1")).product( + factory.tuple("5"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("1")).product( + factory.tuple("6"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("1")).product( + factory.tuple("7"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("2")).product( + factory.tuple("A$0"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("2")).product( + factory.tuple("B$0"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("2")).product( + factory.tuple("-8"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("2")).product( + factory.tuple("-7"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("2")).product( + factory.tuple("-6"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("2")).product( + factory.tuple("-5"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("2")).product( + factory.tuple("-4"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("2")).product( + factory.tuple("-3"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("2")).product( + factory.tuple("-2"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("2")).product( + factory.tuple("-1"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("2")).product( + factory.tuple("0"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("2")).product( + factory.tuple("1"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("2")).product( + factory.tuple("2"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("2")).product( + factory.tuple("3"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("2")).product( + factory.tuple("4"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("2")).product( + factory.tuple("5"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("2")).product( + factory.tuple("6"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("2")).product( + factory.tuple("7"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("3")).product( + factory.tuple("A$0"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("3")).product( + factory.tuple("B$0"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("3")).product( + factory.tuple("-8"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("3")).product( + factory.tuple("-7"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("3")).product( + factory.tuple("-6"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("3")).product( + factory.tuple("-5"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("3")).product( + factory.tuple("-4"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("3")).product( + factory.tuple("-3"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("3")).product( + factory.tuple("-2"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("3")).product( + factory.tuple("-1"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("3")).product( + factory.tuple("0"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("3")).product( + factory.tuple("1"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("3")).product( + factory.tuple("2"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("3")).product( + factory.tuple("3"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("3")).product( + factory.tuple("4"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("3")).product( + factory.tuple("5"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("3")).product( + factory.tuple("6"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("3")).product( + factory.tuple("7"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("4")).product( + factory.tuple("A$0"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("4")).product( + factory.tuple("B$0"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("4")).product( + factory.tuple("-8"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("4")).product( + factory.tuple("-7"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("4")).product( + factory.tuple("-6"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("4")).product( + factory.tuple("-5"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("4")).product( + factory.tuple("-4"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("4")).product( + factory.tuple("-3"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("4")).product( + factory.tuple("-2"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("4")).product( + factory.tuple("-1"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("4")).product( + factory.tuple("0"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("4")).product( + factory.tuple("1"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("4")).product( + factory.tuple("2"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("4")).product( + factory.tuple("3"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("4")).product( + factory.tuple("4"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("4")).product( + factory.tuple("5"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("4")).product( + factory.tuple("6"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("4")).product( + factory.tuple("7"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("5")).product( + factory.tuple("A$0"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("5")).product( + factory.tuple("B$0"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("5")).product( + factory.tuple("-8"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("5")).product( + factory.tuple("-7"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("5")).product( + factory.tuple("-6"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("5")).product( + factory.tuple("-5"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("5")).product( + factory.tuple("-4"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("5")).product( + factory.tuple("-3"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("5")).product( + factory.tuple("-2"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("5")).product( + factory.tuple("-1"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("5")).product( + factory.tuple("0"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("5")).product( + factory.tuple("1"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("5")).product( + factory.tuple("2"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("5")).product( + factory.tuple("3"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("5")).product( + factory.tuple("4"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("5")).product( + factory.tuple("5"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("5")).product( + factory.tuple("6"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("5")).product( + factory.tuple("7"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("6")).product( + factory.tuple("A$0"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("6")).product( + factory.tuple("B$0"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("6")).product( + factory.tuple("-8"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("6")).product( + factory.tuple("-7"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("6")).product( + factory.tuple("-6"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("6")).product( + factory.tuple("-5"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("6")).product( + factory.tuple("-4"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("6")).product( + factory.tuple("-3"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("6")).product( + factory.tuple("-2"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("6")).product( + factory.tuple("-1"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("6")).product( + factory.tuple("0"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("6")).product( + factory.tuple("1"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("6")).product( + factory.tuple("2"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("6")).product( + factory.tuple("3"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("6")).product( + factory.tuple("4"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("6")).product( + factory.tuple("5"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("6")).product( + factory.tuple("6"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("6")).product( + factory.tuple("7"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("7")).product( + factory.tuple("A$0"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("7")).product( + factory.tuple("B$0"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("7")).product( + factory.tuple("-8"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("7")).product( + factory.tuple("-7"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("7")).product( + factory.tuple("-6"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("7")).product( + factory.tuple("-5"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("7")).product( + factory.tuple("-4"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("7")).product( + factory.tuple("-3"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("7")).product( + factory.tuple("-2"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("7")).product( + factory.tuple("-1"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("7")).product( + factory.tuple("0"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("7")).product( + factory.tuple("1"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("7")).product( + factory.tuple("2"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("7")).product( + factory.tuple("3"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("7")).product( + factory.tuple("4"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("7")).product( + factory.tuple("5"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("7")).product( + factory.tuple("6"))); + x13_upper.add(factory.tuple("Relation2$0").product(factory.tuple("7")).product( + factory.tuple("7"))); + bounds.bound(x13, x13_upper); + + TupleSet x14_upper = factory.noneOf(3); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("A$0")).product( + factory.tuple("A$0"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("A$0")).product( + factory.tuple("B$0"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("A$0")).product( + factory.tuple("-8"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("A$0")).product( + factory.tuple("-7"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("A$0")).product( + factory.tuple("-6"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("A$0")).product( + factory.tuple("-5"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("A$0")).product( + factory.tuple("-4"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("A$0")).product( + factory.tuple("-3"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("A$0")).product( + factory.tuple("-2"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("A$0")).product( + factory.tuple("-1"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("A$0")).product( + factory.tuple("0"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("A$0")).product( + factory.tuple("1"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("A$0")).product( + factory.tuple("2"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("A$0")).product( + factory.tuple("3"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("A$0")).product( + factory.tuple("4"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("A$0")).product( + factory.tuple("5"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("A$0")).product( + factory.tuple("6"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("A$0")).product( + factory.tuple("7"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("B$0")).product( + factory.tuple("A$0"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("B$0")).product( + factory.tuple("B$0"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("B$0")).product( + factory.tuple("-8"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("B$0")).product( + factory.tuple("-7"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("B$0")).product( + factory.tuple("-6"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("B$0")).product( + factory.tuple("-5"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("B$0")).product( + factory.tuple("-4"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("B$0")).product( + factory.tuple("-3"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("B$0")).product( + factory.tuple("-2"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("B$0")).product( + factory.tuple("-1"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("B$0")).product( + factory.tuple("0"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("B$0")).product( + factory.tuple("1"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("B$0")).product( + factory.tuple("2"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("B$0")).product( + factory.tuple("3"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("B$0")).product( + factory.tuple("4"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("B$0")).product( + factory.tuple("5"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("B$0")).product( + factory.tuple("6"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("B$0")).product( + factory.tuple("7"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-8")).product( + factory.tuple("A$0"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-8")).product( + factory.tuple("B$0"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-8")).product( + factory.tuple("-8"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-8")).product( + factory.tuple("-7"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-8")).product( + factory.tuple("-6"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-8")).product( + factory.tuple("-5"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-8")).product( + factory.tuple("-4"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-8")).product( + factory.tuple("-3"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-8")).product( + factory.tuple("-2"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-8")).product( + factory.tuple("-1"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-8")).product( + factory.tuple("0"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-8")).product( + factory.tuple("1"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-8")).product( + factory.tuple("2"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-8")).product( + factory.tuple("3"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-8")).product( + factory.tuple("4"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-8")).product( + factory.tuple("5"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-8")).product( + factory.tuple("6"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-8")).product( + factory.tuple("7"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-7")).product( + factory.tuple("A$0"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-7")).product( + factory.tuple("B$0"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-7")).product( + factory.tuple("-8"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-7")).product( + factory.tuple("-7"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-7")).product( + factory.tuple("-6"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-7")).product( + factory.tuple("-5"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-7")).product( + factory.tuple("-4"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-7")).product( + factory.tuple("-3"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-7")).product( + factory.tuple("-2"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-7")).product( + factory.tuple("-1"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-7")).product( + factory.tuple("0"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-7")).product( + factory.tuple("1"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-7")).product( + factory.tuple("2"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-7")).product( + factory.tuple("3"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-7")).product( + factory.tuple("4"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-7")).product( + factory.tuple("5"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-7")).product( + factory.tuple("6"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-7")).product( + factory.tuple("7"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-6")).product( + factory.tuple("A$0"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-6")).product( + factory.tuple("B$0"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-6")).product( + factory.tuple("-8"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-6")).product( + factory.tuple("-7"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-6")).product( + factory.tuple("-6"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-6")).product( + factory.tuple("-5"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-6")).product( + factory.tuple("-4"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-6")).product( + factory.tuple("-3"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-6")).product( + factory.tuple("-2"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-6")).product( + factory.tuple("-1"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-6")).product( + factory.tuple("0"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-6")).product( + factory.tuple("1"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-6")).product( + factory.tuple("2"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-6")).product( + factory.tuple("3"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-6")).product( + factory.tuple("4"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-6")).product( + factory.tuple("5"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-6")).product( + factory.tuple("6"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-6")).product( + factory.tuple("7"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-5")).product( + factory.tuple("A$0"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-5")).product( + factory.tuple("B$0"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-5")).product( + factory.tuple("-8"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-5")).product( + factory.tuple("-7"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-5")).product( + factory.tuple("-6"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-5")).product( + factory.tuple("-5"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-5")).product( + factory.tuple("-4"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-5")).product( + factory.tuple("-3"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-5")).product( + factory.tuple("-2"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-5")).product( + factory.tuple("-1"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-5")).product( + factory.tuple("0"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-5")).product( + factory.tuple("1"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-5")).product( + factory.tuple("2"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-5")).product( + factory.tuple("3"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-5")).product( + factory.tuple("4"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-5")).product( + factory.tuple("5"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-5")).product( + factory.tuple("6"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-5")).product( + factory.tuple("7"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-4")).product( + factory.tuple("A$0"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-4")).product( + factory.tuple("B$0"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-4")).product( + factory.tuple("-8"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-4")).product( + factory.tuple("-7"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-4")).product( + factory.tuple("-6"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-4")).product( + factory.tuple("-5"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-4")).product( + factory.tuple("-4"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-4")).product( + factory.tuple("-3"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-4")).product( + factory.tuple("-2"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-4")).product( + factory.tuple("-1"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-4")).product( + factory.tuple("0"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-4")).product( + factory.tuple("1"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-4")).product( + factory.tuple("2"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-4")).product( + factory.tuple("3"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-4")).product( + factory.tuple("4"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-4")).product( + factory.tuple("5"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-4")).product( + factory.tuple("6"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-4")).product( + factory.tuple("7"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-3")).product( + factory.tuple("A$0"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-3")).product( + factory.tuple("B$0"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-3")).product( + factory.tuple("-8"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-3")).product( + factory.tuple("-7"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-3")).product( + factory.tuple("-6"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-3")).product( + factory.tuple("-5"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-3")).product( + factory.tuple("-4"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-3")).product( + factory.tuple("-3"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-3")).product( + factory.tuple("-2"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-3")).product( + factory.tuple("-1"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-3")).product( + factory.tuple("0"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-3")).product( + factory.tuple("1"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-3")).product( + factory.tuple("2"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-3")).product( + factory.tuple("3"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-3")).product( + factory.tuple("4"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-3")).product( + factory.tuple("5"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-3")).product( + factory.tuple("6"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-3")).product( + factory.tuple("7"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-2")).product( + factory.tuple("A$0"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-2")).product( + factory.tuple("B$0"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-2")).product( + factory.tuple("-8"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-2")).product( + factory.tuple("-7"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-2")).product( + factory.tuple("-6"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-2")).product( + factory.tuple("-5"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-2")).product( + factory.tuple("-4"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-2")).product( + factory.tuple("-3"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-2")).product( + factory.tuple("-2"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-2")).product( + factory.tuple("-1"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-2")).product( + factory.tuple("0"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-2")).product( + factory.tuple("1"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-2")).product( + factory.tuple("2"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-2")).product( + factory.tuple("3"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-2")).product( + factory.tuple("4"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-2")).product( + factory.tuple("5"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-2")).product( + factory.tuple("6"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-2")).product( + factory.tuple("7"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-1")).product( + factory.tuple("A$0"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-1")).product( + factory.tuple("B$0"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-1")).product( + factory.tuple("-8"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-1")).product( + factory.tuple("-7"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-1")).product( + factory.tuple("-6"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-1")).product( + factory.tuple("-5"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-1")).product( + factory.tuple("-4"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-1")).product( + factory.tuple("-3"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-1")).product( + factory.tuple("-2"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-1")).product( + factory.tuple("-1"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-1")).product( + factory.tuple("0"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-1")).product( + factory.tuple("1"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-1")).product( + factory.tuple("2"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-1")).product( + factory.tuple("3"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-1")).product( + factory.tuple("4"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-1")).product( + factory.tuple("5"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-1")).product( + factory.tuple("6"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("-1")).product( + factory.tuple("7"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("0")).product( + factory.tuple("A$0"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("0")).product( + factory.tuple("B$0"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("0")).product( + factory.tuple("-8"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("0")).product( + factory.tuple("-7"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("0")).product( + factory.tuple("-6"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("0")).product( + factory.tuple("-5"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("0")).product( + factory.tuple("-4"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("0")).product( + factory.tuple("-3"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("0")).product( + factory.tuple("-2"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("0")).product( + factory.tuple("-1"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("0")).product( + factory.tuple("0"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("0")).product( + factory.tuple("1"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("0")).product( + factory.tuple("2"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("0")).product( + factory.tuple("3"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("0")).product( + factory.tuple("4"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("0")).product( + factory.tuple("5"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("0")).product( + factory.tuple("6"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("0")).product( + factory.tuple("7"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("1")).product( + factory.tuple("A$0"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("1")).product( + factory.tuple("B$0"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("1")).product( + factory.tuple("-8"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("1")).product( + factory.tuple("-7"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("1")).product( + factory.tuple("-6"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("1")).product( + factory.tuple("-5"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("1")).product( + factory.tuple("-4"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("1")).product( + factory.tuple("-3"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("1")).product( + factory.tuple("-2"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("1")).product( + factory.tuple("-1"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("1")).product( + factory.tuple("0"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("1")).product( + factory.tuple("1"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("1")).product( + factory.tuple("2"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("1")).product( + factory.tuple("3"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("1")).product( + factory.tuple("4"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("1")).product( + factory.tuple("5"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("1")).product( + factory.tuple("6"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("1")).product( + factory.tuple("7"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("2")).product( + factory.tuple("A$0"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("2")).product( + factory.tuple("B$0"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("2")).product( + factory.tuple("-8"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("2")).product( + factory.tuple("-7"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("2")).product( + factory.tuple("-6"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("2")).product( + factory.tuple("-5"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("2")).product( + factory.tuple("-4"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("2")).product( + factory.tuple("-3"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("2")).product( + factory.tuple("-2"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("2")).product( + factory.tuple("-1"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("2")).product( + factory.tuple("0"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("2")).product( + factory.tuple("1"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("2")).product( + factory.tuple("2"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("2")).product( + factory.tuple("3"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("2")).product( + factory.tuple("4"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("2")).product( + factory.tuple("5"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("2")).product( + factory.tuple("6"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("2")).product( + factory.tuple("7"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("3")).product( + factory.tuple("A$0"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("3")).product( + factory.tuple("B$0"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("3")).product( + factory.tuple("-8"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("3")).product( + factory.tuple("-7"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("3")).product( + factory.tuple("-6"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("3")).product( + factory.tuple("-5"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("3")).product( + factory.tuple("-4"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("3")).product( + factory.tuple("-3"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("3")).product( + factory.tuple("-2"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("3")).product( + factory.tuple("-1"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("3")).product( + factory.tuple("0"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("3")).product( + factory.tuple("1"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("3")).product( + factory.tuple("2"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("3")).product( + factory.tuple("3"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("3")).product( + factory.tuple("4"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("3")).product( + factory.tuple("5"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("3")).product( + factory.tuple("6"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("3")).product( + factory.tuple("7"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("4")).product( + factory.tuple("A$0"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("4")).product( + factory.tuple("B$0"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("4")).product( + factory.tuple("-8"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("4")).product( + factory.tuple("-7"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("4")).product( + factory.tuple("-6"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("4")).product( + factory.tuple("-5"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("4")).product( + factory.tuple("-4"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("4")).product( + factory.tuple("-3"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("4")).product( + factory.tuple("-2"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("4")).product( + factory.tuple("-1"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("4")).product( + factory.tuple("0"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("4")).product( + factory.tuple("1"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("4")).product( + factory.tuple("2"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("4")).product( + factory.tuple("3"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("4")).product( + factory.tuple("4"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("4")).product( + factory.tuple("5"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("4")).product( + factory.tuple("6"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("4")).product( + factory.tuple("7"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("5")).product( + factory.tuple("A$0"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("5")).product( + factory.tuple("B$0"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("5")).product( + factory.tuple("-8"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("5")).product( + factory.tuple("-7"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("5")).product( + factory.tuple("-6"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("5")).product( + factory.tuple("-5"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("5")).product( + factory.tuple("-4"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("5")).product( + factory.tuple("-3"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("5")).product( + factory.tuple("-2"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("5")).product( + factory.tuple("-1"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("5")).product( + factory.tuple("0"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("5")).product( + factory.tuple("1"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("5")).product( + factory.tuple("2"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("5")).product( + factory.tuple("3"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("5")).product( + factory.tuple("4"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("5")).product( + factory.tuple("5"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("5")).product( + factory.tuple("6"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("5")).product( + factory.tuple("7"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("6")).product( + factory.tuple("A$0"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("6")).product( + factory.tuple("B$0"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("6")).product( + factory.tuple("-8"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("6")).product( + factory.tuple("-7"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("6")).product( + factory.tuple("-6"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("6")).product( + factory.tuple("-5"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("6")).product( + factory.tuple("-4"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("6")).product( + factory.tuple("-3"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("6")).product( + factory.tuple("-2"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("6")).product( + factory.tuple("-1"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("6")).product( + factory.tuple("0"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("6")).product( + factory.tuple("1"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("6")).product( + factory.tuple("2"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("6")).product( + factory.tuple("3"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("6")).product( + factory.tuple("4"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("6")).product( + factory.tuple("5"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("6")).product( + factory.tuple("6"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("6")).product( + factory.tuple("7"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("7")).product( + factory.tuple("A$0"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("7")).product( + factory.tuple("B$0"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("7")).product( + factory.tuple("-8"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("7")).product( + factory.tuple("-7"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("7")).product( + factory.tuple("-6"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("7")).product( + factory.tuple("-5"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("7")).product( + factory.tuple("-4"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("7")).product( + factory.tuple("-3"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("7")).product( + factory.tuple("-2"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("7")).product( + factory.tuple("-1"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("7")).product( + factory.tuple("0"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("7")).product( + factory.tuple("1"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("7")).product( + factory.tuple("2"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("7")).product( + factory.tuple("3"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("7")).product( + factory.tuple("4"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("7")).product( + factory.tuple("5"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("7")).product( + factory.tuple("6"))); + x14_upper.add(factory.tuple("Relation3$0").product(factory.tuple("7")).product( + factory.tuple("7"))); + bounds.bound(x14, x14_upper); + + TupleSet x15_upper = factory.noneOf(3); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("A$0")).product( + factory.tuple("A$0"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("A$0")).product( + factory.tuple("B$0"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("A$0")).product( + factory.tuple("-8"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("A$0")).product( + factory.tuple("-7"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("A$0")).product( + factory.tuple("-6"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("A$0")).product( + factory.tuple("-5"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("A$0")).product( + factory.tuple("-4"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("A$0")).product( + factory.tuple("-3"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("A$0")).product( + factory.tuple("-2"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("A$0")).product( + factory.tuple("-1"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("A$0")).product( + factory.tuple("0"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("A$0")).product( + factory.tuple("1"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("A$0")).product( + factory.tuple("2"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("A$0")).product( + factory.tuple("3"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("A$0")).product( + factory.tuple("4"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("A$0")).product( + factory.tuple("5"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("A$0")).product( + factory.tuple("6"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("A$0")).product( + factory.tuple("7"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("B$0")).product( + factory.tuple("A$0"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("B$0")).product( + factory.tuple("B$0"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("B$0")).product( + factory.tuple("-8"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("B$0")).product( + factory.tuple("-7"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("B$0")).product( + factory.tuple("-6"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("B$0")).product( + factory.tuple("-5"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("B$0")).product( + factory.tuple("-4"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("B$0")).product( + factory.tuple("-3"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("B$0")).product( + factory.tuple("-2"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("B$0")).product( + factory.tuple("-1"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("B$0")).product( + factory.tuple("0"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("B$0")).product( + factory.tuple("1"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("B$0")).product( + factory.tuple("2"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("B$0")).product( + factory.tuple("3"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("B$0")).product( + factory.tuple("4"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("B$0")).product( + factory.tuple("5"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("B$0")).product( + factory.tuple("6"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("B$0")).product( + factory.tuple("7"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-8")).product( + factory.tuple("A$0"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-8")).product( + factory.tuple("B$0"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-8")).product( + factory.tuple("-8"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-8")).product( + factory.tuple("-7"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-8")).product( + factory.tuple("-6"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-8")).product( + factory.tuple("-5"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-8")).product( + factory.tuple("-4"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-8")).product( + factory.tuple("-3"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-8")).product( + factory.tuple("-2"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-8")).product( + factory.tuple("-1"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-8")).product( + factory.tuple("0"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-8")).product( + factory.tuple("1"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-8")).product( + factory.tuple("2"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-8")).product( + factory.tuple("3"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-8")).product( + factory.tuple("4"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-8")).product( + factory.tuple("5"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-8")).product( + factory.tuple("6"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-8")).product( + factory.tuple("7"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-7")).product( + factory.tuple("A$0"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-7")).product( + factory.tuple("B$0"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-7")).product( + factory.tuple("-8"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-7")).product( + factory.tuple("-7"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-7")).product( + factory.tuple("-6"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-7")).product( + factory.tuple("-5"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-7")).product( + factory.tuple("-4"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-7")).product( + factory.tuple("-3"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-7")).product( + factory.tuple("-2"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-7")).product( + factory.tuple("-1"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-7")).product( + factory.tuple("0"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-7")).product( + factory.tuple("1"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-7")).product( + factory.tuple("2"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-7")).product( + factory.tuple("3"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-7")).product( + factory.tuple("4"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-7")).product( + factory.tuple("5"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-7")).product( + factory.tuple("6"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-7")).product( + factory.tuple("7"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-6")).product( + factory.tuple("A$0"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-6")).product( + factory.tuple("B$0"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-6")).product( + factory.tuple("-8"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-6")).product( + factory.tuple("-7"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-6")).product( + factory.tuple("-6"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-6")).product( + factory.tuple("-5"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-6")).product( + factory.tuple("-4"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-6")).product( + factory.tuple("-3"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-6")).product( + factory.tuple("-2"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-6")).product( + factory.tuple("-1"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-6")).product( + factory.tuple("0"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-6")).product( + factory.tuple("1"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-6")).product( + factory.tuple("2"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-6")).product( + factory.tuple("3"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-6")).product( + factory.tuple("4"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-6")).product( + factory.tuple("5"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-6")).product( + factory.tuple("6"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-6")).product( + factory.tuple("7"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-5")).product( + factory.tuple("A$0"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-5")).product( + factory.tuple("B$0"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-5")).product( + factory.tuple("-8"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-5")).product( + factory.tuple("-7"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-5")).product( + factory.tuple("-6"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-5")).product( + factory.tuple("-5"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-5")).product( + factory.tuple("-4"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-5")).product( + factory.tuple("-3"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-5")).product( + factory.tuple("-2"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-5")).product( + factory.tuple("-1"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-5")).product( + factory.tuple("0"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-5")).product( + factory.tuple("1"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-5")).product( + factory.tuple("2"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-5")).product( + factory.tuple("3"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-5")).product( + factory.tuple("4"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-5")).product( + factory.tuple("5"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-5")).product( + factory.tuple("6"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-5")).product( + factory.tuple("7"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-4")).product( + factory.tuple("A$0"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-4")).product( + factory.tuple("B$0"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-4")).product( + factory.tuple("-8"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-4")).product( + factory.tuple("-7"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-4")).product( + factory.tuple("-6"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-4")).product( + factory.tuple("-5"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-4")).product( + factory.tuple("-4"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-4")).product( + factory.tuple("-3"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-4")).product( + factory.tuple("-2"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-4")).product( + factory.tuple("-1"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-4")).product( + factory.tuple("0"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-4")).product( + factory.tuple("1"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-4")).product( + factory.tuple("2"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-4")).product( + factory.tuple("3"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-4")).product( + factory.tuple("4"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-4")).product( + factory.tuple("5"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-4")).product( + factory.tuple("6"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-4")).product( + factory.tuple("7"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-3")).product( + factory.tuple("A$0"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-3")).product( + factory.tuple("B$0"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-3")).product( + factory.tuple("-8"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-3")).product( + factory.tuple("-7"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-3")).product( + factory.tuple("-6"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-3")).product( + factory.tuple("-5"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-3")).product( + factory.tuple("-4"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-3")).product( + factory.tuple("-3"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-3")).product( + factory.tuple("-2"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-3")).product( + factory.tuple("-1"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-3")).product( + factory.tuple("0"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-3")).product( + factory.tuple("1"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-3")).product( + factory.tuple("2"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-3")).product( + factory.tuple("3"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-3")).product( + factory.tuple("4"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-3")).product( + factory.tuple("5"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-3")).product( + factory.tuple("6"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-3")).product( + factory.tuple("7"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-2")).product( + factory.tuple("A$0"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-2")).product( + factory.tuple("B$0"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-2")).product( + factory.tuple("-8"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-2")).product( + factory.tuple("-7"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-2")).product( + factory.tuple("-6"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-2")).product( + factory.tuple("-5"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-2")).product( + factory.tuple("-4"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-2")).product( + factory.tuple("-3"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-2")).product( + factory.tuple("-2"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-2")).product( + factory.tuple("-1"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-2")).product( + factory.tuple("0"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-2")).product( + factory.tuple("1"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-2")).product( + factory.tuple("2"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-2")).product( + factory.tuple("3"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-2")).product( + factory.tuple("4"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-2")).product( + factory.tuple("5"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-2")).product( + factory.tuple("6"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-2")).product( + factory.tuple("7"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-1")).product( + factory.tuple("A$0"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-1")).product( + factory.tuple("B$0"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-1")).product( + factory.tuple("-8"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-1")).product( + factory.tuple("-7"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-1")).product( + factory.tuple("-6"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-1")).product( + factory.tuple("-5"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-1")).product( + factory.tuple("-4"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-1")).product( + factory.tuple("-3"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-1")).product( + factory.tuple("-2"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-1")).product( + factory.tuple("-1"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-1")).product( + factory.tuple("0"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-1")).product( + factory.tuple("1"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-1")).product( + factory.tuple("2"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-1")).product( + factory.tuple("3"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-1")).product( + factory.tuple("4"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-1")).product( + factory.tuple("5"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-1")).product( + factory.tuple("6"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("-1")).product( + factory.tuple("7"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("0")).product( + factory.tuple("A$0"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("0")).product( + factory.tuple("B$0"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("0")).product( + factory.tuple("-8"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("0")).product( + factory.tuple("-7"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("0")).product( + factory.tuple("-6"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("0")).product( + factory.tuple("-5"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("0")).product( + factory.tuple("-4"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("0")).product( + factory.tuple("-3"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("0")).product( + factory.tuple("-2"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("0")).product( + factory.tuple("-1"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("0")).product( + factory.tuple("0"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("0")).product( + factory.tuple("1"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("0")).product( + factory.tuple("2"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("0")).product( + factory.tuple("3"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("0")).product( + factory.tuple("4"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("0")).product( + factory.tuple("5"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("0")).product( + factory.tuple("6"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("0")).product( + factory.tuple("7"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("1")).product( + factory.tuple("A$0"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("1")).product( + factory.tuple("B$0"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("1")).product( + factory.tuple("-8"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("1")).product( + factory.tuple("-7"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("1")).product( + factory.tuple("-6"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("1")).product( + factory.tuple("-5"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("1")).product( + factory.tuple("-4"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("1")).product( + factory.tuple("-3"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("1")).product( + factory.tuple("-2"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("1")).product( + factory.tuple("-1"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("1")).product( + factory.tuple("0"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("1")).product( + factory.tuple("1"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("1")).product( + factory.tuple("2"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("1")).product( + factory.tuple("3"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("1")).product( + factory.tuple("4"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("1")).product( + factory.tuple("5"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("1")).product( + factory.tuple("6"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("1")).product( + factory.tuple("7"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("2")).product( + factory.tuple("A$0"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("2")).product( + factory.tuple("B$0"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("2")).product( + factory.tuple("-8"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("2")).product( + factory.tuple("-7"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("2")).product( + factory.tuple("-6"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("2")).product( + factory.tuple("-5"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("2")).product( + factory.tuple("-4"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("2")).product( + factory.tuple("-3"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("2")).product( + factory.tuple("-2"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("2")).product( + factory.tuple("-1"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("2")).product( + factory.tuple("0"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("2")).product( + factory.tuple("1"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("2")).product( + factory.tuple("2"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("2")).product( + factory.tuple("3"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("2")).product( + factory.tuple("4"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("2")).product( + factory.tuple("5"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("2")).product( + factory.tuple("6"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("2")).product( + factory.tuple("7"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("3")).product( + factory.tuple("A$0"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("3")).product( + factory.tuple("B$0"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("3")).product( + factory.tuple("-8"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("3")).product( + factory.tuple("-7"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("3")).product( + factory.tuple("-6"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("3")).product( + factory.tuple("-5"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("3")).product( + factory.tuple("-4"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("3")).product( + factory.tuple("-3"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("3")).product( + factory.tuple("-2"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("3")).product( + factory.tuple("-1"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("3")).product( + factory.tuple("0"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("3")).product( + factory.tuple("1"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("3")).product( + factory.tuple("2"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("3")).product( + factory.tuple("3"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("3")).product( + factory.tuple("4"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("3")).product( + factory.tuple("5"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("3")).product( + factory.tuple("6"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("3")).product( + factory.tuple("7"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("4")).product( + factory.tuple("A$0"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("4")).product( + factory.tuple("B$0"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("4")).product( + factory.tuple("-8"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("4")).product( + factory.tuple("-7"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("4")).product( + factory.tuple("-6"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("4")).product( + factory.tuple("-5"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("4")).product( + factory.tuple("-4"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("4")).product( + factory.tuple("-3"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("4")).product( + factory.tuple("-2"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("4")).product( + factory.tuple("-1"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("4")).product( + factory.tuple("0"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("4")).product( + factory.tuple("1"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("4")).product( + factory.tuple("2"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("4")).product( + factory.tuple("3"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("4")).product( + factory.tuple("4"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("4")).product( + factory.tuple("5"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("4")).product( + factory.tuple("6"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("4")).product( + factory.tuple("7"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("5")).product( + factory.tuple("A$0"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("5")).product( + factory.tuple("B$0"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("5")).product( + factory.tuple("-8"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("5")).product( + factory.tuple("-7"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("5")).product( + factory.tuple("-6"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("5")).product( + factory.tuple("-5"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("5")).product( + factory.tuple("-4"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("5")).product( + factory.tuple("-3"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("5")).product( + factory.tuple("-2"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("5")).product( + factory.tuple("-1"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("5")).product( + factory.tuple("0"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("5")).product( + factory.tuple("1"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("5")).product( + factory.tuple("2"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("5")).product( + factory.tuple("3"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("5")).product( + factory.tuple("4"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("5")).product( + factory.tuple("5"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("5")).product( + factory.tuple("6"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("5")).product( + factory.tuple("7"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("6")).product( + factory.tuple("A$0"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("6")).product( + factory.tuple("B$0"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("6")).product( + factory.tuple("-8"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("6")).product( + factory.tuple("-7"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("6")).product( + factory.tuple("-6"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("6")).product( + factory.tuple("-5"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("6")).product( + factory.tuple("-4"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("6")).product( + factory.tuple("-3"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("6")).product( + factory.tuple("-2"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("6")).product( + factory.tuple("-1"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("6")).product( + factory.tuple("0"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("6")).product( + factory.tuple("1"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("6")).product( + factory.tuple("2"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("6")).product( + factory.tuple("3"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("6")).product( + factory.tuple("4"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("6")).product( + factory.tuple("5"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("6")).product( + factory.tuple("6"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("6")).product( + factory.tuple("7"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("7")).product( + factory.tuple("A$0"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("7")).product( + factory.tuple("B$0"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("7")).product( + factory.tuple("-8"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("7")).product( + factory.tuple("-7"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("7")).product( + factory.tuple("-6"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("7")).product( + factory.tuple("-5"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("7")).product( + factory.tuple("-4"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("7")).product( + factory.tuple("-3"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("7")).product( + factory.tuple("-2"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("7")).product( + factory.tuple("-1"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("7")).product( + factory.tuple("0"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("7")).product( + factory.tuple("1"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("7")).product( + factory.tuple("2"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("7")).product( + factory.tuple("3"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("7")).product( + factory.tuple("4"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("7")).product( + factory.tuple("5"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("7")).product( + factory.tuple("6"))); + x15_upper.add(factory.tuple("Relation4$0").product(factory.tuple("7")).product( + factory.tuple("7"))); + bounds.bound(x15, x15_upper); + + bounds.boundExactly(-8, factory.range(factory.tuple("-8"), factory.tuple("-8"))); + bounds.boundExactly(-7, factory.range(factory.tuple("-7"), factory.tuple("-7"))); + bounds.boundExactly(-6, factory.range(factory.tuple("-6"), factory.tuple("-6"))); + bounds.boundExactly(-5, factory.range(factory.tuple("-5"), factory.tuple("-5"))); + bounds.boundExactly(-4, factory.range(factory.tuple("-4"), factory.tuple("-4"))); + bounds.boundExactly(-3, factory.range(factory.tuple("-3"), factory.tuple("-3"))); + bounds.boundExactly(-2, factory.range(factory.tuple("-2"), factory.tuple("-2"))); + bounds.boundExactly(-1, factory.range(factory.tuple("-1"), factory.tuple("-1"))); + bounds.boundExactly(0, factory.range(factory.tuple("0"), factory.tuple("0"))); + bounds.boundExactly(1, factory.range(factory.tuple("1"), factory.tuple("1"))); + bounds.boundExactly(2, factory.range(factory.tuple("2"), factory.tuple("2"))); + bounds.boundExactly(3, factory.range(factory.tuple("3"), factory.tuple("3"))); + bounds.boundExactly(4, factory.range(factory.tuple("4"), factory.tuple("4"))); + bounds.boundExactly(5, factory.range(factory.tuple("5"), factory.tuple("5"))); + bounds.boundExactly(6, factory.range(factory.tuple("6"), factory.tuple("6"))); + bounds.boundExactly(7, factory.range(factory.tuple("7"), factory.tuple("7"))); + + Variable x19 = Variable.unary("p1_this"); + Decls x18 = x19.oneOf(x8); + Expression x23 = x19.join(x12); + Expression x25 = x6.union(x7); + Expression x26 = x6.union(x7); + Expression x24 = x25.product(x26); + Formula x22 = x23.in(x24); + Variable x29 = Variable.unary("v60"); + Decls x28 = x29.oneOf(x25); + Expression x32 = x29.join(x23); + Formula x31 = x32.one(); + Expression x34 = x6.union(x7); + Formula x33 = x32.in(x34); + Formula x30 = x31.and(x33); + Formula x27 = x30.forAll(x28); + Formula x21 = x22.and(x27); + Variable x37 = Variable.unary("v61"); + Decls x36 = x37.oneOf(x26); + Expression x39 = x23.join(x37); + Expression x40 = x6.union(x7); + Formula x38 = x39.in(x40); + Formula x35 = x38.forAll(x36); + Formula x20 = x21.and(x35); + Formula x17 = x20.forAll(x18); + Expression x43 = x12.join(Expression.UNIV); + Expression x42 = x43.join(Expression.UNIV); + Formula x41 = x42.in(x8); + Variable x47 = Variable.unary("p1_this"); + Decls x46 = x47.oneOf(x9); + Expression x51 = x47.join(x13); + Expression x54 = x6.union(x7); + Expression x53 = x54.union(Expression.INTS); + Expression x57 = x6.union(x7); + Expression x56 = x57.union(Expression.INTS); + Expression x52 = x53.product(x56); + Formula x50 = x51.in(x52); + Variable x60 = Variable.unary("v62"); + Decls x59 = x60.oneOf(x53); + Expression x62 = x60.join(x51); + Expression x64 = x6.union(x7); + Expression x63 = x64.union(Expression.INTS); + Formula x61 = x62.in(x63); + Formula x58 = x61.forAll(x59); + Formula x49 = x50.and(x58); + Variable x67 = Variable.unary("v63"); + Decls x66 = x67.oneOf(x56); + Expression x70 = x51.join(x67); + Formula x69 = x70.one(); + Expression x73 = x6.union(x7); + Expression x72 = x73.union(Expression.INTS); + Formula x71 = x70.in(x72); + Formula x68 = x69.and(x71); + Formula x65 = x68.forAll(x66); + Formula x48 = x49.and(x65); + Formula x45 = x48.forAll(x46); + Expression x76 = x13.join(Expression.UNIV); + Expression x75 = x76.join(Expression.UNIV); + Formula x74 = x75.in(x9); + Variable x79 = Variable.unary("p1_this"); + Decls x78 = x79.oneOf(x10); + Expression x83 = x79.join(x14); + Expression x86 = x6.union(x7); + Expression x85 = x86.union(Expression.INTS); + Expression x88 = x6.union(x7); + Expression x87 = x88.union(Expression.INTS); + Expression x84 = x85.product(x87); + Formula x82 = x83.in(x84); + Variable x91 = Variable.unary("v64"); + Decls x90 = x91.oneOf(x85); + Expression x94 = x91.join(x83); + Formula x93 = x94.one(); + Expression x97 = x6.union(x7); + Expression x96 = x97.union(Expression.INTS); + Formula x95 = x94.in(x96); + Formula x92 = x93.and(x95); + Formula x89 = x92.forAll(x90); + Formula x81 = x82.and(x89); + Variable x100 = Variable.unary("v65"); + Decls x99 = x100.oneOf(x87); + Expression x103 = x83.join(x100); + Formula x102 = x103.one(); + Expression x106 = x6.union(x7); + Expression x105 = x106.union(Expression.INTS); + Formula x104 = x103.in(x105); + Formula x101 = x102.and(x104); + Formula x98 = x101.forAll(x99); + Formula x80 = x81.and(x98); + Formula x77 = x80.forAll(x78); + Expression x109 = x14.join(Expression.UNIV); + Expression x108 = x109.join(Expression.UNIV); + Formula x107 = x108.in(x10); + Variable x112 = Variable.unary("p1_this"); + Decls x111 = x112.oneOf(x11); + Expression x114 = x112.join(x15); + Expression x117 = x6.union(x7); + Expression x116 = x117.union(Expression.INTS); + Expression x119 = x6.union(x7); + Expression x118 = x119.union(Expression.INTS); + Expression x115 = x116.product(x118); + Formula x113 = x114.in(x115); + Formula x110 = x113.forAll(x111); + Expression x122 = x15.join(Expression.UNIV); + Expression x121 = x122.join(Expression.UNIV); + Formula x120 = x121.in(x11); + Variable x126 = Variable.unary("p1_r1"); + Decls x125 = x126.oneOf(x8); + Variable x129 = Variable.unary("p1_x"); + Expression x130 = x6.union(x7); + Decls x128 = x129.oneOf(x130); + Expression x133 = x126.join(x12); + Expression x132 = x129.join(x133); + Formula x131 = x132.one(); + Formula x127 = x131.forAll(x128); + Formula x124 = x127.forAll(x125); + Formula x123 = x124.not(); + Formula x134 = x0.eq(x0); + Formula x135 = x1.eq(x1); + Formula x136 = x2.eq(x2); + Formula x137 = x3.eq(x3); + Formula x138 = x4.eq(x4); + Formula x139 = x5.eq(x5); + Formula x140 = x6.eq(x6); + Formula x141 = x7.eq(x7); + Formula x142 = x8.eq(x8); + Formula x143 = x9.eq(x9); + Formula x144 = x10.eq(x10); + Formula x145 = x11.eq(x11); + Formula x146 = x12.eq(x12); + Formula x147 = x13.eq(x13); + Formula x148 = x14.eq(x14); + Formula x149 = x15.eq(x15); + Formula x16 = Formula.compose(FormulaOperator.AND, x17, x41, x45, x74, x77, x107, x110, + x120, x123, x134, x135, x136, x137, x138, x139, x140, x141, x142, x143, x144, x145, + x146, x147, x148, x149); + + Solver solver = new Solver(); + solver.options().setSolver(SATFactory.DefaultSAT4J); + solver.options().setBitwidth(4); + solver.options().setFlatten(false); + solver.options().setIntEncoding(Options.IntEncoding.TWOSCOMPLEMENT); + solver.options().setSymmetryBreaking(20); + solver.options().setSkolemDepth(0); + System.out.println("Solving..."); + System.out.flush(); + Solution sol = solver.solve(x16, bounds); + System.out.println(sol.toString()); + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/tmp/Test.java b/Source/eu.modelwriter.alloyanalyzer/src/tmp/Test.java new file mode 100644 index 00000000..34a66ea7 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/tmp/Test.java @@ -0,0 +1,83 @@ +package tmp; + +import java.util.Arrays; +import java.util.List; +import kodkod.ast.*; +import kodkod.ast.operator.*; +import kodkod.instance.*; +import kodkod.engine.*; +import kodkod.engine.satlab.SATFactory; +import kodkod.engine.config.Options; + +/* + ================================================== + kodkod formula: + ================================================== + #(this/A) = (#(this/B) + 1) && + Int/min = Int/min && + Int/zero = Int/zero && + Int/max = Int/max && + Int/next = Int/next && + seq/Int = seq/Int && + String = String && + this/A = this/A && + this/B = this/B + ================================================== + */ +public final class Test { + + public static void main(String[] args) throws Exception { + + Relation x5 = Relation.unary("String"); + Relation x6 = Relation.unary("this/A"); + Relation x7 = Relation.unary("this/B"); + + List atomlist = Arrays.asList("A$0", "unused0", "unused1", "unused2", "unused3", "unused4", "unused5", "unused6", + "unused7", "unused8"); + + Universe universe = new Universe(atomlist); + TupleFactory factory = universe.factory(); + Bounds bounds = new Bounds(universe); + + TupleSet x5_upper = factory.noneOf(1); + bounds.boundExactly(x5, x5_upper); + + TupleSet x6_upper = factory.noneOf(1); + x6_upper.add(factory.tuple("unused0")); + x6_upper.add(factory.tuple("unused1")); + x6_upper.add(factory.tuple("unused2")); + x6_upper.add(factory.tuple("unused3")); + x6_upper.add(factory.tuple("A$0")); + bounds.bound(x6, x6_upper); + + TupleSet x7_upper = factory.noneOf(1); + x7_upper.add(factory.tuple("unused4")); + x7_upper.add(factory.tuple("unused5")); + x7_upper.add(factory.tuple("unused6")); + x7_upper.add(factory.tuple("unused7")); + x7_upper.add(factory.tuple("unused8")); + bounds.bound(x7, x7_upper); + + IntExpression x10 = x6.count(); + IntExpression x12 = x7.count(); + IntExpression x13 = IntConstant.constant(1); + IntExpression x11 = x12.plus(x13); + Formula x9 = x10.eq(x11); + Formula x19 = x5.eq(x5); + Formula x20 = x6.eq(x6); + Formula x21 = x7.eq(x7); + Formula x8 = Formula.compose(FormulaOperator.AND, x9, x19, x20, x21); + + Solver solver = new Solver(); + solver.options().setSolver(SATFactory.DefaultSAT4J); + solver.options().setBitwidth(4); + solver.options().setFlatten(false); + solver.options().setIntEncoding(Options.IntEncoding.TWOSCOMPLEMENT); + solver.options().setSymmetryBreaking(20); + solver.options().setSkolemDepth(0); + System.out.println("Solving..."); + System.out.flush(); + Solution sol = solver.solve(x8, bounds); + System.out.println(sol.toString()); + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/tmp/Test1.java b/Source/eu.modelwriter.alloyanalyzer/src/tmp/Test1.java new file mode 100644 index 00000000..60292b2a --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/tmp/Test1.java @@ -0,0 +1,100 @@ +package tmp; +import java.util.Arrays; +import java.util.List; +import kodkod.ast.*; +import kodkod.ast.operator.*; +import kodkod.instance.*; +import kodkod.util.nodes.PrettyPrinter; +import kodkod.engine.*; +import kodkod.engine.satlab.SATFactory; +import kodkod.engine.config.Options; + +public final class Test1 { + + public static void main(String[] args) throws Exception { + + Relation x0 = Relation.unary("Int/min"); + Relation x1 = Relation.unary("Int/zero"); + Relation x2 = Relation.unary("Int/max"); + Relation x3 = Relation.nary("Int/next", 2); + Relation x4 = Relation.unary("seq/Int"); + Relation x5 = Relation.unary("String"); + Relation x6 = Relation.unary("this/R"); + + List atomlist = Arrays.asList("-1", "-2", "0", "1", "R$0", "R$1", "unused0"); + + Universe universe = new Universe(atomlist); + TupleFactory factory = universe.factory(); + Bounds bounds = new Bounds(universe); + + TupleSet x0_upper = factory.noneOf(1); + x0_upper.add(factory.tuple("-2")); + bounds.boundExactly(x0, x0_upper); + + TupleSet x1_upper = factory.noneOf(1); + x1_upper.add(factory.tuple("0")); + bounds.boundExactly(x1, x1_upper); + + TupleSet x2_upper = factory.noneOf(1); + x2_upper.add(factory.tuple("1")); + bounds.boundExactly(x2, x2_upper); + + TupleSet x3_upper = factory.noneOf(2); + x3_upper.add(factory.tuple("-2").product(factory.tuple("-1"))); + x3_upper.add(factory.tuple("-1").product(factory.tuple("0"))); + x3_upper.add(factory.tuple("0").product(factory.tuple("1"))); + bounds.boundExactly(x3, x3_upper); + + TupleSet x4_upper = factory.noneOf(1); + x4_upper.add(factory.tuple("0")); + bounds.boundExactly(x4, x4_upper); + + TupleSet x5_upper = factory.noneOf(1); + bounds.boundExactly(x5, x5_upper); + + TupleSet x6_upper = factory.noneOf(1); + x6_upper.add(factory.tuple("unused0")); + x6_upper.add(factory.tuple("R$0")); + x6_upper.add(factory.tuple("R$1")); + bounds.bound(x6, x6_upper); + + bounds.boundExactly(-2, factory.range(factory.tuple("-2"), factory.tuple("-2"))); + bounds.boundExactly(-1, factory.range(factory.tuple("-1"), factory.tuple("-1"))); + bounds.boundExactly(0, factory.range(factory.tuple("0"), factory.tuple("0"))); + bounds.boundExactly(1, factory.range(factory.tuple("1"), factory.tuple("1"))); + + Formula x11 = x6.some(); + Formula x10 = x11.not(); + IntExpression x13 = x6.count(); + IntExpression x14 = IntConstant.constant(0); + Formula x12 = x13.gt(x14); + Formula x9 = x10.or(x12); + Formula x8 = x9.not(); + Formula x15 = x0.eq(x0); + Formula x16 = x1.eq(x1); + Formula x17 = x2.eq(x2); + Formula x18 = x3.eq(x3); + Formula x19 = x4.eq(x4); + Formula x20 = x5.eq(x5); + Formula x21 = x6.eq(x6); + Formula x7 = Formula.compose(FormulaOperator.AND, x8, x15, x16, x17, x18, x19, x20, x21); + + Solver solver = new Solver(); + solver.options().setSolver(SATFactory.DefaultSAT4J); + solver.options().setBitwidth(2); + solver.options().setFlatten(false); + solver.options().setIntEncoding(Options.IntEncoding.TWOSCOMPLEMENT); + solver.options().setSymmetryBreaking(20); + solver.options().setSkolemDepth(0); + System.out.println("Solving..."); + System.out.println(PrettyPrinter.print(x7, 2)); + System.out.println(bounds); + System.out.flush(); + Solution sol = solver.solve(x7, bounds); + System.out.println(sol.toString()); + Evaluator ev = new Evaluator(sol.instance(), solver.options()); + System.out.println(ev.evaluate(x7)); + System.out.println(ev.evaluate(x6.count())); + System.out.println(ev.evaluate(x6.count().gt(IntConstant.constant(0)))); + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/tmp/TestSlow.java b/Source/eu.modelwriter.alloyanalyzer/src/tmp/TestSlow.java new file mode 100644 index 00000000..f23a6030 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/tmp/TestSlow.java @@ -0,0 +1,266 @@ +package tmp; +import java.util.Arrays; +import java.util.List; +import kodkod.ast.*; +import kodkod.ast.operator.*; +import kodkod.instance.*; +import kodkod.util.nodes.PrettyPrinter; +import kodkod.engine.*; +import kodkod.engine.satlab.SATFactory; +import kodkod.engine.config.Options; + +public final class TestSlow { + + public static void main(String[] args) throws Exception { + + Relation x0 = Relation.unary("Int/min"); + Relation x1 = Relation.unary("Int/zero"); + Relation x2 = Relation.unary("Int/max"); + Relation x3 = Relation.nary("Int/next", 2); + Relation x4 = Relation.unary("seq/Int"); + Relation x5 = Relation.unary("String"); + Relation x6 = Relation.unary("this/A"); + Relation x7 = Relation.unary("this/Relation"); + Relation x8 = Relation.nary("this/Relation.r", 5); + + List atomlist = Arrays.asList("-1", "-2", "-3", "-4", "-5", "-6", "-7", "-8", "0", + "1", "2", "3", "4", "5", "6", "7", "unused0", "unused1"); + + Universe universe = new Universe(atomlist); + TupleFactory factory = universe.factory(); + Bounds bounds = new Bounds(universe); + + TupleSet x0_upper = factory.noneOf(1); + x0_upper.add(factory.tuple("-8")); + bounds.boundExactly(x0, x0_upper); + + TupleSet x1_upper = factory.noneOf(1); + x1_upper.add(factory.tuple("0")); + bounds.boundExactly(x1, x1_upper); + + TupleSet x2_upper = factory.noneOf(1); + x2_upper.add(factory.tuple("7")); + bounds.boundExactly(x2, x2_upper); + + TupleSet x3_upper = factory.noneOf(2); + x3_upper.add(factory.tuple("-8").product(factory.tuple("-7"))); + x3_upper.add(factory.tuple("-7").product(factory.tuple("-6"))); + x3_upper.add(factory.tuple("-6").product(factory.tuple("-5"))); + x3_upper.add(factory.tuple("-5").product(factory.tuple("-4"))); + x3_upper.add(factory.tuple("-4").product(factory.tuple("-3"))); + x3_upper.add(factory.tuple("-3").product(factory.tuple("-2"))); + x3_upper.add(factory.tuple("-2").product(factory.tuple("-1"))); + x3_upper.add(factory.tuple("-1").product(factory.tuple("0"))); + x3_upper.add(factory.tuple("0").product(factory.tuple("1"))); + x3_upper.add(factory.tuple("1").product(factory.tuple("2"))); + x3_upper.add(factory.tuple("2").product(factory.tuple("3"))); + x3_upper.add(factory.tuple("3").product(factory.tuple("4"))); + x3_upper.add(factory.tuple("4").product(factory.tuple("5"))); + x3_upper.add(factory.tuple("5").product(factory.tuple("6"))); + x3_upper.add(factory.tuple("6").product(factory.tuple("7"))); + bounds.boundExactly(x3, x3_upper); + + TupleSet x4_upper = factory.noneOf(1); + x4_upper.add(factory.tuple("0")); + bounds.boundExactly(x4, x4_upper); + + TupleSet x5_upper = factory.noneOf(1); + bounds.boundExactly(x5, x5_upper); + + TupleSet x6_upper = factory.noneOf(1); + x6_upper.add(factory.tuple("unused0")); + bounds.bound(x6, x6_upper); + + TupleSet x7_upper = factory.noneOf(1); + x7_upper.add(factory.tuple("unused1")); + bounds.bound(x7, x7_upper); + + TupleSet x8_upper = factory.noneOf(5); + x8_upper.add(factory.tuple("unused1").product(factory.tuple("unused0")).product( + factory.tuple("unused0")).product(factory.tuple("unused0")).product( + factory.tuple("unused0"))); + bounds.bound(x8, x8_upper); + + bounds.boundExactly(-8, factory.range(factory.tuple("-8"), factory.tuple("-8"))); + bounds.boundExactly(-7, factory.range(factory.tuple("-7"), factory.tuple("-7"))); + bounds.boundExactly(-6, factory.range(factory.tuple("-6"), factory.tuple("-6"))); + bounds.boundExactly(-5, factory.range(factory.tuple("-5"), factory.tuple("-5"))); + bounds.boundExactly(-4, factory.range(factory.tuple("-4"), factory.tuple("-4"))); + bounds.boundExactly(-3, factory.range(factory.tuple("-3"), factory.tuple("-3"))); + bounds.boundExactly(-2, factory.range(factory.tuple("-2"), factory.tuple("-2"))); + bounds.boundExactly(-1, factory.range(factory.tuple("-1"), factory.tuple("-1"))); + bounds.boundExactly(0, factory.range(factory.tuple("0"), factory.tuple("0"))); + bounds.boundExactly(1, factory.range(factory.tuple("1"), factory.tuple("1"))); + bounds.boundExactly(2, factory.range(factory.tuple("2"), factory.tuple("2"))); + bounds.boundExactly(3, factory.range(factory.tuple("3"), factory.tuple("3"))); + bounds.boundExactly(4, factory.range(factory.tuple("4"), factory.tuple("4"))); + bounds.boundExactly(5, factory.range(factory.tuple("5"), factory.tuple("5"))); + bounds.boundExactly(6, factory.range(factory.tuple("6"), factory.tuple("6"))); + bounds.boundExactly(7, factory.range(factory.tuple("7"), factory.tuple("7"))); + + Variable x12 = Variable.unary("this"); + Decls x11 = x12.oneOf(x7); + Expression x16 = x12.join(x8); + Expression x19 = x6.product(x6); + Expression x18 = x6.product(x19); + Expression x17 = x6.product(x18); + Formula x15 = x16.in(x17); + Variable x22 = Variable.unary("x22"); + Decls x21 = x22.oneOf(x6); + Expression x26 = x22.join(x16); + Expression x28 = x6.product(x6); + Expression x27 = x6.product(x28); + Formula x25 = x26.in(x27); + Variable x31 = Variable.unary("x31"); + Decls x30 = x31.oneOf(x6); + Expression x35 = x31.join(x26); + Expression x36 = x6.product(x6); + Formula x34 = x35.in(x36); + Variable x39 = Variable.unary("x39"); + Decls x38 = x39.oneOf(x6); + Expression x42 = x39.join(x35); + Formula x41 = x42.one(); + Formula x43 = x42.in(x6); + Formula x40 = x41.and(x43); + Formula x37 = x40.forAll(x38); + Formula x33 = x34.and(x37); + Variable x46 = Variable.unary("x46"); + Decls x45 = x46.oneOf(x6); + Expression x48 = x35.join(x46); + Formula x47 = x48.in(x6); + Formula x44 = x47.forAll(x45); + Formula x32 = x33.and(x44); + Formula x29 = x32.forAll(x30); + Formula x24 = x25.and(x29); + Variable x52 = Variable.unary("x52"); + Decls x51 = x52.oneOf(x6); + Variable x55 = Variable.unary("x55"); + Decls x54 = x55.oneOf(x6); + Decls x50 = x51.and(x54); + Expression x60 = x52.product(x55); + Expression x61 = x6.product(x6); + Formula x59 = x60.in(x61); + Variable x64 = Variable.unary("x64"); + Decls x63 = x64.oneOf(x6); + Expression x67 = x64.join(x60); + Formula x66 = x67.one(); + Formula x68 = x67.in(x6); + Formula x65 = x66.and(x68); + Formula x62 = x65.forAll(x63); + Formula x58 = x59.and(x62); + Variable x71 = Variable.unary("x71"); + Decls x70 = x71.oneOf(x6); + Expression x73 = x60.join(x71); + Formula x72 = x73.in(x6); + Formula x69 = x72.forAll(x70); + Formula x57 = x58.and(x69); + Expression x76 = x26.join(x55); + Expression x75 = x76.join(x52); + Formula x74 = x75.in(x6); + Formula x56 = x57.implies(x74); + Formula x49 = x56.forAll(x50); + Formula x23 = x24.and(x49); + Formula x20 = x23.forAll(x21); + Formula x14 = x15.and(x20); + Variable x80 = Variable.unary("x80"); + Decls x79 = x80.oneOf(x6); + Variable x82 = Variable.unary("x82"); + Decls x81 = x82.oneOf(x6); + Variable x84 = Variable.unary("x84"); + Decls x83 = x84.oneOf(x6); + Decls x78 = x79.and(x81).and(x83); + Expression x90 = x82.product(x84); + Expression x89 = x80.product(x90); + Expression x92 = x6.product(x6); + Expression x91 = x6.product(x92); + Formula x88 = x89.in(x91); + Variable x95 = Variable.unary("x95"); + Decls x94 = x95.oneOf(x6); + Expression x99 = x95.join(x89); + Expression x100 = x6.product(x6); + Formula x98 = x99.in(x100); + Variable x103 = Variable.unary("x103"); + Decls x102 = x103.oneOf(x6); + Expression x106 = x103.join(x99); + Formula x105 = x106.one(); + Formula x107 = x106.in(x6); + Formula x104 = x105.and(x107); + Formula x101 = x104.forAll(x102); + Formula x97 = x98.and(x101); + Variable x110 = Variable.unary("x110"); + Decls x109 = x110.oneOf(x6); + Expression x112 = x99.join(x110); + Formula x111 = x112.in(x6); + Formula x108 = x111.forAll(x109); + Formula x96 = x97.and(x108); + Formula x93 = x96.forAll(x94); + Formula x87 = x88.and(x93); + Variable x116 = Variable.unary("x116"); + Decls x115 = x116.oneOf(x6); + Variable x118 = Variable.unary("x118"); + Decls x117 = x118.oneOf(x6); + Decls x114 = x115.and(x117); + Expression x123 = x116.product(x118); + Expression x124 = x6.product(x6); + Formula x122 = x123.in(x124); + Variable x127 = Variable.unary("x127"); + Decls x126 = x127.oneOf(x6); + Expression x130 = x127.join(x123); + Formula x129 = x130.one(); + Formula x131 = x130.in(x6); + Formula x128 = x129.and(x131); + Formula x125 = x128.forAll(x126); + Formula x121 = x122.and(x125); + Variable x134 = Variable.unary("x134"); + Decls x133 = x134.oneOf(x6); + Expression x136 = x123.join(x134); + Formula x135 = x136.in(x6); + Formula x132 = x135.forAll(x133); + Formula x120 = x121.and(x132); + Expression x139 = x89.join(x118); + Expression x138 = x139.join(x116); + Formula x137 = x138.in(x6); + Formula x119 = x120.implies(x137); + Formula x113 = x119.forAll(x114); + Formula x86 = x87.and(x113); + Expression x143 = x16.join(x84); + Expression x142 = x143.join(x82); + Expression x141 = x142.join(x80); + Formula x140 = x141.in(x6); + Formula x85 = x86.implies(x140); + Formula x77 = x85.forAll(x78); + Formula x13 = x14.and(x77); + Formula x10 = x13.forAll(x11); + Expression x148 = x8.join(Expression.UNIV); + Expression x147 = x148.join(Expression.UNIV); + Expression x146 = x147.join(Expression.UNIV); + Expression x145 = x146.join(Expression.UNIV); + Formula x144 = x145.in(x7); + Formula x149 = x0.eq(x0); + Formula x150 = x1.eq(x1); + Formula x151 = x2.eq(x2); + Formula x152 = x3.eq(x3); + Formula x153 = x4.eq(x4); + Formula x154 = x5.eq(x5); + Formula x155 = x6.eq(x6); + Formula x156 = x7.eq(x7); + Formula x157 = x8.eq(x8); + Formula x9 = Formula.compose(FormulaOperator.AND, x10, x144, x149, x150, x151, x152, x153, + x154, x155, x156, x157); + + Solver solver = new Solver(); + solver.options().setSolver(SATFactory.DefaultSAT4J); + solver.options().setBitwidth(4); + solver.options().setFlatten(false); + solver.options().setIntEncoding(Options.IntEncoding.TWOSCOMPLEMENT); + solver.options().setSymmetryBreaking(20); + solver.options().setSkolemDepth(0); + + System.out.println(PrettyPrinter.print(x9, 0)); + + System.out.println("Solving..."); + System.out.flush(); + Solution sol = solver.solve(x9, bounds); + System.out.println(sol.toString()); + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/tmp/TestSmallFast.java b/Source/eu.modelwriter.alloyanalyzer/src/tmp/TestSmallFast.java new file mode 100644 index 00000000..9e89d3c2 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/tmp/TestSmallFast.java @@ -0,0 +1,328 @@ +package tmp; +import java.util.Arrays; +import java.util.List; +import kodkod.ast.*; +import kodkod.ast.operator.*; +import kodkod.instance.*; +import kodkod.util.nodes.PrettyPrinter; +import kodkod.engine.*; +import kodkod.engine.satlab.SATFactory; +import kodkod.engine.config.Options; + +public final class TestSmallFast { + + public static void main(String[] args) throws Exception { + + Relation x0 = Relation.unary("Int/min"); + Relation x1 = Relation.unary("Int/zero"); + Relation x2 = Relation.unary("Int/max"); + Relation x3 = Relation.nary("Int/next", 2); + Relation x4 = Relation.unary("seq/Int"); + Relation x5 = Relation.unary("String"); + Relation x6 = Relation.unary("this/A"); + Relation x7 = Relation.unary("this/Relation"); + Relation x8 = Relation.nary("this/Relation.r", 4); + + List atomlist = Arrays.asList("-1", "-2", "-3", "-4", "-5", "-6", "-7", "-8", "0", + "1", "2", "3", "4", "5", "6", "7", "A$0", "A$1", "A$2", "Relation$0", "Relation$1", + "Relation$2"); + + Universe universe = new Universe(atomlist); + TupleFactory factory = universe.factory(); + Bounds bounds = new Bounds(universe); + + TupleSet x0_upper = factory.noneOf(1); + x0_upper.add(factory.tuple("-8")); + bounds.boundExactly(x0, x0_upper); + + TupleSet x1_upper = factory.noneOf(1); + x1_upper.add(factory.tuple("0")); + bounds.boundExactly(x1, x1_upper); + + TupleSet x2_upper = factory.noneOf(1); + x2_upper.add(factory.tuple("7")); + bounds.boundExactly(x2, x2_upper); + + TupleSet x3_upper = factory.noneOf(2); + x3_upper.add(factory.tuple("-8").product(factory.tuple("-7"))); + x3_upper.add(factory.tuple("-7").product(factory.tuple("-6"))); + x3_upper.add(factory.tuple("-6").product(factory.tuple("-5"))); + x3_upper.add(factory.tuple("-5").product(factory.tuple("-4"))); + x3_upper.add(factory.tuple("-4").product(factory.tuple("-3"))); + x3_upper.add(factory.tuple("-3").product(factory.tuple("-2"))); + x3_upper.add(factory.tuple("-2").product(factory.tuple("-1"))); + x3_upper.add(factory.tuple("-1").product(factory.tuple("0"))); + x3_upper.add(factory.tuple("0").product(factory.tuple("1"))); + x3_upper.add(factory.tuple("1").product(factory.tuple("2"))); + x3_upper.add(factory.tuple("2").product(factory.tuple("3"))); + x3_upper.add(factory.tuple("3").product(factory.tuple("4"))); + x3_upper.add(factory.tuple("4").product(factory.tuple("5"))); + x3_upper.add(factory.tuple("5").product(factory.tuple("6"))); + x3_upper.add(factory.tuple("6").product(factory.tuple("7"))); + bounds.boundExactly(x3, x3_upper); + + TupleSet x4_upper = factory.noneOf(1); + x4_upper.add(factory.tuple("0")); + x4_upper.add(factory.tuple("1")); + x4_upper.add(factory.tuple("2")); + bounds.boundExactly(x4, x4_upper); + + TupleSet x5_upper = factory.noneOf(1); + bounds.boundExactly(x5, x5_upper); + + TupleSet x6_upper = factory.noneOf(1); + x6_upper.add(factory.tuple("A$0")); + x6_upper.add(factory.tuple("A$1")); + x6_upper.add(factory.tuple("A$2")); + bounds.boundExactly(x6, x6_upper); + + TupleSet x7_upper = factory.noneOf(1); + x7_upper.add(factory.tuple("Relation$0")); + x7_upper.add(factory.tuple("Relation$1")); + x7_upper.add(factory.tuple("Relation$2")); + bounds.boundExactly(x7, x7_upper); + + TupleSet x8_upper = factory.noneOf(4); + x8_upper.add(factory.tuple("Relation$0").product(factory.tuple("A$0")).product( + factory.tuple("A$0")).product(factory.tuple("A$0"))); + x8_upper.add(factory.tuple("Relation$0").product(factory.tuple("A$0")).product( + factory.tuple("A$0")).product(factory.tuple("A$1"))); + x8_upper.add(factory.tuple("Relation$0").product(factory.tuple("A$0")).product( + factory.tuple("A$0")).product(factory.tuple("A$2"))); + x8_upper.add(factory.tuple("Relation$0").product(factory.tuple("A$0")).product( + factory.tuple("A$1")).product(factory.tuple("A$0"))); + x8_upper.add(factory.tuple("Relation$0").product(factory.tuple("A$0")).product( + factory.tuple("A$1")).product(factory.tuple("A$1"))); + x8_upper.add(factory.tuple("Relation$0").product(factory.tuple("A$0")).product( + factory.tuple("A$1")).product(factory.tuple("A$2"))); + x8_upper.add(factory.tuple("Relation$0").product(factory.tuple("A$0")).product( + factory.tuple("A$2")).product(factory.tuple("A$0"))); + x8_upper.add(factory.tuple("Relation$0").product(factory.tuple("A$0")).product( + factory.tuple("A$2")).product(factory.tuple("A$1"))); + x8_upper.add(factory.tuple("Relation$0").product(factory.tuple("A$0")).product( + factory.tuple("A$2")).product(factory.tuple("A$2"))); + x8_upper.add(factory.tuple("Relation$0").product(factory.tuple("A$1")).product( + factory.tuple("A$0")).product(factory.tuple("A$0"))); + x8_upper.add(factory.tuple("Relation$0").product(factory.tuple("A$1")).product( + factory.tuple("A$0")).product(factory.tuple("A$1"))); + x8_upper.add(factory.tuple("Relation$0").product(factory.tuple("A$1")).product( + factory.tuple("A$0")).product(factory.tuple("A$2"))); + x8_upper.add(factory.tuple("Relation$0").product(factory.tuple("A$1")).product( + factory.tuple("A$1")).product(factory.tuple("A$0"))); + x8_upper.add(factory.tuple("Relation$0").product(factory.tuple("A$1")).product( + factory.tuple("A$1")).product(factory.tuple("A$1"))); + x8_upper.add(factory.tuple("Relation$0").product(factory.tuple("A$1")).product( + factory.tuple("A$1")).product(factory.tuple("A$2"))); + x8_upper.add(factory.tuple("Relation$0").product(factory.tuple("A$1")).product( + factory.tuple("A$2")).product(factory.tuple("A$0"))); + x8_upper.add(factory.tuple("Relation$0").product(factory.tuple("A$1")).product( + factory.tuple("A$2")).product(factory.tuple("A$1"))); + x8_upper.add(factory.tuple("Relation$0").product(factory.tuple("A$1")).product( + factory.tuple("A$2")).product(factory.tuple("A$2"))); + x8_upper.add(factory.tuple("Relation$0").product(factory.tuple("A$2")).product( + factory.tuple("A$0")).product(factory.tuple("A$0"))); + x8_upper.add(factory.tuple("Relation$0").product(factory.tuple("A$2")).product( + factory.tuple("A$0")).product(factory.tuple("A$1"))); + x8_upper.add(factory.tuple("Relation$0").product(factory.tuple("A$2")).product( + factory.tuple("A$0")).product(factory.tuple("A$2"))); + x8_upper.add(factory.tuple("Relation$0").product(factory.tuple("A$2")).product( + factory.tuple("A$1")).product(factory.tuple("A$0"))); + x8_upper.add(factory.tuple("Relation$0").product(factory.tuple("A$2")).product( + factory.tuple("A$1")).product(factory.tuple("A$1"))); + x8_upper.add(factory.tuple("Relation$0").product(factory.tuple("A$2")).product( + factory.tuple("A$1")).product(factory.tuple("A$2"))); + x8_upper.add(factory.tuple("Relation$0").product(factory.tuple("A$2")).product( + factory.tuple("A$2")).product(factory.tuple("A$0"))); + x8_upper.add(factory.tuple("Relation$0").product(factory.tuple("A$2")).product( + factory.tuple("A$2")).product(factory.tuple("A$1"))); + x8_upper.add(factory.tuple("Relation$0").product(factory.tuple("A$2")).product( + factory.tuple("A$2")).product(factory.tuple("A$2"))); + x8_upper.add(factory.tuple("Relation$1").product(factory.tuple("A$0")).product( + factory.tuple("A$0")).product(factory.tuple("A$0"))); + x8_upper.add(factory.tuple("Relation$1").product(factory.tuple("A$0")).product( + factory.tuple("A$0")).product(factory.tuple("A$1"))); + x8_upper.add(factory.tuple("Relation$1").product(factory.tuple("A$0")).product( + factory.tuple("A$0")).product(factory.tuple("A$2"))); + x8_upper.add(factory.tuple("Relation$1").product(factory.tuple("A$0")).product( + factory.tuple("A$1")).product(factory.tuple("A$0"))); + x8_upper.add(factory.tuple("Relation$1").product(factory.tuple("A$0")).product( + factory.tuple("A$1")).product(factory.tuple("A$1"))); + x8_upper.add(factory.tuple("Relation$1").product(factory.tuple("A$0")).product( + factory.tuple("A$1")).product(factory.tuple("A$2"))); + x8_upper.add(factory.tuple("Relation$1").product(factory.tuple("A$0")).product( + factory.tuple("A$2")).product(factory.tuple("A$0"))); + x8_upper.add(factory.tuple("Relation$1").product(factory.tuple("A$0")).product( + factory.tuple("A$2")).product(factory.tuple("A$1"))); + x8_upper.add(factory.tuple("Relation$1").product(factory.tuple("A$0")).product( + factory.tuple("A$2")).product(factory.tuple("A$2"))); + x8_upper.add(factory.tuple("Relation$1").product(factory.tuple("A$1")).product( + factory.tuple("A$0")).product(factory.tuple("A$0"))); + x8_upper.add(factory.tuple("Relation$1").product(factory.tuple("A$1")).product( + factory.tuple("A$0")).product(factory.tuple("A$1"))); + x8_upper.add(factory.tuple("Relation$1").product(factory.tuple("A$1")).product( + factory.tuple("A$0")).product(factory.tuple("A$2"))); + x8_upper.add(factory.tuple("Relation$1").product(factory.tuple("A$1")).product( + factory.tuple("A$1")).product(factory.tuple("A$0"))); + x8_upper.add(factory.tuple("Relation$1").product(factory.tuple("A$1")).product( + factory.tuple("A$1")).product(factory.tuple("A$1"))); + x8_upper.add(factory.tuple("Relation$1").product(factory.tuple("A$1")).product( + factory.tuple("A$1")).product(factory.tuple("A$2"))); + x8_upper.add(factory.tuple("Relation$1").product(factory.tuple("A$1")).product( + factory.tuple("A$2")).product(factory.tuple("A$0"))); + x8_upper.add(factory.tuple("Relation$1").product(factory.tuple("A$1")).product( + factory.tuple("A$2")).product(factory.tuple("A$1"))); + x8_upper.add(factory.tuple("Relation$1").product(factory.tuple("A$1")).product( + factory.tuple("A$2")).product(factory.tuple("A$2"))); + x8_upper.add(factory.tuple("Relation$1").product(factory.tuple("A$2")).product( + factory.tuple("A$0")).product(factory.tuple("A$0"))); + x8_upper.add(factory.tuple("Relation$1").product(factory.tuple("A$2")).product( + factory.tuple("A$0")).product(factory.tuple("A$1"))); + x8_upper.add(factory.tuple("Relation$1").product(factory.tuple("A$2")).product( + factory.tuple("A$0")).product(factory.tuple("A$2"))); + x8_upper.add(factory.tuple("Relation$1").product(factory.tuple("A$2")).product( + factory.tuple("A$1")).product(factory.tuple("A$0"))); + x8_upper.add(factory.tuple("Relation$1").product(factory.tuple("A$2")).product( + factory.tuple("A$1")).product(factory.tuple("A$1"))); + x8_upper.add(factory.tuple("Relation$1").product(factory.tuple("A$2")).product( + factory.tuple("A$1")).product(factory.tuple("A$2"))); + x8_upper.add(factory.tuple("Relation$1").product(factory.tuple("A$2")).product( + factory.tuple("A$2")).product(factory.tuple("A$0"))); + x8_upper.add(factory.tuple("Relation$1").product(factory.tuple("A$2")).product( + factory.tuple("A$2")).product(factory.tuple("A$1"))); + x8_upper.add(factory.tuple("Relation$1").product(factory.tuple("A$2")).product( + factory.tuple("A$2")).product(factory.tuple("A$2"))); + x8_upper.add(factory.tuple("Relation$2").product(factory.tuple("A$0")).product( + factory.tuple("A$0")).product(factory.tuple("A$0"))); + x8_upper.add(factory.tuple("Relation$2").product(factory.tuple("A$0")).product( + factory.tuple("A$0")).product(factory.tuple("A$1"))); + x8_upper.add(factory.tuple("Relation$2").product(factory.tuple("A$0")).product( + factory.tuple("A$0")).product(factory.tuple("A$2"))); + x8_upper.add(factory.tuple("Relation$2").product(factory.tuple("A$0")).product( + factory.tuple("A$1")).product(factory.tuple("A$0"))); + x8_upper.add(factory.tuple("Relation$2").product(factory.tuple("A$0")).product( + factory.tuple("A$1")).product(factory.tuple("A$1"))); + x8_upper.add(factory.tuple("Relation$2").product(factory.tuple("A$0")).product( + factory.tuple("A$1")).product(factory.tuple("A$2"))); + x8_upper.add(factory.tuple("Relation$2").product(factory.tuple("A$0")).product( + factory.tuple("A$2")).product(factory.tuple("A$0"))); + x8_upper.add(factory.tuple("Relation$2").product(factory.tuple("A$0")).product( + factory.tuple("A$2")).product(factory.tuple("A$1"))); + x8_upper.add(factory.tuple("Relation$2").product(factory.tuple("A$0")).product( + factory.tuple("A$2")).product(factory.tuple("A$2"))); + x8_upper.add(factory.tuple("Relation$2").product(factory.tuple("A$1")).product( + factory.tuple("A$0")).product(factory.tuple("A$0"))); + x8_upper.add(factory.tuple("Relation$2").product(factory.tuple("A$1")).product( + factory.tuple("A$0")).product(factory.tuple("A$1"))); + x8_upper.add(factory.tuple("Relation$2").product(factory.tuple("A$1")).product( + factory.tuple("A$0")).product(factory.tuple("A$2"))); + x8_upper.add(factory.tuple("Relation$2").product(factory.tuple("A$1")).product( + factory.tuple("A$1")).product(factory.tuple("A$0"))); + x8_upper.add(factory.tuple("Relation$2").product(factory.tuple("A$1")).product( + factory.tuple("A$1")).product(factory.tuple("A$1"))); + x8_upper.add(factory.tuple("Relation$2").product(factory.tuple("A$1")).product( + factory.tuple("A$1")).product(factory.tuple("A$2"))); + x8_upper.add(factory.tuple("Relation$2").product(factory.tuple("A$1")).product( + factory.tuple("A$2")).product(factory.tuple("A$0"))); + x8_upper.add(factory.tuple("Relation$2").product(factory.tuple("A$1")).product( + factory.tuple("A$2")).product(factory.tuple("A$1"))); + x8_upper.add(factory.tuple("Relation$2").product(factory.tuple("A$1")).product( + factory.tuple("A$2")).product(factory.tuple("A$2"))); + x8_upper.add(factory.tuple("Relation$2").product(factory.tuple("A$2")).product( + factory.tuple("A$0")).product(factory.tuple("A$0"))); + x8_upper.add(factory.tuple("Relation$2").product(factory.tuple("A$2")).product( + factory.tuple("A$0")).product(factory.tuple("A$1"))); + x8_upper.add(factory.tuple("Relation$2").product(factory.tuple("A$2")).product( + factory.tuple("A$0")).product(factory.tuple("A$2"))); + x8_upper.add(factory.tuple("Relation$2").product(factory.tuple("A$2")).product( + factory.tuple("A$1")).product(factory.tuple("A$0"))); + x8_upper.add(factory.tuple("Relation$2").product(factory.tuple("A$2")).product( + factory.tuple("A$1")).product(factory.tuple("A$1"))); + x8_upper.add(factory.tuple("Relation$2").product(factory.tuple("A$2")).product( + factory.tuple("A$1")).product(factory.tuple("A$2"))); + x8_upper.add(factory.tuple("Relation$2").product(factory.tuple("A$2")).product( + factory.tuple("A$2")).product(factory.tuple("A$0"))); + x8_upper.add(factory.tuple("Relation$2").product(factory.tuple("A$2")).product( + factory.tuple("A$2")).product(factory.tuple("A$1"))); + x8_upper.add(factory.tuple("Relation$2").product(factory.tuple("A$2")).product( + factory.tuple("A$2")).product(factory.tuple("A$2"))); + bounds.bound(x8, x8_upper); + + bounds.boundExactly(-8, factory.range(factory.tuple("-8"), factory.tuple("-8"))); + bounds.boundExactly(-7, factory.range(factory.tuple("-7"), factory.tuple("-7"))); + bounds.boundExactly(-6, factory.range(factory.tuple("-6"), factory.tuple("-6"))); + bounds.boundExactly(-5, factory.range(factory.tuple("-5"), factory.tuple("-5"))); + bounds.boundExactly(-4, factory.range(factory.tuple("-4"), factory.tuple("-4"))); + bounds.boundExactly(-3, factory.range(factory.tuple("-3"), factory.tuple("-3"))); + bounds.boundExactly(-2, factory.range(factory.tuple("-2"), factory.tuple("-2"))); + bounds.boundExactly(-1, factory.range(factory.tuple("-1"), factory.tuple("-1"))); + bounds.boundExactly(0, factory.range(factory.tuple("0"), factory.tuple("0"))); + bounds.boundExactly(1, factory.range(factory.tuple("1"), factory.tuple("1"))); + bounds.boundExactly(2, factory.range(factory.tuple("2"), factory.tuple("2"))); + bounds.boundExactly(3, factory.range(factory.tuple("3"), factory.tuple("3"))); + bounds.boundExactly(4, factory.range(factory.tuple("4"), factory.tuple("4"))); + bounds.boundExactly(5, factory.range(factory.tuple("5"), factory.tuple("5"))); + bounds.boundExactly(6, factory.range(factory.tuple("6"), factory.tuple("6"))); + bounds.boundExactly(7, factory.range(factory.tuple("7"), factory.tuple("7"))); + + Variable x12 = Variable.unary("this"); + Decls x11 = x12.oneOf(x7); + Expression x16 = x12.join(x8); + Expression x18 = x6.product(x6); + Expression x17 = x18.product(x6); + Formula x15 = x16.in(x17); + Variable x22 = Variable.unary("x22"); + Decls x21 = x22.oneOf(Expression.UNIV); + Variable x25 = Variable.unary("x25"); + Decls x24 = x25.oneOf(Expression.UNIV); + Decls x20 = x21.and(x24); + Expression x28 = x25.product(x22); + Expression x29 = x6.product(x6); + Formula x27 = x28.in(x29); + Expression x33 = x25.join(x16); + Expression x32 = x22.join(x33); + Formula x31 = x32.one(); + Formula x34 = x32.in(x6); + Formula x30 = x31.and(x34); + Formula x26 = x27.implies(x30); + Formula x19 = x26.forAll(x20); + Formula x14 = x15.and(x19); + Variable x37 = Variable.unary("x37"); + Decls x36 = x37.oneOf(x6); + Expression x39 = x16.join(x37); + Expression x40 = x6.product(x6); + Formula x38 = x39.in(x40); + Formula x35 = x38.forAll(x36); + Formula x13 = x14.and(x35); + Formula x10 = x13.forAll(x11); + Expression x44 = x8.join(Expression.UNIV); + Expression x43 = x44.join(Expression.UNIV); + Expression x42 = x43.join(Expression.UNIV); + Formula x41 = x42.in(x7); + Formula x45 = x0.eq(x0); + Formula x46 = x1.eq(x1); + Formula x47 = x2.eq(x2); + Formula x48 = x3.eq(x3); + Formula x49 = x4.eq(x4); + Formula x50 = x5.eq(x5); + Formula x51 = x6.eq(x6); + Formula x52 = x7.eq(x7); + Formula x53 = x8.eq(x8); + Formula x9 = Formula.compose(FormulaOperator.AND, x10, x41, x45, x46, x47, x48, x49, x50, + x51, x52, x53); + + Solver solver = new Solver(); + solver.options().setSolver(SATFactory.DefaultSAT4J); + solver.options().setBitwidth(4); + solver.options().setFlatten(false); + solver.options().setIntEncoding(Options.IntEncoding.TWOSCOMPLEMENT); + solver.options().setSymmetryBreaking(20); + solver.options().setSkolemDepth(0); + + System.out.println(PrettyPrinter.print(x9, 0)); + + System.out.println("Solving..."); + System.out.flush(); + Solution sol = solver.solve(x9, bounds); + System.out.println(sol.toString()); + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/tmp/TestSmallSlow.java b/Source/eu.modelwriter.alloyanalyzer/src/tmp/TestSmallSlow.java new file mode 100644 index 00000000..2826fb8b --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/tmp/TestSmallSlow.java @@ -0,0 +1,374 @@ +package tmp; +import java.util.Arrays; +import java.util.List; +import kodkod.ast.*; +import kodkod.ast.operator.*; +import kodkod.instance.*; +import kodkod.util.nodes.PrettyPrinter; +import kodkod.engine.*; +import kodkod.engine.satlab.SATFactory; +import kodkod.engine.config.Options; + +public final class TestSmallSlow { + + public static void main(String[] args) throws Exception { + + Relation x0 = Relation.unary("Int/min"); + Relation x1 = Relation.unary("Int/zero"); + Relation x2 = Relation.unary("Int/max"); + Relation x3 = Relation.nary("Int/next", 2); + Relation x4 = Relation.unary("seq/Int"); + Relation x5 = Relation.unary("String"); + Relation x6 = Relation.unary("this/A"); + Relation x7 = Relation.unary("this/Relation"); + Relation x8 = Relation.nary("this/Relation.r", 4); + + List atomlist = Arrays.asList("-1", "-2", "-3", "-4", "-5", "-6", "-7", "-8", "0", + "1", "2", "3", "4", "5", "6", "7", "A$0", "A$1", "A$2", "Relation$0", "unused0", + "unused1"); + + Universe universe = new Universe(atomlist); + TupleFactory factory = universe.factory(); + Bounds bounds = new Bounds(universe); + + TupleSet x0_upper = factory.noneOf(1); + x0_upper.add(factory.tuple("-8")); + bounds.boundExactly(x0, x0_upper); + + TupleSet x1_upper = factory.noneOf(1); + x1_upper.add(factory.tuple("0")); + bounds.boundExactly(x1, x1_upper); + + TupleSet x2_upper = factory.noneOf(1); + x2_upper.add(factory.tuple("7")); + bounds.boundExactly(x2, x2_upper); + + TupleSet x3_upper = factory.noneOf(2); + x3_upper.add(factory.tuple("-8").product(factory.tuple("-7"))); + x3_upper.add(factory.tuple("-7").product(factory.tuple("-6"))); + x3_upper.add(factory.tuple("-6").product(factory.tuple("-5"))); + x3_upper.add(factory.tuple("-5").product(factory.tuple("-4"))); + x3_upper.add(factory.tuple("-4").product(factory.tuple("-3"))); + x3_upper.add(factory.tuple("-3").product(factory.tuple("-2"))); + x3_upper.add(factory.tuple("-2").product(factory.tuple("-1"))); + x3_upper.add(factory.tuple("-1").product(factory.tuple("0"))); + x3_upper.add(factory.tuple("0").product(factory.tuple("1"))); + x3_upper.add(factory.tuple("1").product(factory.tuple("2"))); + x3_upper.add(factory.tuple("2").product(factory.tuple("3"))); + x3_upper.add(factory.tuple("3").product(factory.tuple("4"))); + x3_upper.add(factory.tuple("4").product(factory.tuple("5"))); + x3_upper.add(factory.tuple("5").product(factory.tuple("6"))); + x3_upper.add(factory.tuple("6").product(factory.tuple("7"))); + bounds.boundExactly(x3, x3_upper); + + TupleSet x4_upper = factory.noneOf(1); + x4_upper.add(factory.tuple("0")); + x4_upper.add(factory.tuple("1")); + x4_upper.add(factory.tuple("2")); + bounds.boundExactly(x4, x4_upper); + + TupleSet x5_upper = factory.noneOf(1); + bounds.boundExactly(x5, x5_upper); + + TupleSet x6_upper = factory.noneOf(1); + x6_upper.add(factory.tuple("A$0")); + x6_upper.add(factory.tuple("A$1")); + x6_upper.add(factory.tuple("A$2")); + bounds.boundExactly(x6, x6_upper); + + TupleSet x7_upper = factory.noneOf(1); + x7_upper.add(factory.tuple("unused0")); + x7_upper.add(factory.tuple("unused1")); + x7_upper.add(factory.tuple("Relation$0")); + bounds.bound(x7, x7_upper); + + TupleSet x8_upper = factory.noneOf(4); + x8_upper.add(factory.tuple("unused0").product(factory.tuple("A$0")).product( + factory.tuple("A$0")).product(factory.tuple("A$0"))); + x8_upper.add(factory.tuple("unused0").product(factory.tuple("A$0")).product( + factory.tuple("A$0")).product(factory.tuple("A$1"))); + x8_upper.add(factory.tuple("unused0").product(factory.tuple("A$0")).product( + factory.tuple("A$0")).product(factory.tuple("A$2"))); + x8_upper.add(factory.tuple("unused0").product(factory.tuple("A$0")).product( + factory.tuple("A$1")).product(factory.tuple("A$0"))); + x8_upper.add(factory.tuple("unused0").product(factory.tuple("A$0")).product( + factory.tuple("A$1")).product(factory.tuple("A$1"))); + x8_upper.add(factory.tuple("unused0").product(factory.tuple("A$0")).product( + factory.tuple("A$1")).product(factory.tuple("A$2"))); + x8_upper.add(factory.tuple("unused0").product(factory.tuple("A$0")).product( + factory.tuple("A$2")).product(factory.tuple("A$0"))); + x8_upper.add(factory.tuple("unused0").product(factory.tuple("A$0")).product( + factory.tuple("A$2")).product(factory.tuple("A$1"))); + x8_upper.add(factory.tuple("unused0").product(factory.tuple("A$0")).product( + factory.tuple("A$2")).product(factory.tuple("A$2"))); + x8_upper.add(factory.tuple("unused0").product(factory.tuple("A$1")).product( + factory.tuple("A$0")).product(factory.tuple("A$0"))); + x8_upper.add(factory.tuple("unused0").product(factory.tuple("A$1")).product( + factory.tuple("A$0")).product(factory.tuple("A$1"))); + x8_upper.add(factory.tuple("unused0").product(factory.tuple("A$1")).product( + factory.tuple("A$0")).product(factory.tuple("A$2"))); + x8_upper.add(factory.tuple("unused0").product(factory.tuple("A$1")).product( + factory.tuple("A$1")).product(factory.tuple("A$0"))); + x8_upper.add(factory.tuple("unused0").product(factory.tuple("A$1")).product( + factory.tuple("A$1")).product(factory.tuple("A$1"))); + x8_upper.add(factory.tuple("unused0").product(factory.tuple("A$1")).product( + factory.tuple("A$1")).product(factory.tuple("A$2"))); + x8_upper.add(factory.tuple("unused0").product(factory.tuple("A$1")).product( + factory.tuple("A$2")).product(factory.tuple("A$0"))); + x8_upper.add(factory.tuple("unused0").product(factory.tuple("A$1")).product( + factory.tuple("A$2")).product(factory.tuple("A$1"))); + x8_upper.add(factory.tuple("unused0").product(factory.tuple("A$1")).product( + factory.tuple("A$2")).product(factory.tuple("A$2"))); + x8_upper.add(factory.tuple("unused0").product(factory.tuple("A$2")).product( + factory.tuple("A$0")).product(factory.tuple("A$0"))); + x8_upper.add(factory.tuple("unused0").product(factory.tuple("A$2")).product( + factory.tuple("A$0")).product(factory.tuple("A$1"))); + x8_upper.add(factory.tuple("unused0").product(factory.tuple("A$2")).product( + factory.tuple("A$0")).product(factory.tuple("A$2"))); + x8_upper.add(factory.tuple("unused0").product(factory.tuple("A$2")).product( + factory.tuple("A$1")).product(factory.tuple("A$0"))); + x8_upper.add(factory.tuple("unused0").product(factory.tuple("A$2")).product( + factory.tuple("A$1")).product(factory.tuple("A$1"))); + x8_upper.add(factory.tuple("unused0").product(factory.tuple("A$2")).product( + factory.tuple("A$1")).product(factory.tuple("A$2"))); + x8_upper.add(factory.tuple("unused0").product(factory.tuple("A$2")).product( + factory.tuple("A$2")).product(factory.tuple("A$0"))); + x8_upper.add(factory.tuple("unused0").product(factory.tuple("A$2")).product( + factory.tuple("A$2")).product(factory.tuple("A$1"))); + x8_upper.add(factory.tuple("unused0").product(factory.tuple("A$2")).product( + factory.tuple("A$2")).product(factory.tuple("A$2"))); + x8_upper.add(factory.tuple("unused1").product(factory.tuple("A$0")).product( + factory.tuple("A$0")).product(factory.tuple("A$0"))); + x8_upper.add(factory.tuple("unused1").product(factory.tuple("A$0")).product( + factory.tuple("A$0")).product(factory.tuple("A$1"))); + x8_upper.add(factory.tuple("unused1").product(factory.tuple("A$0")).product( + factory.tuple("A$0")).product(factory.tuple("A$2"))); + x8_upper.add(factory.tuple("unused1").product(factory.tuple("A$0")).product( + factory.tuple("A$1")).product(factory.tuple("A$0"))); + x8_upper.add(factory.tuple("unused1").product(factory.tuple("A$0")).product( + factory.tuple("A$1")).product(factory.tuple("A$1"))); + x8_upper.add(factory.tuple("unused1").product(factory.tuple("A$0")).product( + factory.tuple("A$1")).product(factory.tuple("A$2"))); + x8_upper.add(factory.tuple("unused1").product(factory.tuple("A$0")).product( + factory.tuple("A$2")).product(factory.tuple("A$0"))); + x8_upper.add(factory.tuple("unused1").product(factory.tuple("A$0")).product( + factory.tuple("A$2")).product(factory.tuple("A$1"))); + x8_upper.add(factory.tuple("unused1").product(factory.tuple("A$0")).product( + factory.tuple("A$2")).product(factory.tuple("A$2"))); + x8_upper.add(factory.tuple("unused1").product(factory.tuple("A$1")).product( + factory.tuple("A$0")).product(factory.tuple("A$0"))); + x8_upper.add(factory.tuple("unused1").product(factory.tuple("A$1")).product( + factory.tuple("A$0")).product(factory.tuple("A$1"))); + x8_upper.add(factory.tuple("unused1").product(factory.tuple("A$1")).product( + factory.tuple("A$0")).product(factory.tuple("A$2"))); + x8_upper.add(factory.tuple("unused1").product(factory.tuple("A$1")).product( + factory.tuple("A$1")).product(factory.tuple("A$0"))); + x8_upper.add(factory.tuple("unused1").product(factory.tuple("A$1")).product( + factory.tuple("A$1")).product(factory.tuple("A$1"))); + x8_upper.add(factory.tuple("unused1").product(factory.tuple("A$1")).product( + factory.tuple("A$1")).product(factory.tuple("A$2"))); + x8_upper.add(factory.tuple("unused1").product(factory.tuple("A$1")).product( + factory.tuple("A$2")).product(factory.tuple("A$0"))); + x8_upper.add(factory.tuple("unused1").product(factory.tuple("A$1")).product( + factory.tuple("A$2")).product(factory.tuple("A$1"))); + x8_upper.add(factory.tuple("unused1").product(factory.tuple("A$1")).product( + factory.tuple("A$2")).product(factory.tuple("A$2"))); + x8_upper.add(factory.tuple("unused1").product(factory.tuple("A$2")).product( + factory.tuple("A$0")).product(factory.tuple("A$0"))); + x8_upper.add(factory.tuple("unused1").product(factory.tuple("A$2")).product( + factory.tuple("A$0")).product(factory.tuple("A$1"))); + x8_upper.add(factory.tuple("unused1").product(factory.tuple("A$2")).product( + factory.tuple("A$0")).product(factory.tuple("A$2"))); + x8_upper.add(factory.tuple("unused1").product(factory.tuple("A$2")).product( + factory.tuple("A$1")).product(factory.tuple("A$0"))); + x8_upper.add(factory.tuple("unused1").product(factory.tuple("A$2")).product( + factory.tuple("A$1")).product(factory.tuple("A$1"))); + x8_upper.add(factory.tuple("unused1").product(factory.tuple("A$2")).product( + factory.tuple("A$1")).product(factory.tuple("A$2"))); + x8_upper.add(factory.tuple("unused1").product(factory.tuple("A$2")).product( + factory.tuple("A$2")).product(factory.tuple("A$0"))); + x8_upper.add(factory.tuple("unused1").product(factory.tuple("A$2")).product( + factory.tuple("A$2")).product(factory.tuple("A$1"))); + x8_upper.add(factory.tuple("unused1").product(factory.tuple("A$2")).product( + factory.tuple("A$2")).product(factory.tuple("A$2"))); + x8_upper.add(factory.tuple("Relation$0").product(factory.tuple("A$0")).product( + factory.tuple("A$0")).product(factory.tuple("A$0"))); + x8_upper.add(factory.tuple("Relation$0").product(factory.tuple("A$0")).product( + factory.tuple("A$0")).product(factory.tuple("A$1"))); + x8_upper.add(factory.tuple("Relation$0").product(factory.tuple("A$0")).product( + factory.tuple("A$0")).product(factory.tuple("A$2"))); + x8_upper.add(factory.tuple("Relation$0").product(factory.tuple("A$0")).product( + factory.tuple("A$1")).product(factory.tuple("A$0"))); + x8_upper.add(factory.tuple("Relation$0").product(factory.tuple("A$0")).product( + factory.tuple("A$1")).product(factory.tuple("A$1"))); + x8_upper.add(factory.tuple("Relation$0").product(factory.tuple("A$0")).product( + factory.tuple("A$1")).product(factory.tuple("A$2"))); + x8_upper.add(factory.tuple("Relation$0").product(factory.tuple("A$0")).product( + factory.tuple("A$2")).product(factory.tuple("A$0"))); + x8_upper.add(factory.tuple("Relation$0").product(factory.tuple("A$0")).product( + factory.tuple("A$2")).product(factory.tuple("A$1"))); + x8_upper.add(factory.tuple("Relation$0").product(factory.tuple("A$0")).product( + factory.tuple("A$2")).product(factory.tuple("A$2"))); + x8_upper.add(factory.tuple("Relation$0").product(factory.tuple("A$1")).product( + factory.tuple("A$0")).product(factory.tuple("A$0"))); + x8_upper.add(factory.tuple("Relation$0").product(factory.tuple("A$1")).product( + factory.tuple("A$0")).product(factory.tuple("A$1"))); + x8_upper.add(factory.tuple("Relation$0").product(factory.tuple("A$1")).product( + factory.tuple("A$0")).product(factory.tuple("A$2"))); + x8_upper.add(factory.tuple("Relation$0").product(factory.tuple("A$1")).product( + factory.tuple("A$1")).product(factory.tuple("A$0"))); + x8_upper.add(factory.tuple("Relation$0").product(factory.tuple("A$1")).product( + factory.tuple("A$1")).product(factory.tuple("A$1"))); + x8_upper.add(factory.tuple("Relation$0").product(factory.tuple("A$1")).product( + factory.tuple("A$1")).product(factory.tuple("A$2"))); + x8_upper.add(factory.tuple("Relation$0").product(factory.tuple("A$1")).product( + factory.tuple("A$2")).product(factory.tuple("A$0"))); + x8_upper.add(factory.tuple("Relation$0").product(factory.tuple("A$1")).product( + factory.tuple("A$2")).product(factory.tuple("A$1"))); + x8_upper.add(factory.tuple("Relation$0").product(factory.tuple("A$1")).product( + factory.tuple("A$2")).product(factory.tuple("A$2"))); + x8_upper.add(factory.tuple("Relation$0").product(factory.tuple("A$2")).product( + factory.tuple("A$0")).product(factory.tuple("A$0"))); + x8_upper.add(factory.tuple("Relation$0").product(factory.tuple("A$2")).product( + factory.tuple("A$0")).product(factory.tuple("A$1"))); + x8_upper.add(factory.tuple("Relation$0").product(factory.tuple("A$2")).product( + factory.tuple("A$0")).product(factory.tuple("A$2"))); + x8_upper.add(factory.tuple("Relation$0").product(factory.tuple("A$2")).product( + factory.tuple("A$1")).product(factory.tuple("A$0"))); + x8_upper.add(factory.tuple("Relation$0").product(factory.tuple("A$2")).product( + factory.tuple("A$1")).product(factory.tuple("A$1"))); + x8_upper.add(factory.tuple("Relation$0").product(factory.tuple("A$2")).product( + factory.tuple("A$1")).product(factory.tuple("A$2"))); + x8_upper.add(factory.tuple("Relation$0").product(factory.tuple("A$2")).product( + factory.tuple("A$2")).product(factory.tuple("A$0"))); + x8_upper.add(factory.tuple("Relation$0").product(factory.tuple("A$2")).product( + factory.tuple("A$2")).product(factory.tuple("A$1"))); + x8_upper.add(factory.tuple("Relation$0").product(factory.tuple("A$2")).product( + factory.tuple("A$2")).product(factory.tuple("A$2"))); + bounds.bound(x8, x8_upper); + + bounds.boundExactly(-8, factory.range(factory.tuple("-8"), factory.tuple("-8"))); + bounds.boundExactly(-7, factory.range(factory.tuple("-7"), factory.tuple("-7"))); + bounds.boundExactly(-6, factory.range(factory.tuple("-6"), factory.tuple("-6"))); + bounds.boundExactly(-5, factory.range(factory.tuple("-5"), factory.tuple("-5"))); + bounds.boundExactly(-4, factory.range(factory.tuple("-4"), factory.tuple("-4"))); + bounds.boundExactly(-3, factory.range(factory.tuple("-3"), factory.tuple("-3"))); + bounds.boundExactly(-2, factory.range(factory.tuple("-2"), factory.tuple("-2"))); + bounds.boundExactly(-1, factory.range(factory.tuple("-1"), factory.tuple("-1"))); + bounds.boundExactly(0, factory.range(factory.tuple("0"), factory.tuple("0"))); + bounds.boundExactly(1, factory.range(factory.tuple("1"), factory.tuple("1"))); + bounds.boundExactly(2, factory.range(factory.tuple("2"), factory.tuple("2"))); + bounds.boundExactly(3, factory.range(factory.tuple("3"), factory.tuple("3"))); + bounds.boundExactly(4, factory.range(factory.tuple("4"), factory.tuple("4"))); + bounds.boundExactly(5, factory.range(factory.tuple("5"), factory.tuple("5"))); + bounds.boundExactly(6, factory.range(factory.tuple("6"), factory.tuple("6"))); + bounds.boundExactly(7, factory.range(factory.tuple("7"), factory.tuple("7"))); + + Variable x12 = Variable.unary("this"); + Decls x11 = x12.oneOf(x7); + Expression x16 = x12.join(x8); + Expression x18 = x6.product(x6); + Expression x17 = x6.product(x18); + Formula x15 = x16.in(x17); + Variable x21 = Variable.unary("x21"); + Decls x20 = x21.oneOf(x6); + Expression x25 = x21.join(x16); + Expression x26 = x6.product(x6); + Formula x24 = x25.in(x26); + Variable x29 = Variable.unary("x29"); + Decls x28 = x29.oneOf(x6); + Expression x32 = x29.join(x25); + Formula x31 = x32.one(); + Formula x33 = x32.in(x6); + Formula x30 = x31.and(x33); + Formula x27 = x30.forAll(x28); + Formula x23 = x24.and(x27); + Variable x36 = Variable.unary("x36"); + Decls x35 = x36.oneOf(x6); + Expression x38 = x25.join(x36); + Formula x37 = x38.in(x6); + Formula x34 = x37.forAll(x35); + Formula x22 = x23.and(x34); + Formula x19 = x22.forAll(x20); + Formula x14 = x15.and(x19); + Variable x42 = Variable.unary("x42"); + Decls x41 = x42.oneOf(Expression.UNIV); + Variable x45 = Variable.unary("x45"); + Decls x44 = x45.oneOf(Expression.UNIV); + Decls x40 = x41.and(x44); + Expression x50 = x42.product(x45); + Expression x51 = x6.product(x6); + Formula x49 = x50.in(x51); + Variable x54 = Variable.unary("x54"); + Decls x53 = x54.oneOf(x6); + Expression x57 = x54.join(x50); + Formula x56 = x57.one(); + Formula x58 = x57.in(x6); + Formula x55 = x56.and(x58); + Formula x52 = x55.forAll(x53); + Formula x48 = x49.and(x52); + Variable x61 = Variable.unary("x61"); + Decls x60 = x61.oneOf(x6); + Expression x63 = x50.join(x61); + Formula x62 = x63.in(x6); + Formula x59 = x62.forAll(x60); + Formula x47 = x48.and(x59); + Expression x66 = x16.join(x45); + Expression x65 = x66.join(x42); + Formula x64 = x65.in(x6); + Formula x46 = x47.implies(x64); + Formula x39 = x46.forAll(x40); + Formula x13 = x14.and(x39); + Formula x10 = x13.forAll(x11); + Expression x70 = x8.join(Expression.UNIV); + Expression x69 = x70.join(Expression.UNIV); + Expression x68 = x69.join(Expression.UNIV); + Formula x67 = x68.in(x7); + Formula x71 = x0.eq(x0); + Formula x72 = x1.eq(x1); + Formula x73 = x2.eq(x2); + Formula x74 = x3.eq(x3); + Formula x75 = x4.eq(x4); + Formula x76 = x5.eq(x5); + Formula x77 = x6.eq(x6); + Formula x78 = x7.eq(x7); + Formula x79 = x8.eq(x8); + Formula x9 = Formula.compose(FormulaOperator.AND, x10, x67, x71, x72, x73, x74, x75, x76, + x77, x78, x79); + + Solver solver = new Solver(); + solver.options().setSolver(SATFactory.DefaultSAT4J); + solver.options().setBitwidth(4); + solver.options().setFlatten(false); + solver.options().setIntEncoding(Options.IntEncoding.TWOSCOMPLEMENT); + solver.options().setSymmetryBreaking(20); + solver.options().setSkolemDepth(0); + + System.out.println(PrettyPrinter.print(x9, 0)); + System.out.println(bounds); + + System.out.println("Solving..."); + System.out.flush(); + Solution sol = solver.solve(x9, bounds); + System.out.println(sol.toString()); + + Instance inst = sol.instance(); + Evaluator ev = new Evaluator(inst); + + System.out.println("Universe: " + ev.evaluate(Expression.UNIV)); + Formula xx = x46.forAll(x40).forAll(x11); + System.out.println(PrettyPrinter.print(xx, 2)); + System.out.println(ev.evaluate(xx)); + + System.out.println(PrettyPrinter.print(x46, 4)); + +// Variable r = Variable.unary("this"); +// Variable u1 = Variable.unary("u1"); +// Variable u2 = Variable.unary("u2"); +// +// Formula ff = u1.product(u2).in(x6.product(x6)).forAll(u1.oneOf(Expression.UNIV).and(u2.oneOf(Expression.UNIV))).forAll(r.oneOf(x7)); +// System.out.println(PrettyPrinter.print(ff, 0)); +// System.out.println(ev.evaluate(ff)); + + } +} diff --git a/Source/eu.modelwriter.alloyanalyzer/src/tmp/TestXml.java b/Source/eu.modelwriter.alloyanalyzer/src/tmp/TestXml.java new file mode 100644 index 00000000..ed602823 --- /dev/null +++ b/Source/eu.modelwriter.alloyanalyzer/src/tmp/TestXml.java @@ -0,0 +1,243 @@ +package tmp; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.prefs.Preferences; + +import javax.swing.JDialog; + +import edu.mit.csail.sdg.alloy4.A4Reporter; +import edu.mit.csail.sdg.alloy4.Computer; +import edu.mit.csail.sdg.alloy4.Err; +import edu.mit.csail.sdg.alloy4.ErrorFatal; +import edu.mit.csail.sdg.alloy4.ErrorType; +import edu.mit.csail.sdg.alloy4.Util; +import edu.mit.csail.sdg.alloy4.Util.BooleanPref; +import edu.mit.csail.sdg.alloy4.Version; +import edu.mit.csail.sdg.alloy4.XMLNode; +import edu.mit.csail.sdg.alloy4compiler.ast.Expr; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprVar; +import edu.mit.csail.sdg.alloy4compiler.ast.Module; +import edu.mit.csail.sdg.alloy4compiler.ast.Sig; +import edu.mit.csail.sdg.alloy4compiler.ast.Sig.Field; +import edu.mit.csail.sdg.alloy4compiler.parser.CompUtil; +import edu.mit.csail.sdg.alloy4compiler.sim.SimInstance; +import edu.mit.csail.sdg.alloy4compiler.sim.SimTuple; +import edu.mit.csail.sdg.alloy4compiler.sim.SimTupleset; +import edu.mit.csail.sdg.alloy4compiler.translator.A4Solution; +import edu.mit.csail.sdg.alloy4compiler.translator.A4SolutionReader; +import edu.mit.csail.sdg.alloy4compiler.translator.A4Tuple; +import edu.mit.csail.sdg.alloy4compiler.translator.A4TupleSet; +import edu.mit.csail.sdg.alloy4viz.AlloyInstance; +import edu.mit.csail.sdg.alloy4viz.StaticInstanceReader; +import edu.mit.csail.sdg.alloy4viz.VizGUI; +import edu.mit.csail.sdg.alloy4viz.VizGraphPanel; +import edu.mit.csail.sdg.alloy4viz.VizState; +import kodkod.engine.fol2sat.HigherOrderDeclException; + +public class TestXml { + + private static final BooleanPref ImplicitThis = new BooleanPref("ImplicitThis"); + // static String filename = "C:\\Users\\3\\Desktop\\Alloyyy\\alloyXmlSample.xml"; + // C:\\Users\\Mete\\runtime-EclipseApplication\\.modelwriter\\persistance.xml + static String filename = + "C:\\Users\\Mete\\runtime-EclipseApplication\\.modelwriter\\persistence.xml"; + + public static void showViz() { + final String xmlFileName = Util.canon(filename); + File f = new File(xmlFileName); + AlloyInstance myInstance = null; + try { + if (!f.exists()) + throw new IOException("File " + xmlFileName + " does not exist."); + myInstance = StaticInstanceReader.parseInstance(f); + } catch (Err e1) { + + e1.printStackTrace(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + VizState myState = null; + + myState = new VizState(myInstance); + + VizGraphPanel graph = new VizGraphPanel(myState, false); + JDialog dialog = new JDialog(); + dialog.add(graph); + dialog.setVisible(true); + dialog.pack(); + + } + + + public static void main(String[] args) { + + // VizGUI gui = new VizGUI(false, filename, null); + // gui.loadXML(filename, false); + + showViz(); + + Computer evaluator = new Computer() { + // private String filename = null; + + public final String compute(final Object input) throws Exception { + // if (input instanceof File) { + // filename = ((File) input).getAbsolutePath(); + // return ""; + // } + if (!(input instanceof String)) + return ""; + final String str = (String) input; + if (str.trim().length() == 0) + return ""; // Empty line + Module root = null; + A4Solution ans = null; + try { + Map fc = new LinkedHashMap(); + XMLNode x = new XMLNode(new File(filename)); + if (!x.is("alloy")) + throw new Exception(); + String mainname = null; + for (XMLNode sub : x) + if (sub.is("instance")) { + mainname = sub.getAttribute("filename"); + break; + } + if (mainname == null) + throw new Exception(); + for (XMLNode sub : x) + if (sub.is("source")) { + String name = sub.getAttribute("filename"); + String content = sub.getAttribute("content"); + fc.put(name, content); + } + root = CompUtil.parseEverything_fromFile(A4Reporter.NOP, fc, mainname, + (Version.experimental && ImplicitThis.get()) ? 2 : 1); + ans = A4SolutionReader.read(root.getAllReachableSigs(), x); + for (ExprVar a : ans.getAllAtoms()) { + root.addGlobal(a.label, a); + } + for (ExprVar a : ans.getAllSkolems()) { + root.addGlobal(a.label, a); + } + } catch (Throwable ex) { + throw new ErrorFatal("Failed to read or parse the XML file."); + } + try { + Expr e = CompUtil.parseOneExpression_fromString(root, str); + if ("yes".equals(System.getProperty("debug")) && Verbosity.get() == Verbosity.FULLDEBUG) { + SimInstance simInst = convert(root, ans); + return simInst.visitThis(e).toString() + (simInst.wasOverflow() ? " (OF)" : ""); + } else + return ans.eval(e).toString(); + } catch (HigherOrderDeclException ex) { + throw new ErrorType("Higher-order quantification is not allowed in the evaluator."); + } + } + }; + + VizGUI gui = new VizGUI(false, filename, null, null, evaluator); + try { + String str = evaluator.compute("eClassifiers"); + + System.out.println(str); + } catch (Exception e) { + + e.printStackTrace(); + } + } + + private static SimTupleset convert(Object object) throws Err { + if (!(object instanceof A4TupleSet)) + throw new ErrorFatal("Unexpected type error: expecting an A4TupleSet."); + A4TupleSet s = (A4TupleSet) object; + if (s.size() == 0) + return SimTupleset.EMPTY; + List list = new ArrayList(s.size()); + int arity = s.arity(); + for (A4Tuple t : s) { + String[] array = new String[arity]; + for (int i = 0; i < t.arity(); i++) + array[i] = t.atom(i); + list.add(SimTuple.make(array)); + } + return SimTupleset.make(list); + } + + private static SimInstance convert(Module root, A4Solution ans) throws Err { + SimInstance ct = new SimInstance(root, ans.getBitwidth(), ans.getMaxSeq()); + for (Sig s : ans.getAllReachableSigs()) { + if (!s.builtin) + ct.init(s, convert(ans.eval(s))); + for (Field f : s.getFields()) + if (!f.defined) + ct.init(f, convert(ans.eval(f))); + } + for (ExprVar a : ans.getAllAtoms()) + ct.init(a, convert(ans.eval(a))); + for (ExprVar a : ans.getAllSkolems()) + ct.init(a, convert(ans.eval(a))); + return ct; + } + + private enum Verbosity { + /** Level 0. */ + DEFAULT("0", "low"), + /** Level 1. */ + VERBOSE("1", "medium"), + /** Level 2. */ + DEBUG("2", "high"), + /** Level 3. */ + FULLDEBUG("3", "debug only"); + /** Returns true if it is greater than or equal to "other". */ + public boolean geq(Verbosity other) { + return ordinal() >= other.ordinal(); + } + + /** This is a unique String for this value; it should be kept consistent in future versions. */ + private final String id; + /** This is the label that the toString() method will return. */ + private final String label; + + /** Constructs a new Verbosity value with the given id and label. */ + private Verbosity(String id, String label) { + this.id = id; + this.label = label; + } + + /** + * Given an id, return the enum value corresponding to it (if there's no match, then return + * DEFAULT). + */ + private static Verbosity parse(String id) { + for (Verbosity vb : values()) + if (vb.id.equals(id)) + return vb; + return DEFAULT; + } + + /** Returns the human-readable label for this enum value. */ + @Override + public final String toString() { + return label; + } + + /** Saves this value into the Java preference object. */ + private void set() { + Preferences.userNodeForPackage(Util.class).put("Verbosity", id); + } + + /** + * Reads the current value of the Java preference object (if it's not set, then return DEFAULT). + */ + private static Verbosity get() { + return parse(Preferences.userNodeForPackage(Util.class).get("Verbosity", "")); + } + }; + +} diff --git a/Source/eu.modelwriter.configuration/.classpath b/Source/eu.modelwriter.configuration/.classpath new file mode 100644 index 00000000..eca7bdba --- /dev/null +++ b/Source/eu.modelwriter.configuration/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/Source/eu.modelwriter.configuration/.gitignore b/Source/eu.modelwriter.configuration/.gitignore new file mode 100644 index 00000000..ae3c1726 --- /dev/null +++ b/Source/eu.modelwriter.configuration/.gitignore @@ -0,0 +1 @@ +/bin/ diff --git a/Source/eu.modelwriter.configuration/.project b/Source/eu.modelwriter.configuration/.project new file mode 100644 index 00000000..3419f1a1 --- /dev/null +++ b/Source/eu.modelwriter.configuration/.project @@ -0,0 +1,28 @@ + + + eu.modelwriter.configuration + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + diff --git a/Source/eu.modelwriter.configuration/.settings/org.eclipse.jdt.core.prefs b/Source/eu.modelwriter.configuration/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 00000000..0c68a61d --- /dev/null +++ b/Source/eu.modelwriter.configuration/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,7 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 +org.eclipse.jdt.core.compiler.compliance=1.8 +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.8 diff --git a/Source/eu.modelwriter.configuration/.settings/org.eclipse.pde.core.prefs b/Source/eu.modelwriter.configuration/.settings/org.eclipse.pde.core.prefs new file mode 100644 index 00000000..923c37fb --- /dev/null +++ b/Source/eu.modelwriter.configuration/.settings/org.eclipse.pde.core.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +resolve.requirebundle=false diff --git a/Source/eu.modelwriter.configuration/FileSystem.als b/Source/eu.modelwriter.configuration/FileSystem.als new file mode 100644 index 00000000..8ee65292 --- /dev/null +++ b/Source/eu.modelwriter.configuration/FileSystem.als @@ -0,0 +1,21 @@ +module ferhat/FileSystem + +abstract sig Object {} +sig Directory extends Object { contents: set Object} +one sig Root extends Directory { } +sig File extends Object { } +sig Alias extends Object { refs: set Word } +sig Word extends File { } + +fact { + no d:Directory | d in d.^contents --no directory cycles + Object in Root.*contents --each object is reacble from the root + all o: Object | lone o.~contents --each object has at most one parent + no disj a, a': Alias | some a.refs & a'.refs --no overlapping references to objects, + --each references maps each Alias to different objects +} + + + +pred example {#Alias >2 #refs> 2 #Directory >2} +run example for exactly 10 Object diff --git a/Source/eu.modelwriter.configuration/Helper.als b/Source/eu.modelwriter.configuration/Helper.als new file mode 100644 index 00000000..f7df902d --- /dev/null +++ b/Source/eu.modelwriter.configuration/Helper.als @@ -0,0 +1,4 @@ +module ferhat/Helper + +/* Helper Predicates*/ +pred acyclic [s: set univ, r: univ->univ] { no x: s | x in x.^r } diff --git a/Source/eu.modelwriter.configuration/META-INF/MANIFEST.MF b/Source/eu.modelwriter.configuration/META-INF/MANIFEST.MF new file mode 100644 index 00000000..99f78c89 --- /dev/null +++ b/Source/eu.modelwriter.configuration/META-INF/MANIFEST.MF @@ -0,0 +1,35 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: Configuration +Bundle-SymbolicName: eu.modelwriter.configuration;singleton:=true +Bundle-Version: 1.0.0.qualifier +Bundle-Activator: eu.modelwriter.configuration.Activator +Require-Bundle: org.eclipse.ui, + org.eclipse.core.runtime, + org.eclipse.core.resources, + eu.modelwriter.traceability.core.persistence;bundle-version="1.0.0", + eu.modelwriter.alloyanalyzer, + org.eclipse.emf.ecore +Bundle-RequiredExecutionEnvironment: JavaSE-1.8 +Bundle-ActivationPolicy: lazy +Export-Package: eu.modelwriter.configuration.alloy, + eu.modelwriter.configuration.alloy.analysis, + eu.modelwriter.configuration.alloy.analysis.evaluator, + eu.modelwriter.configuration.alloy.analysis.provider, + eu.modelwriter.configuration.alloy.trace, + eu.modelwriter.configuration.alloy2emf, + eu.modelwriter.configuration.emf2alloy, + eu.modelwriter.configuration.generation, + eu.modelwriter.configuration.internal; + uses:="org.eclipse.emf.ecore, + eu.modelwriter.traceability.core.persistence, + org.eclipse.emf.common.util, + org.eclipse.core.resources, + org.eclipse.emf.ecore.resource, + edu.mit.csail.sdg.alloy4viz, + org.eclipse.jface.viewers", + eu.modelwriter.configuration.synthesis;uses:="org.eclipse.emf.ecore,org.eclipse.core.resources" +Import-Package: eu.modelwriter.marker.internal, + org.eclipse.emf.ecore.resource, + org.eclipse.jface.text +Bundle-ClassPath: . diff --git a/Source/eu.modelwriter.configuration/RequirementNoOpposite.als b/Source/eu.modelwriter.configuration/RequirementNoOpposite.als new file mode 100644 index 00000000..f764fad4 --- /dev/null +++ b/Source/eu.modelwriter.configuration/RequirementNoOpposite.als @@ -0,0 +1,84 @@ +module ferhat/Requirement + +abstract sig Element {} + +--@exclude +abstract sig State {} + +abstract sig ElementState extends State {} +one sig Deleted, Changed, Created extends ElementState {} + +abstract sig TaskState extends State {} +one sig Done, ToDo, Cancelled, Ready extends TaskState {} + +abstract sig Requirement extends Element { + state: one ElementState +} + +sig Task extends Element { + precede: lone Task, + state: one TaskState +}{ all t: Task | one t.~task} + + +one sig Project extends Requirement { + requirement: some ContractRequirement +} + +sig ContractRequirement extends Requirement { + system: set SystemRequirement, + relate: set ContractRequirement +}{all c: ContractRequirement | one c.~requirement} + +--@name: "System Requirement" +sig SystemRequirement extends Requirement { + child: some Implementation +}{ all s: SystemRequirement | one s.~system} + +abstract sig Implementation extends Requirement { + task: set Task +} +{ all i: Implementation | one i.~child} + +--@context.editor: "ReqIFEditor" //context'leri bir attribute ile tanıt kullanıcı dokümanlarını yaratırken bunlardan seçsin +//contenttype +sig SoftwareRequirement extends Implementation { + test: some TestCase +} + +sig HardwareRequirement extends Implementation {} + +sig TestCase extends Element { +}{ all t:TestCase | one t.~test} + + +fact noSelfRelation{ + no c: ContractRequirement | c in c.relate + no t: Task | t in t.precede +} + +fact noCycles{ + no t:Task | t in t.^precede +} + +fact realismConstraint { + some ContractRequirement + some HardwareRequirement + some SoftwareRequirement + some precede +} + +/* Counterexample Finding */ + + +/* Instance Finding */ +pred example { + #ContractRequirement >1 and #SystemRequirement >2 and + #SoftwareRequirement >1 and #HardwareRequirement >1 and + #precede >1 and #task >1 #(Task <: state).Cancelled >1 and + #(Requirement <: state).Created >4 and + #(Requirement <: state).Changed >1 and + #(Requirement <: state).Deleted >1 +} +run example for exactly 20 Element + diff --git a/Source/eu.modelwriter.configuration/SimlifiedConfigurationModel.als b/Source/eu.modelwriter.configuration/SimlifiedConfigurationModel.als new file mode 100644 index 00000000..7b163051 --- /dev/null +++ b/Source/eu.modelwriter.configuration/SimlifiedConfigurationModel.als @@ -0,0 +1,58 @@ +module Haveksan/Requirement + +abstract sig Requirement {} + +sig Task { + precede: lone Task, +}{ all t: Task | one t.~task} + +one sig Project extends Requirement { + requirement: some ContractRequirement } + +sig ContractRequirement extends Requirement { + system: set SystemRequirement, + relate: set ContractRequirement +}{all c: ContractRequirement | one c.~requirement} + +--@name: "System Requirement" +sig SystemRequirement extends Requirement { + child: some Implementation +}{ all s: SystemRequirement | one s.~system} + +abstract sig Implementation extends Requirement { + task: set Task +}{ all i: Implementation | one i.~child} + +--@context.editor: "ReqIFEditor" +sig SoftwareRequirement extends Implementation { + test: some TestCase +} + +sig HardwareRequirement extends Implementation {} + +sig TestCase { }{ all t:TestCase | one t.~test} + + +fact noSelfRelation{ + no c: ContractRequirement | c in c.relate + no t: Task | t in t.precede } + +fact noCycles{no t:Task | t in t.^precede} + +fact realismConstraint { + some ContractRequirement + some HardwareRequirement + some SoftwareRequirement + some precede} + +/* Counterexample Finding */ + + +/* Instance Finding */ +pred example { + #ContractRequirement >1 and #SystemRequirement >2 and + #SoftwareRequirement >1 and #HardwareRequirement >1 and + #precede >1 and #task >1 +} +run example for 10 + diff --git a/Source/eu.modelwriter.configuration/Type.als b/Source/eu.modelwriter.configuration/Type.als new file mode 100644 index 00000000..05c58e26 --- /dev/null +++ b/Source/eu.modelwriter.configuration/Type.als @@ -0,0 +1,8 @@ +module ferhat/Type + +/*Type Module*/ +abstract sig Element {} +sig string extends Element {} +abstract sig boolean extends Element {} +one sig false,true extends boolean {} +--sig integer extends Element {} diff --git a/Source/eu.modelwriter.configuration/build.properties b/Source/eu.modelwriter.configuration/build.properties new file mode 100644 index 00000000..c34b2af8 --- /dev/null +++ b/Source/eu.modelwriter.configuration/build.properties @@ -0,0 +1,23 @@ +############################################################################### +# Copyright (c) 2015 UNIT Information Technologies R&D Ltd +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Eclipse Public License v1.0 +# which accompanies this distribution, and is available at +# http://www.eclipse.org/legal/epl-v10.html +# +# Contributors: +# Ferhat Erata - initial API and implementation +# H. Emre Kirmizi - initial API and implementation +# Serhat Celik - initial API and implementation +# U. Anil Ozturk - initial API and implementation +############################################################################### +source.. = src/ +output.. = bin/ +bin.includes = META-INF/,\ + .,\ + lib/,\ + bin/,\ + .settings/,\ + .project,\ + .classpath,\ + build.properties diff --git a/Source/eu.modelwriter.configuration/empty.als b/Source/eu.modelwriter.configuration/empty.als new file mode 100644 index 00000000..e69de29b diff --git a/Source/eu.modelwriter.configuration/grandpa.als b/Source/eu.modelwriter.configuration/grandpa.als new file mode 100644 index 00000000..df0fffe1 --- /dev/null +++ b/Source/eu.modelwriter.configuration/grandpa.als @@ -0,0 +1,26 @@ +abstract sig Person { + father: lone Man, + mother: lone Woman + } + +sig Man extends Person { wife: lone Woman } + +sig Woman extends Person { husband: lone Man } + +fact Biology { no p: Person | p in p.^(mother+father) } + +fact Terminology { wife = ~husband } + +fact SocialConvention { + no wife & *(mother+father).mother + no husband & *(mother+father).father + } + +fun grandpas [p: Person] : set Person { + let parent = mother + father + father.wife + mother.husband | + p.parent.parent & Man + } + +pred ownGrandpa [m: Man] { m in grandpas[m] } + +run ownGrandpa for 4 Person \ No newline at end of file diff --git a/Source/eu.modelwriter.configuration/lib/alloy4.2.jar b/Source/eu.modelwriter.configuration/lib/alloy4.2.jar new file mode 100644 index 00000000..3be21612 Binary files /dev/null and b/Source/eu.modelwriter.configuration/lib/alloy4.2.jar differ diff --git a/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/Activator.java b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/Activator.java new file mode 100644 index 00000000..a23c7158 --- /dev/null +++ b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/Activator.java @@ -0,0 +1,74 @@ +/******************************************************************************* + * Copyright (c) 2015 UNIT Information Technologies R&D Ltd All rights reserved. This program and + * the accompanying materials are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: Ferhat Erata - initial API and implementation H. Emre Kirmizi - initial API and + * implementation Serhat Celik - initial API and implementation U. Anil Ozturk - initial API and + * implementation + *******************************************************************************/ +package eu.modelwriter.configuration; + +import org.eclipse.swt.widgets.Shell; +import org.eclipse.ui.IWorkbenchWindow; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.plugin.AbstractUIPlugin; +import org.osgi.framework.BundleContext; + +/** + * The activator class controls the plug-in life cycle + */ +public class Activator extends AbstractUIPlugin { + + // The shared instance + private static Activator plugin; + + // The plug-in ID + public static final String PLUGIN_ID = "eu.modelwriter.configuration"; //$NON-NLS-1$ + + /** + * Returns the shared instance + * + * @return the shared instance + */ + public static Activator getDefault() { + return Activator.plugin; + } + + /** + * The constructor + */ + public Activator() {} + + /* + * (non-Javadoc) + * + * @see org.eclipse.ui.plugin.AbstractUIPlugin#start(org.osgi.framework.BundleContext) + */ + @Override + public void start(BundleContext context) throws Exception { + super.start(context); + Activator.plugin = this; + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.ui.plugin.AbstractUIPlugin#stop(org.osgi.framework.BundleContext) + */ + @Override + public void stop(BundleContext context) throws Exception { + Activator.plugin = null; + super.stop(context); + } + + public static Shell getShell() { + return getActiveWorkbenchWindow().getShell(); + } + + public static IWorkbenchWindow getActiveWorkbenchWindow() { + return PlatformUI.getWorkbench().getActiveWorkbenchWindow(); + } + +} diff --git a/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/alloy/AlloyParser.java b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/alloy/AlloyParser.java new file mode 100644 index 00000000..0f782d44 --- /dev/null +++ b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/alloy/AlloyParser.java @@ -0,0 +1,442 @@ +/******************************************************************************* + * Copyright (c) 2015 UNIT Information Technologies R&D Ltd All rights reserved. This program and + * the accompanying materials are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: Ferhat Erata - initial API and implementation H. Emre Kirmizi - initial API and + * implementation Serhat Celik - initial API and implementation U. Anil Ozturk - initial API and + * implementation + *******************************************************************************/ +package eu.modelwriter.configuration.alloy; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Map.Entry; + +import org.eclipse.emf.common.util.EList; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.ui.PlatformUI; + +import edu.mit.csail.sdg.alloy4.A4Reporter; +import edu.mit.csail.sdg.alloy4.Err; +import edu.mit.csail.sdg.alloy4.SafeList; +import edu.mit.csail.sdg.alloy4compiler.ast.Module; +import edu.mit.csail.sdg.alloy4compiler.ast.Sig; +import edu.mit.csail.sdg.alloy4compiler.ast.Sig.Field; +import edu.mit.csail.sdg.alloy4compiler.ast.Sig.PrimSig; +import edu.mit.csail.sdg.alloy4compiler.ast.Sig.SubsetSig; +import edu.mit.csail.sdg.alloy4compiler.ast.Type; +import edu.mit.csail.sdg.alloy4compiler.ast.Type.ProductType; +import edu.mit.csail.sdg.alloy4compiler.parser.CompUtil; +import eu.modelwriter.configuration.internal.AlloyUtilities; +import eu.modelwriter.marker.internal.MarkerTypeElement; +import eu.modelwriter.traceability.core.persistence.AlloyType; +import eu.modelwriter.traceability.core.persistence.DocumentRoot; +import eu.modelwriter.traceability.core.persistence.FieldType; +import eu.modelwriter.traceability.core.persistence.InstanceType; +import eu.modelwriter.traceability.core.persistence.RelationType; +import eu.modelwriter.traceability.core.persistence.RepositoryType; +import eu.modelwriter.traceability.core.persistence.SigType; +import eu.modelwriter.traceability.core.persistence.SourceType; +import eu.modelwriter.traceability.core.persistence.TypeType; +import eu.modelwriter.traceability.core.persistence.TypesType; +import eu.modelwriter.traceability.core.persistence.persistenceFactory; + +public class AlloyParser { + + private String filename; + private final ArrayList rels = new ArrayList(); + private final Map sigTypeParentMap = new HashMap(); + private final ArrayList types = new ArrayList(); + + AlloyParser() {} + + public AlloyParser(final String filename) { + this.filename = filename; + this.parse(filename); + } + + private MarkerTypeElement convertToMarkerType(final Sig rootSig) { + if (rootSig instanceof PrimSig) { + final PrimSig primSig = (PrimSig) rootSig; + MarkerTypeElement rootType; + if (primSig.isAbstract != null) { + rootType = new MarkerTypeElement( + primSig.toString().substring(primSig.toString().indexOf("/") + 1) + " {abs}"); + } else { + rootType = new MarkerTypeElement( + primSig.toString().substring(primSig.toString().indexOf("/") + 1)); + } + try { + if (primSig.children().isEmpty()) { + return rootType; + } else { + for (int i = 0; i < primSig.children().size(); i++) { + rootType.getChildren().add(this.convertToMarkerType(primSig.children().get(i))); + } + return rootType; + } + } catch (final Err e) { + final MessageDialog dialog = + new MessageDialog(PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(), + "Alloy Error Information", null, e.getMessage(), MessageDialog.INFORMATION, + new String[] {"OK"}, 0); + dialog.open(); + } + } else if (rootSig instanceof SubsetSig) { + final SubsetSig subsetSig = (SubsetSig) rootSig; + String parentName = subsetSig.type().toString(); + parentName = parentName.substring(parentName.indexOf("/") + 1, parentName.length() - 1); + final MarkerTypeElement parentMarkerType = + this.getMarkTypeElementByName(parentName, this.types); + parentMarkerType.getChildren().add(new MarkerTypeElement( + subsetSig.toString().substring(subsetSig.toString().indexOf("/") + 1))); + } + return null; + } + + private DocumentRoot createBaseXmlFile() { + // Resource res = AlloyUtilities.getResource(); + // RepositoryType oldRepositoryType = null; + // + // if (res == null) { + // res = AlloyUtilities.createResource(); + // } else { + // DocumentRoot oldDocumentRoot = (DocumentRoot) res.getContents().get(0); + // oldRepositoryType = oldDocumentRoot.getAlloy().getRepository(); + // } + + RepositoryType oldRepositoryType = null; + RelationType oldRelationType = null; + final DocumentRoot oldDocumentRoot = AlloyUtilities.getDocumentRoot(); + if (oldDocumentRoot != null) { + oldRepositoryType = oldDocumentRoot.getAlloy().getRepository(); + oldRelationType = oldDocumentRoot.getAlloy().getRelation(); + } + + // RepositoryType oldRepositoryType = null; + // if (res.getContents().size() != 0) { + // DocumentRoot oldDocumentRoot = (DocumentRoot) res.getContents().get(0); + // oldRepositoryType = oldDocumentRoot.getAlloy().getRepository(); + // } + + final DocumentRoot documentRoot = persistenceFactory.eINSTANCE.createDocumentRoot(); + + final AlloyType alloyType = persistenceFactory.eINSTANCE.createAlloyType(); + documentRoot.setAlloy(alloyType); + alloyType.setBuilddate(""); + if (oldRepositoryType == null) { + final RepositoryType repositoryType = persistenceFactory.eINSTANCE.createRepositoryType(); + repositoryType.setNextId(0); + alloyType.setRepository(repositoryType); + } else { + alloyType.setRepository(oldRepositoryType); + } + + if (oldRelationType == null) { + final RelationType relationType = persistenceFactory.eINSTANCE.createRelationType(); + alloyType.setRelation(relationType); + } else { + alloyType.setRelation(oldRelationType); + } + + final InstanceType instanceType = persistenceFactory.eINSTANCE.createInstanceType(); + alloyType.setInstance(instanceType); + instanceType.setBitwidth(0); + instanceType.setFilename(this.filename); + instanceType.setMaxseq(0); + + final SigType sigSegInt = persistenceFactory.eINSTANCE.createSigType(); + instanceType.getSig().add(sigSegInt); + sigSegInt.setID(0); + sigSegInt.setLabel("seq/Int"); + sigSegInt.setParentID(1); + sigSegInt.setBuiltin("yes"); + + final SigType sigInt = persistenceFactory.eINSTANCE.createSigType(); + instanceType.getSig().add(sigInt); + sigInt.setID(1); + sigInt.setLabel("Int"); + sigInt.setParentID(2); + sigInt.setBuiltin("yes"); + + final SigType sigUniv = persistenceFactory.eINSTANCE.createSigType(); + instanceType.getSig().add(sigUniv); + sigUniv.setID(2); + sigUniv.setLabel("univ"); + sigUniv.setBuiltin("yes"); + + final SigType sigString = persistenceFactory.eINSTANCE.createSigType(); + instanceType.getSig().add(sigString); + sigString.setID(3); + sigString.setLabel("String"); + sigString.setParentID(2); + sigString.setBuiltin("yes"); + + + + // AlloyUtilities.saveResource(res, documentRoot); + + return documentRoot; + } + + private FieldType getFieldType(final Field field, final int idIndex, + final EList fieldTypeList, final EList sigTypeList) { + final FieldType fieldType = persistenceFactory.eINSTANCE.createFieldType(); + + final int fieldParentId = this.parentId(field.sig.label.toString(), sigTypeList); + final int firstTypeId = this.parentId( + field.type().toString().substring(1, field.type().toString().indexOf("->")), sigTypeList); + fieldType.setLabel(field.label); + fieldType.setID(idIndex); + fieldType.setParentID(fieldParentId); + + final Iterator iter = field.decl().expr.type().iterator(); + while (iter.hasNext()) { + final Type.ProductType productType = iter.next(); + + final TypesType typesType = persistenceFactory.eINSTANCE.createTypesType(); + fieldType.getTypes().add(typesType); + + final TypeType firstTypeType = persistenceFactory.eINSTANCE.createTypeType(); + typesType.getType().add(firstTypeType); + firstTypeType.setID(firstTypeId); + + final TypeType secondTypeType = persistenceFactory.eINSTANCE.createTypeType(); + typesType.getType().add(secondTypeType); + final int secondTypeId = this.parentId(productType.toString(), sigTypeList); + secondTypeType.setID(secondTypeId); + } + + return fieldType; + } + + private MarkerTypeElement getMarkTypeElementByName(final String elementName, + final ArrayList typeList) { + MarkerTypeElement element = null; + + for (final MarkerTypeElement markerTypeElement : typeList) { + if (markerTypeElement.getType().contains("{abs}") && markerTypeElement.getType() + .substring(0, markerTypeElement.getType().indexOf(" {abs}")).equals(elementName) + || markerTypeElement.getType().equals(elementName)) { + return markerTypeElement; + } + if (markerTypeElement.getChildren().size() != 0) { + element = this.getMarkTypeElementByName(elementName, markerTypeElement.getChildren()); + } + if (element != null) { + break; + } + } + + return element; + } + + public ArrayList getRels() { + return this.rels; + } + + private SigType getSigType(final PrimSig primSig, final int idIndex, + final EList sigTypeList) { + final String parentName = primSig.parent.toString(); + + + // int parentId = parentId(parentName, sigTypeList); + + final SigType sigType = persistenceFactory.eINSTANCE.createSigType(); + sigType.setID(idIndex); + sigType.setLabel(primSig.label); + // sigType.setParentID(parentId); + + this.sigTypeParentMap.put(sigType, parentName); + + this.setStatuofSig(primSig, sigType); + + return sigType; + } + + private SigType getSigType(final SubsetSig subsetSig, final int idIndex, + final EList sigTypeList) { + String typeName = subsetSig.type().toString(); + typeName = typeName.substring(1, typeName.length() - 1); + + final SigType sigType = persistenceFactory.eINSTANCE.createSigType(); + sigType.setID(idIndex); + sigType.setLabel(subsetSig.label); + + for (final SigType sigTypes : sigTypeList) { + if (sigTypes.getLabel().equals(typeName)) { + final TypeType type = persistenceFactory.eINSTANCE.createTypeType(); + type.setID(sigTypes.getID()); + sigType.getType().add(type); + break; + } + + } + + this.setStatuofSig(subsetSig, sigType); + + return sigType; + + } + + public ArrayList getTypes() { + return this.types; + } + + private int parentId(final String parentName, final EList sigTypeList) { + + for (final SigType sigType : sigTypeList) { + if (sigType.getLabel().equals(parentName)) { + return sigType.getID(); + } + } + + return -1; + + } + + private void parse(final String filename) { + // AlloyUtilities.createXMLFromAlloy(filename); + try { + // Parse+typecheck the model + // System.out.println("=========== Parsing+Typechecking " + filename + " ============="); + Module world; + + final DocumentRoot documentRoot = this.createBaseXmlFile(); + final EList xmlSigList = documentRoot.getAlloy().getInstance().getSig(); + final EList xmlFieldList = documentRoot.getAlloy().getInstance().getField(); + + int idIndex = 4; + final Map map = new LinkedHashMap(); + world = CompUtil.parseEverything_fromFile(new A4Reporter(), map, filename); + for (final Module modules : world.getAllReachableModules()) { + final SafeList list = modules.getAllSigs(); + for (final Sig sig : list) { + if (sig instanceof PrimSig) { + final PrimSig primSig = (PrimSig) sig; + + xmlSigList.add(this.getSigType(primSig, idIndex, xmlSigList)); + idIndex++; + + if (primSig.children().size() == 0 && primSig.toString() + .substring(primSig.toString().indexOf("/") + 1).equals("Univ")) { + break; + } + if (primSig.isTopLevel()) { + this.types.add(this.convertToMarkerType(primSig)); + } + } else if (sig instanceof SubsetSig) { + final SubsetSig subsetSig = (SubsetSig) sig; + this.convertToMarkerType(subsetSig); + xmlSigList.add(this.getSigType(subsetSig, idIndex, xmlSigList)); + idIndex++; + // this.types.add(this.convertToMarkerType(subsetSig)); + } + } + } + + this.setParentIdForSigTypes(xmlSigList); + + for (final Module modules : world.getAllReachableModules()) { + final SafeList list = modules.getAllSigs(); + for (final Sig sig : list) { + final SafeList fields = sig.getFields(); + for (final Field field : fields) { + + xmlFieldList.add(this.getFieldType(field, idIndex, xmlFieldList, xmlSigList)); + idIndex++; + + String product = ""; + if (field.decl().expr.type().size() > 1) { + final Iterator iter = field.decl().expr.type().iterator(); + while (iter.hasNext()) { + final Type.ProductType productType = iter.next(); + if (iter.hasNext()) { + product += + productType.toString().substring(productType.toString().indexOf("/") + 1) + + ","; + } else { + product += + productType.toString().substring(productType.toString().indexOf("/") + 1); + } + } + } else { + product = field.decl().expr.type().toExpr().toString() + .substring(field.decl().expr.type().toExpr().toString().indexOf("/") + 1); + } + final String str2 = field.label + " : " + + field.sig.toString().substring(field.sig.toString().indexOf("/") + 1) + " -> " + + field.decl().expr.mult() + " " + product; + this.rels.add(str2); + } + } + } + + final Iterator> mapIter = map.entrySet().iterator(); + while (mapIter.hasNext()) { + final Entry entry = mapIter.next(); + final SourceType sourceType = persistenceFactory.eINSTANCE.createSourceType(); + sourceType.setFilename(entry.getKey()); + sourceType.setContent(entry.getValue()); + documentRoot.getAlloy().getSource().add(sourceType); + } + + AlloyUtilities.writeDocumentRoot(documentRoot); + // AlloyUtilities.saveResource(AlloyUtilities.getResource(), documentRoot); + + final MessageDialog messageDialog = + new MessageDialog(PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(), + "Information", null, "Alloy file has been parsed succesfully", + MessageDialog.INFORMATION, new String[] {"OK"}, 0); + messageDialog.open(); + } catch (final Err e) { + final MessageDialog dialog = + new MessageDialog(PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(), + "Alloy Error Information", null, e.getMessage(), MessageDialog.INFORMATION, + new String[] {"OK"}, 0); + dialog.open(); + } + } + + + private void setParentIdForSigTypes(final EList sigTypeList) { + + for (final SigType sigType : sigTypeList) { + final String parentName = this.sigTypeParentMap.get(sigType); + if (parentName != null) { + final int parentId = this.parentId(parentName, sigTypeList); + sigType.setParentID(parentId); + } + } + } + + private void setStatuofSig(final Sig sig, final SigType sigType) { + if (sig.isAbstract != null) { + sigType.setAbstract("yes"); + } + if (sig.isEnum != null) { + sigType.setEnum("yes"); + } + if (sig.isLone != null) { + sigType.setLone("yes"); + } + if (sig.isMeta != null) { + sigType.setMeta("yes"); + } + if (sig.isOne != null) { + sigType.setOne("yes"); + } + if (sig.isPrivate != null) { + sigType.setPrivate("yes"); + } + if (sig.isSome != null) { + sigType.setSome("yes"); + } + } + +} diff --git a/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/alloy/AlloyParserForMetamodel.java b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/alloy/AlloyParserForMetamodel.java new file mode 100644 index 00000000..8732f8e0 --- /dev/null +++ b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/alloy/AlloyParserForMetamodel.java @@ -0,0 +1,424 @@ +/******************************************************************************* + * Copyright (c) 2015 UNIT Information Technologies R&D Ltd All rights reserved. This program and + * the accompanying materials are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: Ferhat Erata - initial API and implementation H. Emre Kirmizi - initial API and + * implementation Serhat Celik - initial API and implementation U. Anil Ozturk - initial API and + * implementation + *******************************************************************************/ +package eu.modelwriter.configuration.alloy; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Map.Entry; + +import org.eclipse.emf.common.util.EList; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.ui.PlatformUI; + +import edu.mit.csail.sdg.alloy4.A4Reporter; +import edu.mit.csail.sdg.alloy4.Err; +import edu.mit.csail.sdg.alloy4.SafeList; +import edu.mit.csail.sdg.alloy4compiler.ast.Module; +import edu.mit.csail.sdg.alloy4compiler.ast.Sig; +import edu.mit.csail.sdg.alloy4compiler.ast.Sig.Field; +import edu.mit.csail.sdg.alloy4compiler.ast.Sig.PrimSig; +import edu.mit.csail.sdg.alloy4compiler.ast.Sig.SubsetSig; +import edu.mit.csail.sdg.alloy4compiler.ast.Type; +import edu.mit.csail.sdg.alloy4compiler.ast.Type.ProductType; +import edu.mit.csail.sdg.alloy4compiler.parser.CompUtil; +import eu.modelwriter.configuration.internal.AlloyUtilities; +import eu.modelwriter.marker.internal.MarkerTypeElement; +import eu.modelwriter.traceability.core.persistence.AlloyType; +import eu.modelwriter.traceability.core.persistence.DocumentRoot; +import eu.modelwriter.traceability.core.persistence.FieldType; +import eu.modelwriter.traceability.core.persistence.InstanceType; +import eu.modelwriter.traceability.core.persistence.RelationType; +import eu.modelwriter.traceability.core.persistence.RepositoryType; +import eu.modelwriter.traceability.core.persistence.SigType; +import eu.modelwriter.traceability.core.persistence.SourceType; +import eu.modelwriter.traceability.core.persistence.TypeType; +import eu.modelwriter.traceability.core.persistence.TypesType; +import eu.modelwriter.traceability.core.persistence.persistenceFactory; + +public class AlloyParserForMetamodel { + + private static ArrayList rels; + + private static ArrayList sigs; + + public static ArrayList getRels() { + return rels; + } + + public static ArrayList getSigs() { + return sigs; + } + + private String xmlName; + private String filepath; + private final Map sigTypeParentMap = new HashMap<>(); + + private final ArrayList types = new ArrayList<>(); + + AlloyParserForMetamodel() {} + + public AlloyParserForMetamodel(final String filepath, final String xmlName) throws Err { + this.filepath = filepath; + this.xmlName = xmlName; + this.parse(); + } + + private MarkerTypeElement convertToMarkerType(final Sig rootSig) { + if (rootSig instanceof PrimSig) { + final PrimSig primSig = (PrimSig) rootSig; + MarkerTypeElement rootType; + if (primSig.isAbstract != null) { + rootType = new MarkerTypeElement( + primSig.toString().substring(primSig.toString().indexOf("/") + 1) + " {abs}"); + } else { + rootType = new MarkerTypeElement( + primSig.toString().substring(primSig.toString().indexOf("/") + 1)); + } + try { + if (primSig.children().isEmpty()) { + return rootType; + } else { + for (int i = 0; i < primSig.children().size(); i++) { + rootType.getChildren().add(this.convertToMarkerType(primSig.children().get(i))); + } + return rootType; + } + } catch (final Err e) { + final MessageDialog dialog = + new MessageDialog(PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(), + "Alloy Error Information", null, e.getMessage(), MessageDialog.INFORMATION, + new String[] {"OK"}, 0); + dialog.open(); + } + } else if (rootSig instanceof SubsetSig) { + final SubsetSig subsetSig = (SubsetSig) rootSig; + String parentName = subsetSig.type().toString(); + parentName = parentName.substring(parentName.indexOf("/") + 1, parentName.length() - 1); + final MarkerTypeElement parentMarkerType = + this.getMarkTypeElementByName(parentName, this.types); + parentMarkerType.getChildren().add(new MarkerTypeElement( + subsetSig.toString().substring(subsetSig.toString().indexOf("/") + 1))); + } + return null; + } + + private DocumentRoot createBaseXmlFile() { + // Resource res = AlloyUtilities.getResource(); + // RepositoryType oldRepositoryType = null; + // + // if (res == null) { + // res = AlloyUtilities.createResource(); + // } else { + // DocumentRoot oldDocumentRoot = (DocumentRoot) res.getContents().get(0); + // oldRepositoryType = oldDocumentRoot.getAlloy().getRepository(); + // } + + // RepositoryType oldRepositoryType = null; + // RelationType oldRelationType = null; + // DocumentRoot oldDocumentRoot = AlloyUtilities.getDocumentRoot(); + // if (oldDocumentRoot != null) { + // oldRepositoryType = oldDocumentRoot.getAlloy().getRepository(); + // oldRelationType = oldDocumentRoot.getAlloy().getRelation(); + // } + + // RepositoryType oldRepositoryType = null; + // if (res.getContents().size() != 0) { + // DocumentRoot oldDocumentRoot = (DocumentRoot) res.getContents().get(0); + // oldRepositoryType = oldDocumentRoot.getAlloy().getRepository(); + // } + + final DocumentRoot documentRoot = persistenceFactory.eINSTANCE.createDocumentRoot(); + + final AlloyType alloyType = persistenceFactory.eINSTANCE.createAlloyType(); + documentRoot.setAlloy(alloyType); + alloyType.setBuilddate(""); + // if (oldRepositoryType == null) { + final RepositoryType repositoryType = persistenceFactory.eINSTANCE.createRepositoryType(); + repositoryType.setNextId(0); + alloyType.setRepository(repositoryType); + // } else { + // alloyType.setRepository(oldRepositoryType); + // } + + // if (oldRelationType == null) { + final RelationType relationType = persistenceFactory.eINSTANCE.createRelationType(); + alloyType.setRelation(relationType); + // } else { + // alloyType.setRelation(oldRelationType); + // } + + final InstanceType instanceType = persistenceFactory.eINSTANCE.createInstanceType(); + alloyType.setInstance(instanceType); + instanceType.setBitwidth(0); + instanceType.setFilename(AlloyUtilities.getLocationForMetamodel(this.xmlName)); + instanceType.setMaxseq(0); + instanceType.setMetamodel("yes"); + + final SigType sigSegInt = persistenceFactory.eINSTANCE.createSigType(); + instanceType.getSig().add(sigSegInt); + sigSegInt.setID(0); + sigSegInt.setLabel("seq/Int"); + sigSegInt.setParentID(1); + sigSegInt.setBuiltin("yes"); + + final SigType sigInt = persistenceFactory.eINSTANCE.createSigType(); + instanceType.getSig().add(sigInt); + sigInt.setID(1); + sigInt.setLabel("Int"); + sigInt.setParentID(2); + sigInt.setBuiltin("yes"); + + final SigType sigUniv = persistenceFactory.eINSTANCE.createSigType(); + instanceType.getSig().add(sigUniv); + sigUniv.setID(2); + sigUniv.setLabel("univ"); + sigUniv.setBuiltin("yes"); + + final SigType sigString = persistenceFactory.eINSTANCE.createSigType(); + instanceType.getSig().add(sigString); + sigString.setID(3); + sigString.setLabel("String"); + sigString.setParentID(2); + sigString.setBuiltin("yes"); + + return documentRoot; + } + + private FieldType getFieldType(final Field field, final int idIndex, + final EList fieldTypeList, final EList sigTypeList) { + final FieldType fieldType = persistenceFactory.eINSTANCE.createFieldType(); + + final int fieldParentId = this.parentId(field.sig.label.toString(), sigTypeList); + final int firstTypeId = this.parentId( + field.type().toString().substring(1, field.type().toString().indexOf("->")), sigTypeList); + fieldType.setLabel(field.label); + fieldType.setID(idIndex); + fieldType.setParentID(fieldParentId); + + final Iterator iter = field.decl().expr.type().iterator(); + while (iter.hasNext()) { + final Type.ProductType productType = iter.next(); + + final TypesType typesType = persistenceFactory.eINSTANCE.createTypesType(); + fieldType.getTypes().add(typesType); + + final TypeType firstTypeType = persistenceFactory.eINSTANCE.createTypeType(); + typesType.getType().add(firstTypeType); + firstTypeType.setID(firstTypeId); + + final TypeType secondTypeType = persistenceFactory.eINSTANCE.createTypeType(); + typesType.getType().add(secondTypeType); + final int secondTypeId = this.parentId(productType.toString(), sigTypeList); + secondTypeType.setID(secondTypeId); + } + + return fieldType; + } + + private MarkerTypeElement getMarkTypeElementByName(final String elementName, + final ArrayList typeList) { + MarkerTypeElement element = null; + + for (final MarkerTypeElement markerTypeElement : typeList) { + if (markerTypeElement.getType().contains("{abs}") && markerTypeElement.getType() + .substring(0, markerTypeElement.getType().indexOf(" {abs}")).equals(elementName) + || markerTypeElement.getType().equals(elementName)) { + return markerTypeElement; + } + if (markerTypeElement.getChildren().size() != 0) { + element = this.getMarkTypeElementByName(elementName, markerTypeElement.getChildren()); + } + if (element != null) { + break; + } + } + + return element; + } + + private SigType getSigType(final PrimSig primSig, final int idIndex, + final EList sigTypeList) { + final String parentName = primSig.parent.toString(); + + + // int parentId = parentId(parentName, sigTypeList); + + final SigType sigType = persistenceFactory.eINSTANCE.createSigType(); + sigType.setID(idIndex); + sigType.setLabel(primSig.label); + // sigType.setParentID(parentId); + + this.sigTypeParentMap.put(sigType, parentName); + + this.setStatuofSig(primSig, sigType); + + return sigType; + } + + private SigType getSigType(final SubsetSig subsetSig, final int idIndex, + final EList sigTypeList) { + String typeName = subsetSig.type().toString(); + typeName = typeName.substring(1, typeName.length() - 1); + + final SigType sigType = persistenceFactory.eINSTANCE.createSigType(); + sigType.setID(idIndex); + sigType.setLabel(subsetSig.label); + + for (final SigType sigTypes : sigTypeList) { + if (sigTypes.getLabel().equals(typeName)) { + final TypeType type = persistenceFactory.eINSTANCE.createTypeType(); + type.setID(sigTypes.getID()); + sigType.getType().add(type); + break; + } + + } + + this.setStatuofSig(subsetSig, sigType); + + return sigType; + + } + + private int parentId(final String parentName, final EList sigTypeList) { + + for (final SigType sigType : sigTypeList) { + if (sigType.getLabel().equals(parentName)) { + return sigType.getID(); + } + } + + return -1; + + } + + private void parse() throws Err { + final ArrayList relations = new ArrayList<>(); + final ArrayList signatures = new ArrayList<>(); + + // Parse+typecheck the model + // System.out.println("=========== Parsing+Typechecking " + this.filepath + " ============="); + + final DocumentRoot documentRoot = this.createBaseXmlFile(); + final EList xmlSigList = documentRoot.getAlloy().getInstance().getSig(); + final EList xmlFieldList = documentRoot.getAlloy().getInstance().getField(); + + int idIndex = 4; + final Map map = new LinkedHashMap<>(); + final Module world = CompUtil.parseEverything_fromFile(new A4Reporter(), map, this.filepath); + for (final Module modules : world.getAllReachableModules()) { + final SafeList list = modules.getAllSigs(); + for (final Sig sig : list) { + if (sig instanceof PrimSig) { + final PrimSig primSig = (PrimSig) sig; + xmlSigList.add(this.getSigType(primSig, idIndex, xmlSigList)); + idIndex++; + + if (primSig.children().size() == 0 + && primSig.toString().substring(primSig.toString().indexOf("/") + 1).equals("Univ")) { + break; + } + if (primSig.isTopLevel()) { + this.types.add(this.convertToMarkerType(primSig)); + } + } else if (sig instanceof SubsetSig) { + final SubsetSig subsetSig = (SubsetSig) sig; + this.convertToMarkerType(subsetSig); + xmlSigList.add(this.getSigType(subsetSig, idIndex, xmlSigList)); + idIndex++; + } + } + } + + this.setParentIdForSigTypes(xmlSigList); + + for (final Module modules : world.getAllReachableModules()) { + final SafeList list = modules.getAllSigs(); + for (final Sig sig : list) { + if (!signatures.contains(sig.label)) { + signatures.add(sig.label.substring(sig.label.indexOf("/") + 1)); + } + final SafeList fields = sig.getFields(); + for (final Field field : fields) { + + xmlFieldList.add(this.getFieldType(field, idIndex, xmlFieldList, xmlSigList)); + idIndex++; + + if (!relations.contains(field.label)) { + relations.add(field.label); + } + } + } + } + + final Iterator> mapIter = map.entrySet().iterator(); + while (mapIter.hasNext()) { + final Entry entry = mapIter.next(); + final SourceType sourceType = persistenceFactory.eINSTANCE.createSourceType(); + if (entry.getKey().contains("temp")) { + sourceType.setFilename(AlloyUtilities.getLocationForMetamodel(this.xmlName)); + } else { + sourceType.setFilename(entry.getKey()); + } + sourceType.setContent(entry.getValue()); + documentRoot.getAlloy().getSource().add(sourceType); + } + + // If the code reaches here, it means there is not any parse error. + // we'r loading these fields because of reconciler and completions usage. + rels = new ArrayList<>(); + rels.addAll(relations); + + sigs = new ArrayList<>(); + sigs.addAll(signatures); + + AlloyUtilities.writeDocumentRootForMetamodel(documentRoot, this.xmlName); + } + + private void setParentIdForSigTypes(final EList sigTypeList) { + + for (final SigType sigType : sigTypeList) { + final String parentName = this.sigTypeParentMap.get(sigType); + if (parentName != null) { + final int parentId = this.parentId(parentName, sigTypeList); + sigType.setParentID(parentId); + } + } + } + + + private void setStatuofSig(final Sig sig, final SigType sigType) { + if (sig.isAbstract != null) { + sigType.setAbstract("yes"); + } + if (sig.isEnum != null) { + sigType.setEnum("yes"); + } + if (sig.isLone != null) { + sigType.setLone("yes"); + } + if (sig.isMeta != null) { + sigType.setMeta("yes"); + } + if (sig.isOne != null) { + sigType.setOne("yes"); + } + if (sig.isPrivate != null) { + sigType.setPrivate("yes"); + } + if (sig.isSome != null) { + sigType.setSome("yes"); + } + } +} diff --git a/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/alloy/analysis/AlloySolutionFinder.java b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/alloy/analysis/AlloySolutionFinder.java new file mode 100644 index 00000000..e6ac5452 --- /dev/null +++ b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/alloy/analysis/AlloySolutionFinder.java @@ -0,0 +1,50 @@ +package eu.modelwriter.configuration.alloy.analysis; + +import edu.mit.csail.sdg.alloy4.A4Reporter; +import edu.mit.csail.sdg.alloy4.Err; +import edu.mit.csail.sdg.alloy4.ErrorWarning; +import edu.mit.csail.sdg.alloy4compiler.ast.Command; +import edu.mit.csail.sdg.alloy4compiler.ast.Module; +import edu.mit.csail.sdg.alloy4compiler.parser.CompUtil; +import edu.mit.csail.sdg.alloy4compiler.translator.A4Options; +import edu.mit.csail.sdg.alloy4compiler.translator.A4Solution; +import edu.mit.csail.sdg.alloy4compiler.translator.TranslateAlloyToKodkod; + +public class AlloySolutionFinder { + private final String alsPath; + + public AlloySolutionFinder(final String alsPath) { + this.alsPath = alsPath; + } + + public A4Solution find() { + Module world = null; + A4Solution solution = null; + try { + final A4Reporter rep = new A4Reporter() { + @Override + public void warning(final ErrorWarning msg) { + System.out.print("Relevance Warning:\n" + msg.toString().trim() + "\n\n"); + System.out.flush(); + } + }; + + world = CompUtil.parseEverything_fromFile(rep, null, alsPath); + + final A4Options options = new A4Options(); + options.solver = A4Options.SatSolver.SAT4J; + + for (final Command command : world.getAllCommands()) { + solution = TranslateAlloyToKodkod.execute_command(rep, world.getAllReachableSigs(), command, + options); + + if (solution.satisfiable()) { + break; + } + } + } catch (final Err e) { + e.printStackTrace(); + } + return solution; + } +} diff --git a/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/alloy/analysis/IAlloyAnalyzer.java b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/alloy/analysis/IAlloyAnalyzer.java new file mode 100644 index 00000000..a0ce00a1 --- /dev/null +++ b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/alloy/analysis/IAlloyAnalyzer.java @@ -0,0 +1,17 @@ +package eu.modelwriter.configuration.alloy.analysis; + +import edu.mit.csail.sdg.alloy4.Err; + +public interface IAlloyAnalyzer { + boolean start() throws Err; + + boolean next() throws Err; + + boolean previous() throws Err; + + void finish(); + + void setFilterState(boolean isOpen); + + void setNextSolMaxTime(int maxTime); +} diff --git a/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/alloy/analysis/RUN_TYPE.java b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/alloy/analysis/RUN_TYPE.java new file mode 100644 index 00000000..df04531b --- /dev/null +++ b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/alloy/analysis/RUN_TYPE.java @@ -0,0 +1,5 @@ +package eu.modelwriter.configuration.alloy.analysis; + +public enum RUN_TYPE { + NEXT, PREVIOUS, START +} diff --git a/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/alloy/analysis/StaticAlloyAnalysisManager.java b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/alloy/analysis/StaticAlloyAnalysisManager.java new file mode 100644 index 00000000..4c3ae829 --- /dev/null +++ b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/alloy/analysis/StaticAlloyAnalysisManager.java @@ -0,0 +1,151 @@ +package eu.modelwriter.configuration.alloy.analysis; + +import org.eclipse.ui.services.ISourceProviderService; + +import edu.mit.csail.sdg.alloy4.Err; +import eu.modelwriter.configuration.Activator; +import eu.modelwriter.configuration.alloy.analysis.consistencychecking.ConsistencyChecking; +import eu.modelwriter.configuration.alloy.analysis.discovering.Discovering; +import eu.modelwriter.configuration.alloy.analysis.provider.AnalysisSourceProvider; +import eu.modelwriter.configuration.alloy.analysis.provider.AnalysisSourceProvider.AnalysisFilter; +import eu.modelwriter.configuration.alloy.analysis.provider.AnalysisSourceProvider.AnalysisState; +import eu.modelwriter.configuration.alloy.analysis.provider.AnalysisSourceProvider.AnalysisType; +import eu.modelwriter.configuration.alloy.analysis.provider.AnalysisSourceProvider.EvaluationState; +import eu.modelwriter.configuration.alloy.analysis.reasoning.Reasoning; +import eu.modelwriter.configuration.alloy.analysis.reasoningforatom.ReasoningForAtom; + +public class StaticAlloyAnalysisManager { + private static IAlloyAnalyzer currentAnalyzer; + private static final AnalysisSourceProvider sourceProvider = (AnalysisSourceProvider) Activator + .getDefault().getWorkbench().getService(ISourceProviderService.class) + .getSourceProvider(AnalysisSourceProvider.ANALYSIS_STATE); + + public static boolean isAnalysisFilterOpen() { + return StaticAlloyAnalysisManager.sourceProvider.getAnalysisFilter() + .equals(AnalysisFilter.OPEN); + } + + public static void openAnalysisFilter() { + if (StaticAlloyAnalysisManager.currentAnalyzer == null) { + Reasoning.getInstance().setFilterState(true); + Discovering.getInstance().setFilterState(true); + ReasoningForAtom.getInstance(null, null).setFilterState(true); + } else { + StaticAlloyAnalysisManager.currentAnalyzer.setFilterState(true); + } + StaticAlloyAnalysisManager.sourceProvider.setAnalysisFilter(AnalysisFilter.OPEN); + } + + public static void closeAnalysisFilter() { + if (StaticAlloyAnalysisManager.currentAnalyzer == null) { + Reasoning.getInstance().setFilterState(false); + Discovering.getInstance().setFilterState(false); + ReasoningForAtom.getInstance(null, null).setFilterState(false); + } else { + StaticAlloyAnalysisManager.currentAnalyzer.setFilterState(false); + } + StaticAlloyAnalysisManager.sourceProvider.setAnalysisFilter(AnalysisFilter.CLOSE); + } + + public static boolean isEvaluatorOpen() { + return StaticAlloyAnalysisManager.sourceProvider.getEvaluationState() + .equals(EvaluationState.OPEN); + } + + public static void openEvaluator() { + StaticAlloyAnalysisManager.sourceProvider.setEvaluationState(EvaluationState.OPEN); + } + + public static void closeEvaluator() { + StaticAlloyAnalysisManager.sourceProvider.setEvaluationState(EvaluationState.CLOSE); + } + + public static boolean checkConsistency() { + return ConsistencyChecking.getInstance().check(); + } + + public static boolean isAnalysisActive() { + return StaticAlloyAnalysisManager.sourceProvider.getAnalysisState() + .equals(AnalysisState.ACTIVE); + } + + public static boolean isAnalysisProcessing() { + return StaticAlloyAnalysisManager.sourceProvider.getAnalysisState() + .equals(AnalysisState.PROCESSING); + } + + public static boolean startDiscovering() { + StaticAlloyAnalysisManager.currentAnalyzer = Discovering.getInstance(); + return StaticAlloyAnalysisManager.startAnalysis(); + } + + public static boolean startReasoning() { + StaticAlloyAnalysisManager.currentAnalyzer = Reasoning.getInstance(); + return StaticAlloyAnalysisManager.startAnalysis(); + } + + public static boolean startReasoningForAtom(final String atomName, final String atomType) { + StaticAlloyAnalysisManager.currentAnalyzer = ReasoningForAtom.getInstance(atomName, atomType); + return StaticAlloyAnalysisManager.startAnalysis(); + } + + private static boolean startAnalysis() { + StaticAlloyAnalysisManager.sourceProvider.setProcessing(); + boolean success = true; + try { + success = StaticAlloyAnalysisManager.currentAnalyzer.start(); + StaticAlloyAnalysisManager.sourceProvider.setActive(AnalysisType.REASON_RELATION_FOR_ATOM); + } catch (final Err e) { + success = false; + } + if (!success) { + StaticAlloyAnalysisManager.finishAnalysis(); + } + return success; + } + + public static boolean nextAnalysis() { + boolean success = true; + StaticAlloyAnalysisManager.sourceProvider.setProcessing(); + try { + success = StaticAlloyAnalysisManager.currentAnalyzer.next(); + } catch (final Err e) { + return false; + } finally { + StaticAlloyAnalysisManager.sourceProvider + .setActive(StaticAlloyAnalysisManager.sourceProvider.getAnalysisType()); + } + return success; + } + + public static boolean previousAnalysis() { + final boolean success = true; + StaticAlloyAnalysisManager.sourceProvider.setProcessing(); + try { + StaticAlloyAnalysisManager.currentAnalyzer.previous(); + } catch (final Err e) { + return false; + } finally { + StaticAlloyAnalysisManager.sourceProvider + .setActive(StaticAlloyAnalysisManager.sourceProvider.getAnalysisType()); + } + return success; + } + + public static void finishAnalysis() { + if (StaticAlloyAnalysisManager.currentAnalyzer != null) { + StaticAlloyAnalysisManager.currentAnalyzer.finish(); + } + StaticAlloyAnalysisManager.sourceProvider.setPassive(); + } + + public static void setNextSolutionMaxTime(final int time) { + if (StaticAlloyAnalysisManager.currentAnalyzer == null) { + Reasoning.getInstance().setNextSolMaxTime(time); + Discovering.getInstance().setNextSolMaxTime(time); + ReasoningForAtom.getInstance(null, null).setNextSolMaxTime(time); + } else { + StaticAlloyAnalysisManager.currentAnalyzer.setNextSolMaxTime(time); + } + } +} diff --git a/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/alloy/analysis/consistencychecking/ConsistencyChecking.java b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/alloy/analysis/consistencychecking/ConsistencyChecking.java new file mode 100644 index 00000000..e8a1707b --- /dev/null +++ b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/alloy/analysis/consistencychecking/ConsistencyChecking.java @@ -0,0 +1,43 @@ +package eu.modelwriter.configuration.alloy.analysis.consistencychecking; + +import java.io.File; + +import org.eclipse.core.resources.ResourcesPlugin; + +import edu.mit.csail.sdg.alloy4compiler.translator.A4Solution; +import eu.modelwriter.configuration.alloy.analysis.AlloySolutionFinder; + +public class ConsistencyChecking { + private static ConsistencyChecking instance; + public static String baseFileDirectory = ResourcesPlugin.getWorkspace().getRoot().getLocation() + + " .modelwriter validation ".replace(" ", System.getProperty("file.separator")); + private static final String alsPath = ConsistencyChecking.baseFileDirectory + "validation.als"; + + private ConsistencyChecking() {} + public static ConsistencyChecking getInstance() { + if (ConsistencyChecking.instance == null) { + ConsistencyChecking.instance = new ConsistencyChecking(); + } + + return ConsistencyChecking.instance; + } + + public boolean check() { + final File validationAls = new File(ConsistencyChecking.alsPath); + if (validationAls.exists()) { + validationAls.delete(); + } + + final InstanceTranslator4ConsistencyChecking instanceTranslator = + new InstanceTranslator4ConsistencyChecking(ConsistencyChecking.baseFileDirectory, + ConsistencyChecking.alsPath); + instanceTranslator.translate(); + + final AlloySolutionFinder parser = new AlloySolutionFinder(ConsistencyChecking.alsPath); + final A4Solution solution = parser.find(); + if (solution == null || !solution.satisfiable()) { + return false; + } + return true; + } +} diff --git a/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/alloy/analysis/consistencychecking/InstanceTranslator4ConsistencyChecking.java b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/alloy/analysis/consistencychecking/InstanceTranslator4ConsistencyChecking.java new file mode 100644 index 00000000..f423a5a3 --- /dev/null +++ b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/alloy/analysis/consistencychecking/InstanceTranslator4ConsistencyChecking.java @@ -0,0 +1,187 @@ +package eu.modelwriter.configuration.alloy.analysis.consistencychecking; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.eclipse.emf.common.util.EList; + +import eu.modelwriter.configuration.internal.AlloyUtilities; +import eu.modelwriter.traceability.core.persistence.AlloyType; +import eu.modelwriter.traceability.core.persistence.DocumentRoot; +import eu.modelwriter.traceability.core.persistence.FieldType; +import eu.modelwriter.traceability.core.persistence.SigType; +import eu.modelwriter.traceability.core.persistence.SourceType; +import eu.modelwriter.traceability.core.persistence.TupleType; + +public class InstanceTranslator4ConsistencyChecking { + private final Map sig2oldValue = new HashMap<>(); + + private final StringBuilder builder; + private final String baseFileDirectory; + private final String alsPath; + + public InstanceTranslator4ConsistencyChecking(final String baseFileDirectory, + final String alsPath) { + this.baseFileDirectory = baseFileDirectory; + this.alsPath = alsPath; + builder = new StringBuilder(); + } + + private void createFactPart(final DocumentRoot documentRoot, final List fields) { + builder.append("fact {\n"); + + for (final FieldType field : fields) { + final String fieldName = field.getLabel(); + int tupleCount = 0; + + String parentSigName = AlloyUtilities.getSigTypeById(field.getParentID()).getLabel(); + parentSigName = parentSigName.substring(parentSigName.indexOf("/") + 1); + + for (final TupleType tuple : field.getTuple()) { + tupleCount++; + + final String sigName1 = + AlloyUtilities.getAtomNameById(tuple.getAtom().get(0).getLabel()).replace("$", ""); + final String sigName2 = + AlloyUtilities.getAtomNameById(tuple.getAtom().get(1).getLabel()).replace("$", ""); + + builder.append(sigName1 + "->" + sigName2); + + if (tupleCount != field.getTuple().size()) { + builder.append(" +\n"); + } else { + builder.append(" = " + parentSigName + "<:" + fieldName + "\n"); + } + } + + if (field.getTuple().size() == 0) { + builder.append(parentSigName + "." + fieldName + " = none\n"); + } + } + + builder.append("}\n"); + } + + private File createFile(final String filePath) { + File file = null; + try { + file = new File(filePath); + + if (!file.getParentFile().exists()) { + file.getParentFile().mkdirs(); + } + // if file doesn't exists, then create it + if (!file.exists()) { + file.createNewFile(); + } + } catch (final IOException e) { + e.printStackTrace(); + } + return file; + } + + private void createSigPart(final List sigs) { + for (final SigType sig : sigs) { + final String sigName = sig.getLabel().substring(sig.getLabel().indexOf("/") + 1); + for (int i = 0; i < sig.getAtom().size(); i++) { + builder.append("one sig " + sigName + i + " extends " + sigName + "{ } \n"); + } + } + } + + private void createSourceFiles(final EList sources) { + boolean isFirst = false; + for (final SourceType source : sources) { + final String sourceFilePath = source.getFilename(); + final String fileName = sourceFilePath.substring( + sourceFilePath.lastIndexOf(System.getProperty("file.separator")) + 1, + sourceFilePath.lastIndexOf(".")); + if (!isFirst) { + builder.append("open " + fileName + "\n"); + isFirst = true; + } + final String newFilePath = baseFileDirectory + fileName + ".als"; + + final String newContent = removeReasoningParts(source.getContent()); + writeContentToFile(newFilePath, newContent); + } + } + + private String removeReasoningParts(final String content) { + String newContent = ""; + + final List lines = Arrays.asList(content.split("\n")); + + final Pattern p = + Pattern.compile("(-)(-)(\\s*)(Reason|reason)(@)((?:[a-z0-9_]+))(\\.)((?:[a-z0-9_]+))(\\s*)", + Pattern.CASE_INSENSITIVE | Pattern.DOTALL); + + boolean isRemoveFact = false; + for (final String line : lines) { + final Matcher matcher = p.matcher(line); + if (matcher.find()) { + isRemoveFact = true; + } else if (!isRemoveFact) { + newContent += line + "\n"; + } else if (line.contains("}")) { + isRemoveFact = false; + } + } + + return newContent; + } + + public void translate() { + final DocumentRoot documentRoot = AlloyUtilities.getDocumentRoot(); + final AlloyType alloy = documentRoot.getAlloy(); + + calcOldSigValues(alloy.getInstance().getSig()); + createSourceFiles(alloy.getSource()); + createSigPart(alloy.getInstance().getSig()); + createFactPart(documentRoot, alloy.getInstance().getField()); + createRunPart(); + + writeContentToFile(alsPath, builder.toString()); + } + + private void createRunPart() { + builder.append("pred show{}\n"); + builder.append("run show for"); + + for (final Entry oldEntry : sig2oldValue.entrySet()) { + final int value = oldEntry.getValue(); + builder.append("\nexactly " + value + " " + oldEntry.getKey() + ","); + } + + builder.replace(0, builder.length(), builder.substring(0, builder.length() - 1)); // to delete + // last ',' + } + + private void writeContentToFile(final String filePath, final String content) { + try { + final File file = createFile(filePath); + final FileOutputStream out = new FileOutputStream(file); + out.write(content.getBytes()); + out.close(); + } catch (final IOException e) { + e.printStackTrace(); + } + } + + private void calcOldSigValues(final EList sigTypes) { + for (final SigType sigType : sigTypes) { + final String sigName = sigType.getLabel().substring(sigType.getLabel().lastIndexOf("/") + 1); + if (sigType.getID() > 3) { + sig2oldValue.put(sigName, sigType.getAbstract() != null ? 0 : sigType.getAtom().size()); + } + } + } +} diff --git a/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/alloy/analysis/discovering/Discovering.java b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/alloy/analysis/discovering/Discovering.java new file mode 100644 index 00000000..5136db5f --- /dev/null +++ b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/alloy/analysis/discovering/Discovering.java @@ -0,0 +1,862 @@ +package eu.modelwriter.configuration.alloy.analysis.discovering; + +import java.io.File; +import java.io.IOException; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.stream.Collectors; + +import javax.swing.JOptionPane; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; + +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.emf.common.util.EList; +import org.eclipse.emf.common.util.URI; +import org.w3c.dom.Document; +import org.w3c.dom.Node; +import org.xml.sax.SAXException; + +import edu.mit.csail.sdg.alloy4.Err; +import edu.mit.csail.sdg.alloy4compiler.translator.A4Solution; +import eu.modelwriter.configuration.alloy.analysis.AlloySolutionFinder; +import eu.modelwriter.configuration.alloy.analysis.IAlloyAnalyzer; +import eu.modelwriter.configuration.alloy.analysis.RUN_TYPE; +import eu.modelwriter.configuration.internal.AlloyUtilities; +import eu.modelwriter.marker.internal.MarkerFactory; +import eu.modelwriter.traceability.core.persistence.AtomType; +import eu.modelwriter.traceability.core.persistence.DocumentRoot; +import eu.modelwriter.traceability.core.persistence.FieldType; +import eu.modelwriter.traceability.core.persistence.SigType; +import eu.modelwriter.traceability.core.persistence.TupleType; +import eu.modelwriter.traceability.core.persistence.TypesType; +import eu.modelwriter.traceability.core.persistence.persistenceFactory; +import eu.modelwriter.traceability.core.persistence.internal.ModelIO; + +public class Discovering implements IAlloyAnalyzer { + + private static Discovering instance; + private static String baseFileDirectory = ResourcesPlugin.getWorkspace().getRoot().getLocation() + + " .modelwriter discovering ".replace(" ", System.getProperty("file.separator")); + private static final String alsPath = Discovering.baseFileDirectory + "discovering.als"; + private static final String xmlPath = Discovering.baseFileDirectory + "discovering.xml"; + private static Map discoverSigs; + private static List solutions; + private static List>> discoveredAtoms; + private static List>> reasonedTuples; + private static List> discoveredAtomLabels2OriginalAtomTypes; + private static List> reasonedTupleStrings2OriginalTupleTypes; + private static int currentSolutionIndex; + private static int MAX_NEXT_SOLUTION_ATTEMPT_TIME = 10; // 10 sec + private static long NEXT_SOLUTION_ATTEMPT_START_TIME; + private static int NEXT_SOLUTION_ATTEMPT_COUNT; + private static boolean filterOpen = true; + + private Discovering() {} + + public static Discovering getInstance() { + if (Discovering.instance == null) { + Discovering.instance = new Discovering(); + Discovering.discoverSigs = new HashMap<>(); + Discovering.solutions = new ArrayList<>(); + Discovering.discoveredAtoms = new ArrayList<>(); + Discovering.reasonedTuples = new ArrayList<>(); + Discovering.discoveredAtomLabels2OriginalAtomTypes = new ArrayList<>(); + Discovering.reasonedTupleStrings2OriginalTupleTypes = new ArrayList<>(); + } + + return Discovering.instance; + } + + @Override + public boolean start() throws Err { + final File discoveringXml = new File(Discovering.xmlPath); + if (discoveringXml.exists()) { + discoveringXml.delete(); + } + final File discoveringAls = new File(Discovering.alsPath); + if (discoveringAls.exists()) { + discoveringAls.delete(); + } + + final InstanceTranslator4Discovering instanceTranslator = + new InstanceTranslator4Discovering(Discovering.baseFileDirectory, Discovering.alsPath); + instanceTranslator.translate(); + Discovering.discoverSigs = instanceTranslator.getDiscoverSig2ExpectValue(); + + final AlloySolutionFinder parser = new AlloySolutionFinder(Discovering.alsPath); + final A4Solution solution = parser.find(); + + if (solution == null || !solution.satisfiable()) { + return false; + } + + solution.writeXML(Discovering.xmlPath); + parse(Discovering.xmlPath); + Discovering.solutions.add(solution); + Discovering.currentSolutionIndex = 0; + Discovering.NEXT_SOLUTION_ATTEMPT_COUNT = 0; + + return discovering(RUN_TYPE.START); + } + + @Override + public boolean next() throws Err { + A4Solution solution; + if (Discovering.solutions.size() == Discovering.currentSolutionIndex + 1) { + solution = Discovering.solutions.get(Discovering.currentSolutionIndex).next(); + if (solution.equals(Discovering.solutions.get(Discovering.currentSolutionIndex))) { + return false; + } else if (solution.satisfiable()) { + Discovering.solutions.add(solution); + Discovering.currentSolutionIndex++; + } else { + return false; + } + } else { + solution = Discovering.solutions.get(Discovering.currentSolutionIndex + 1); + Discovering.currentSolutionIndex++; + } + + solution.writeXML(Discovering.xmlPath); + parse(Discovering.xmlPath); + + return discovering(RUN_TYPE.NEXT); + } + + @Override + public boolean previous() throws Err { + A4Solution solution; + if (Discovering.currentSolutionIndex == 0) { + return false; + } else { + solution = Discovering.solutions.get(Discovering.currentSolutionIndex - 1); + Discovering.currentSolutionIndex--; + } + + solution.writeXML(Discovering.xmlPath); + parse(Discovering.xmlPath); + + return discovering(RUN_TYPE.PREVIOUS); + } + + private void parse(final String xmlPath) { + final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + DocumentBuilder builder; + try { + builder = factory.newDocumentBuilder(); + final File file = new File(xmlPath); + final Document document = builder.parse(file); + final Node instance = document.getElementsByTagName("instance").item(0); + instance.getAttributes().removeNamedItem("command"); + + Transformer transformer; + try { + transformer = TransformerFactory.newInstance().newTransformer(); + final DOMSource source = new DOMSource(document); + final StreamResult result = new StreamResult(file); + transformer.transform(source, result); + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2"); + } catch (final Exception e) { + } + } catch (ParserConfigurationException | SAXException | IOException e) { + e.printStackTrace(); + } + } + + private void deleteOldDiscovering(final RUN_TYPE runType) { + final DocumentRoot documentRoot = AlloyUtilities.getDocumentRoot(); + int solutionNumber = runType == RUN_TYPE.NEXT ? Discovering.currentSolutionIndex - 1 + : runType == RUN_TYPE.PREVIOUS ? Discovering.currentSolutionIndex + 1 : -1; + solutionNumber = solutionNumber >= Discovering.discoveredAtoms.size() ? -1 : solutionNumber; + if (solutionNumber == -1) { + return; + } + + final Iterator>> oldDiscoveredAtomsIterator = + Discovering.discoveredAtoms.get(solutionNumber).entrySet().iterator(); + final EList sigTypes = documentRoot.getAlloy().getInstance().getSig(); + while (oldDiscoveredAtomsIterator.hasNext()) { + final Entry> entry = oldDiscoveredAtomsIterator.next(); + for (final SigType sigType : sigTypes) { + if (sigType.getID() == entry.getKey()) { + for (final AtomType oldAtomType : entry.getValue()) { + final Iterator atomIter = sigType.getAtom().iterator(); + while (atomIter.hasNext()) { + final AtomType atomType = atomIter.next(); + if (atomType.isReasoned()) { + if (oldAtomType.getLabel().equals(atomType.getLabel())) { + atomIter.remove(); + } + } + } + } + } + } + } + + final Iterator>> oldReasonedTuplesIterator = + Discovering.reasonedTuples.get(solutionNumber).entrySet().iterator(); + final EList fieldTypes = documentRoot.getAlloy().getInstance().getField(); + while (oldReasonedTuplesIterator.hasNext()) { + final Entry> entry = oldReasonedTuplesIterator.next(); + for (final FieldType fieldType : fieldTypes) { + if (fieldType.getID() == entry.getKey()) { + for (final TupleType oldTupleType : entry.getValue()) { + final Iterator tupleIter = fieldType.getTuple().iterator(); + final AtomType oldAtomType0 = oldTupleType.getAtom().get(0); + final AtomType oldAtomType1 = oldTupleType.getAtom().get(1); + while (tupleIter.hasNext()) { + final TupleType tupleType = tupleIter.next(); + if (tupleType.isReasoned()) { + final AtomType atomType0 = tupleType.getAtom().get(0); + final AtomType atomType1 = tupleType.getAtom().get(1); + if (oldAtomType0.getLabel().equals(atomType0.getLabel()) + && oldAtomType1.getLabel().equals(atomType1.getLabel())) { + tupleIter.remove(); + } + } + } + } + } + } + } + + final int discoveredAtomCount = + calcDiscoveredAtomCount(Discovering.discoveredAtoms.get(solutionNumber)); + MarkerFactory.rewindId(discoveredAtomCount, documentRoot); + + AlloyUtilities.writeDocumentRoot(documentRoot); + } + + @Override + public void finish() { + Discovering.solutions.clear(); + Discovering.currentSolutionIndex = 0; + Discovering.discoverSigs.clear(); + Discovering.discoveredAtoms.clear(); + Discovering.reasonedTuples.clear(); + Discovering.discoveredAtomLabels2OriginalAtomTypes.clear(); + Discovering.reasonedTupleStrings2OriginalTupleTypes.clear(); + } + + public DocumentRoot getDocumentRoot() { + @SuppressWarnings("rawtypes") + final ModelIO modelIO = new ModelIO<>(); + @SuppressWarnings("rawtypes") + List list = null; + try { + list = modelIO.read(URI.createFileURI(Discovering.xmlPath)); + } catch (final IOException e) { + return null; + } + if (list == null || list.isEmpty()) { + return null; + } + final DocumentRoot documentRoot = (DocumentRoot) list.get(0); + return documentRoot; + } + + private boolean discovering(final RUN_TYPE runType) throws Err { + deleteOldDiscovering(runType); + + int unacceptedReasonedTupleCount = 0, acceptedTupleCount = 0; + int uninterpretedDiscoveredAtomCount = 0, interpretedAtomCount = 0; + if (Discovering.solutions.size() > Discovering.discoveredAtoms.size()) { + + final Map> newDiscoveredAtoms = findDiscoveredAtoms(); + Discovering.discoveredAtoms.add(newDiscoveredAtoms); + + final Map> newReasonedTuples = findReasonedTuples(); + Discovering.reasonedTuples.add(newReasonedTuples); + + if (Discovering.filterOpen) { + if (!isValidAndUniqueReason(newDiscoveredAtoms, newReasonedTuples)) { + final long nextSolutionProcessTime = + Instant.now().getEpochSecond() - Discovering.NEXT_SOLUTION_ATTEMPT_START_TIME; + if (Discovering.NEXT_SOLUTION_ATTEMPT_COUNT != 0 + && nextSolutionProcessTime > Discovering.MAX_NEXT_SOLUTION_ATTEMPT_TIME) { + final boolean rollBackSucceed = rollBackNextAttemption(false); + if (rollBackSucceed) { + final Map> uninterpretedAtomTypes = + filterInterpretedAtomTypes( + Discovering.discoveredAtoms.get(Discovering.currentSolutionIndex)); + writeDiscoveredAtomTypes(uninterpretedAtomTypes); + uninterpretedDiscoveredAtomCount = calcDiscoveredAtomCount(uninterpretedAtomTypes); + + final Map> unacceptedTupleTypes = filterAcceptedTupleTypes( + Discovering.reasonedTuples.get(Discovering.currentSolutionIndex)); + writeReasonedTupleTypes(unacceptedTupleTypes); + unacceptedReasonedTupleCount = calcReasonedTupleCount(unacceptedTupleTypes); + } + return false; + } + if (Discovering.NEXT_SOLUTION_ATTEMPT_COUNT == 0) { + Discovering.NEXT_SOLUTION_ATTEMPT_START_TIME = Instant.now().getEpochSecond(); + } + Discovering.NEXT_SOLUTION_ATTEMPT_COUNT++; + return next(); + } else { + if (Discovering.NEXT_SOLUTION_ATTEMPT_COUNT > 0) { + rollBackNextAttemption(true); + } + writeDiscoveredAtomTypes(newDiscoveredAtoms); + uninterpretedDiscoveredAtomCount = calcDiscoveredAtomCount(newDiscoveredAtoms); + writeReasonedTupleTypes(newReasonedTuples); + unacceptedReasonedTupleCount = calcReasonedTupleCount(newReasonedTuples); + } + } else { + final int discoveredAtomCount = calcDiscoveredAtomCount(newDiscoveredAtoms); + final Map> uninterpretedAtomTypes = + filterInterpretedAtomTypes(newDiscoveredAtoms); + writeDiscoveredAtomTypes(uninterpretedAtomTypes); + uninterpretedDiscoveredAtomCount = calcDiscoveredAtomCount(uninterpretedAtomTypes); + interpretedAtomCount = discoveredAtomCount - uninterpretedDiscoveredAtomCount; + + final int reasonedTupleCount = calcReasonedTupleCount(newReasonedTuples); + final Map> unacceptedTupleTypes = + filterAcceptedTupleTypes(newReasonedTuples); + writeReasonedTupleTypes(unacceptedTupleTypes); + unacceptedReasonedTupleCount = calcReasonedTupleCount(unacceptedTupleTypes); + acceptedTupleCount = reasonedTupleCount - unacceptedReasonedTupleCount; + } + } else { + final Map> existingDiscoveredAtoms = + Discovering.discoveredAtoms.get(Discovering.currentSolutionIndex); + final Map> existingReasonedTuples = + Discovering.reasonedTuples.get(Discovering.currentSolutionIndex); + + if (Discovering.filterOpen) { + if (!isValidAndUniqueReason(existingDiscoveredAtoms, existingReasonedTuples)) { + if (runType.equals(RUN_TYPE.NEXT)) { + return next(); + } else if (runType.equals(RUN_TYPE.PREVIOUS)) { + return previous(); + } + } + writeDiscoveredAtomTypes(existingDiscoveredAtoms); + uninterpretedDiscoveredAtomCount = calcDiscoveredAtomCount(existingDiscoveredAtoms); + writeReasonedTupleTypes(existingReasonedTuples); + unacceptedReasonedTupleCount = calcReasonedTupleCount(existingReasonedTuples); + } else { + final int discoveredAtomCount = calcDiscoveredAtomCount(existingDiscoveredAtoms); + final Map> uninterpretedAtomTypes = + filterInterpretedAtomTypes(existingDiscoveredAtoms); + writeDiscoveredAtomTypes(uninterpretedAtomTypes); + uninterpretedDiscoveredAtomCount = calcDiscoveredAtomCount(uninterpretedAtomTypes); + interpretedAtomCount = discoveredAtomCount - uninterpretedDiscoveredAtomCount; + + final int reasonedTupleCount = calcReasonedTupleCount(existingReasonedTuples); + final Map> unacceptedTupleTypes = + filterAcceptedTupleTypes(existingReasonedTuples); + writeReasonedTupleTypes(unacceptedTupleTypes); + unacceptedReasonedTupleCount = calcReasonedTupleCount(unacceptedTupleTypes); + acceptedTupleCount = reasonedTupleCount - unacceptedReasonedTupleCount; + } + } + + final String discoveredAtomCountMessage = + uninterpretedDiscoveredAtomCount + (interpretedAtomCount > 0 + ? "\nYou interpreted " + interpretedAtomCount + " discovered atom before." : ""); + final String reasonedTupleCountMessage = unacceptedReasonedTupleCount + (acceptedTupleCount > 0 + ? "\nYou accepted " + acceptedTupleCount + " reasoned relation before." : ""); + + final String discoveringAtomMessage = + "Discovering on atoms successfully completed.\nDiscovered atom count: " + + discoveredAtomCountMessage; + final String discoveringRelationMessage = + "Reasoning on relations successfully completed.\nReasoned relation count: " + + reasonedTupleCountMessage; + + JOptionPane.showMessageDialog(null, discoveringAtomMessage + "\n" + discoveringRelationMessage, + "Discovering Atom", JOptionPane.INFORMATION_MESSAGE); + return true; + } + + private Map> filterInterpretedAtomTypes( + final Map> newDiscoveredAtoms) { + final DocumentRoot documentRoot = AlloyUtilities.getDocumentRoot(); + final Map> uninterpretedAtomTypes = new HashMap<>(); + + final Iterator sigIterator = documentRoot.getAlloy().getInstance().getSig().iterator(); + while (sigIterator.hasNext()) { + final SigType oldSigType = sigIterator.next(); + final int sigId = oldSigType.getID(); + final List newAtomTypes = newDiscoveredAtoms.get(sigId); + if (newAtomTypes != null) { + final List uninterpretedAtomTypesList = new ArrayList<>(); + for (final AtomType newAtomType : newAtomTypes) { + if (!isAtomInterpreted(sigId, newAtomType)) { // IF DISCOVERED ATOM IS NOT INTERPRETED + // DURING ANALYSIS + uninterpretedAtomTypesList.add(newAtomType); + } + } + uninterpretedAtomTypes.put(sigId, uninterpretedAtomTypesList); + } + } + + return uninterpretedAtomTypes; + } + + /** + * checks if atomType is interpreted in persistence file. + * + * @param sigId + * @param atomType + * @return + */ + private boolean isAtomInterpreted(final int sigId, final AtomType atomType) { + return AlloyUtilities.getDocumentRoot().getAlloy().getInstance().getSig().stream() + .filter(s -> s.getID() == sigId).findFirst().get().getAtom().stream() + .anyMatch(a -> a.getLabel().equals(atomType.getLabel())); + } + + private void writeDiscoveredAtomTypes(final Map> newDiscoveredAtoms) { + final DocumentRoot documentRoot = AlloyUtilities.getDocumentRoot(); + + final Iterator sigIterator = documentRoot.getAlloy().getInstance().getSig().iterator(); + while (sigIterator.hasNext()) { + final SigType oldSigType = sigIterator.next(); + final int sigId = oldSigType.getID(); + final List newAtomTypes = newDiscoveredAtoms.get(sigId); + if (newAtomTypes != null) { + oldSigType.getAtom().addAll(newAtomTypes); + } + } + + AlloyUtilities.writeDocumentRoot(documentRoot); + } + + /** + * filters accepted tupleTypes and returns unaccepted ones. + * + * @param reasonedTuples + * @return + */ + private Map> filterAcceptedTupleTypes( + final Map> reasonedTuples) { + final DocumentRoot documentRoot = AlloyUtilities.getDocumentRoot(); + final Map> unacceptedTupleTypes = new HashMap<>(); + + final Iterator fieldIterator = + documentRoot.getAlloy().getInstance().getField().iterator(); + while (fieldIterator.hasNext()) { + final FieldType oldFieldType = fieldIterator.next(); + final int fieldId = oldFieldType.getID(); + final List newTupleTypes = reasonedTuples.get(fieldId); + if (newTupleTypes != null) { + final List unacceptedTupleTypesList = new ArrayList<>(); + for (final TupleType newTupleType : newTupleTypes) { + if (!isTupleAccepted(fieldId, newTupleType)) { // IF REASONED TUPLE IS NOT ACCEPTED DURING + // ANALYSIS. + unacceptedTupleTypesList.add(newTupleType); + } + } + unacceptedTupleTypes.put(fieldId, unacceptedTupleTypesList); + } + } + + return unacceptedTupleTypes; + } + + /** + * checks if tupleType is accepted in persistence file. + * + * @param fieldId + * @param tupleType + * @return + */ + private boolean isTupleAccepted(final int fieldId, final TupleType tupleType) { + return AlloyUtilities.getDocumentRoot().getAlloy().getInstance().getField().stream() + .filter(f -> f.getID() == fieldId).findFirst().get().getTuple().stream() + .anyMatch(t -> (t.getAtom().get(0).getLabel().equals(tupleType.getAtom().get(0).getLabel()) + && t.getAtom().get(1).getLabel().equals(tupleType.getAtom().get(1).getLabel()))); + } + + private void writeReasonedTupleTypes(final Map> newReasonedTuples) { + final DocumentRoot documentRoot = AlloyUtilities.getDocumentRoot(); + + final Iterator fieldIterator = + documentRoot.getAlloy().getInstance().getField().iterator(); + while (fieldIterator.hasNext()) { + final FieldType oldFieldType = fieldIterator.next(); + final int fieldId = oldFieldType.getID(); + final List newTupleTypes = newReasonedTuples.get(fieldId); + if (newTupleTypes != null) { + oldFieldType.getTuple().addAll(newTupleTypes); + } + } + + AlloyUtilities.writeDocumentRoot(documentRoot); + } + + private boolean rollBackNextAttemption(final boolean lastSolutionIsValid) { + final int lastSolutionIndex = Discovering.solutions.size() - 1; + int validSolutionIndex = lastSolutionIsValid ? lastSolutionIndex + : lastSolutionIndex - Discovering.NEXT_SOLUTION_ATTEMPT_COUNT - 1; + Discovering.currentSolutionIndex = + lastSolutionIsValid ? lastSolutionIndex - Discovering.NEXT_SOLUTION_ATTEMPT_COUNT + : lastSolutionIndex - Discovering.NEXT_SOLUTION_ATTEMPT_COUNT - 1; + Discovering.NEXT_SOLUTION_ATTEMPT_COUNT = 0; + Discovering.NEXT_SOLUTION_ATTEMPT_START_TIME = 0; + + validSolutionIndex = validSolutionIndex > 0 ? validSolutionIndex : 0; + Discovering.currentSolutionIndex = + Discovering.currentSolutionIndex > 0 ? Discovering.currentSolutionIndex : 0; + + if (Discovering.discoveredAtoms.get(validSolutionIndex).size() == 0) { + finish(); + return false; + } + + Discovering.solutions.set(Discovering.currentSolutionIndex, + Discovering.solutions.get(validSolutionIndex)); + Discovering.reasonedTuples.set(Discovering.currentSolutionIndex, + Discovering.reasonedTuples.get(validSolutionIndex)); + Discovering.discoveredAtoms.set(Discovering.currentSolutionIndex, + Discovering.discoveredAtoms.get(validSolutionIndex)); + Discovering.discoveredAtomLabels2OriginalAtomTypes.set(Discovering.currentSolutionIndex, + Discovering.discoveredAtomLabels2OriginalAtomTypes.get(validSolutionIndex)); + Discovering.reasonedTupleStrings2OriginalTupleTypes.set(Discovering.currentSolutionIndex, + Discovering.reasonedTupleStrings2OriginalTupleTypes.get(validSolutionIndex)); + + for (int i = lastSolutionIndex; i > Discovering.currentSolutionIndex; i--) { + Discovering.solutions.remove(i); + Discovering.reasonedTuples.remove(i); + Discovering.discoveredAtoms.remove(i); + Discovering.discoveredAtomLabels2OriginalAtomTypes.remove(i); + Discovering.reasonedTupleStrings2OriginalTupleTypes.remove(i); + } + return true; + } + + private int calcReasonedTupleCount(final Map> newReasonedTuples) { + int reasonedTupleCount = 0; + for (final Entry> entry : newReasonedTuples.entrySet()) { + reasonedTupleCount += entry.getValue().size(); + } + return reasonedTupleCount; + } + + private int calcDiscoveredAtomCount(final Map> newDiscoveredAtoms) { + int discoveredAtomCount = 0; + for (final Entry> entry : newDiscoveredAtoms.entrySet()) { + discoveredAtomCount += entry.getValue().size(); + } + return discoveredAtomCount; + } + + private Map> findDiscoveredAtoms() throws Err { + final DocumentRoot documentRootDiscovering = getDocumentRoot(); + final DocumentRoot documentRootOriginal = AlloyUtilities.getDocumentRoot(); + + final Map> newDiscoveredAtoms = new HashMap<>(); + final Map discoveredAtomLabel2OriginalAtomType = new HashMap<>(); + + final String moduleName = AlloyUtilities.getOriginalModuleName(); + + for (final SigType sigType_D : documentRootDiscovering.getAlloy().getInstance().getSig()) { + if (!sigType_D.getLabel().contains(moduleName)) { + continue; + } + final String label = sigType_D.getLabel(); + final String sigName = label.substring(label.lastIndexOf("/") + 1); + if (!Discovering.discoverSigs.containsKey(sigName)) { + continue; + } + + for (final AtomType atomType_D : sigType_D.getAtom()) { + final String id = MarkerFactory.generateId(documentRootOriginal); + final AtomType atomType_O = persistenceFactory.eINSTANCE.createAtomType(); + atomType_O.setLabel(id); + atomType_O.setReasoned(true); + + discoveredAtomLabel2OriginalAtomType.put(atomType_D.getLabel(), atomType_O); + + final SigType sigType_O = AlloyUtilities.getSigTypeById( + AlloyUtilities.getSigTypeIdByName(sigName, documentRootOriginal), documentRootOriginal); + + if (newDiscoveredAtoms.get(sigType_O.getID()) == null) { + newDiscoveredAtoms.put(sigType_O.getID(), new ArrayList<>(Arrays.asList(atomType_O))); + } else { + newDiscoveredAtoms.get(sigType_O.getID()).add(atomType_O); + } + } + } + + Discovering.discoveredAtomLabels2OriginalAtomTypes.add(discoveredAtomLabel2OriginalAtomType); + return newDiscoveredAtoms; + } + + private Map> findReasonedTuples() throws Err { + final DocumentRoot documentRootDiscovering = getDocumentRoot(); + final DocumentRoot documentRootOriginal = AlloyUtilities.getDocumentRoot(); + + final Map> newReasonedTuples = new HashMap<>(); + final Map reasonedTupleString2OriginalTupleType = new HashMap<>(); + + final String moduleName = AlloyUtilities.getOriginalModuleName(); + + final Map reasonedTuples_D = new HashMap<>(); + + for (final SigType sigType_D : documentRootDiscovering.getAlloy().getInstance().getSig()) { + if (!sigType_D.getLabel().contains(moduleName)) { + continue; + } + final String label = sigType_D.getLabel(); + final String sigName = label.substring(label.lastIndexOf("/") + 1); + if (!Discovering.discoverSigs.containsKey(sigName)) { + continue; + } + + final int id = sigType_D.getID(); + final ArrayList allParentIds = + AlloyUtilities.getAllParentIds(id, documentRootDiscovering); + + for (final FieldType fieldType_D : documentRootDiscovering.getAlloy().getInstance() + .getField()) { + for (final TypesType typesType_D : fieldType_D.getTypes()) { + for (int i = 0; i < typesType_D.getType().size(); i++) { + if (allParentIds.contains(typesType_D.getType().get(i).getID())) { + for (final TupleType tupleType_D : fieldType_D.getTuple()) { + for (final AtomType atomType : tupleType_D.getAtom()) { + if (atomType.getLabel().contains(moduleName)) { + reasonedTuples_D.put(tupleType_D, fieldType_D); + break; + } + } + } + } + } + } + } + } + + for (final Entry entry : reasonedTuples_D.entrySet()) { + + final TupleType tupleType_D = entry.getKey(); + final FieldType fieldType_D = entry.getValue(); + final String fieldName = fieldType_D.getLabel(); + + final EList fieldTypesList = + documentRootOriginal.getAlloy().getInstance().getField(); + for (final FieldType fieldType_O : fieldTypesList) { + + final String originalParentName = + AlloyUtilities.getSigNameById(fieldType_O.getParentID(), documentRootOriginal); + final String discoverParentName = + AlloyUtilities.getSigNameById(fieldType_D.getParentID(), documentRootDiscovering); + + if (originalParentName.equals(discoverParentName) + && fieldType_O.getLabel().equals(fieldName)) { + + String sourceAtomLabel = tupleType_D.getAtom().get(0).getLabel(); + AtomType atomType0_O = Discovering.discoveredAtomLabels2OriginalAtomTypes + .get(Discovering.currentSolutionIndex).get(sourceAtomLabel); + final String sourceTupleString = sourceAtomLabel; + if (atomType0_O == null) { + atomType0_O = getOriginalAtomType(sourceAtomLabel); + sourceAtomLabel = atomType0_O.getLabel(); + } + atomType0_O = AlloyUtilities.cloneAtomType(atomType0_O); + + final String targetAtomLabel = tupleType_D.getAtom().get(1).getLabel(); + AtomType atomType1_O = Discovering.discoveredAtomLabels2OriginalAtomTypes + .get(Discovering.currentSolutionIndex).get(targetAtomLabel); + String targetTupleString = targetAtomLabel; + if (atomType1_O == null) { + atomType1_O = getOriginalAtomType(targetAtomLabel); + targetTupleString = atomType1_O.getLabel(); + } + atomType1_O = AlloyUtilities.cloneAtomType(atomType1_O); + + final TupleType tupleType_O = persistenceFactory.eINSTANCE.createTupleType(); + tupleType_O.getAtom().addAll(Arrays.asList(new AtomType[] {atomType0_O, atomType1_O})); + tupleType_O.setReasoned(true); + reasonedTupleString2OriginalTupleType.put(sourceTupleString + " -> " + targetTupleString, + tupleType_O); + + if (newReasonedTuples.get(fieldType_O.getID()) == null) { + newReasonedTuples.put(fieldType_O.getID(), new ArrayList<>(Arrays.asList(tupleType_O))); + } else { + newReasonedTuples.get(fieldType_O.getID()).add(tupleType_O); + } + } + } + } + Discovering.reasonedTupleStrings2OriginalTupleTypes.add(reasonedTupleString2OriginalTupleType); + return newReasonedTuples; + } + + private boolean isValidAndUniqueReason(final Map> discoveredAtoms, + final Map> reasonedTuples) { + final Map> uninterpretedAtomTypes = + filterInterpretedAtomTypes(discoveredAtoms); + final Map> unacceptedTupleTypes = + filterAcceptedTupleTypes(reasonedTuples); + + final int uninterpretedDiscoveredAtomCount = calcDiscoveredAtomCount(uninterpretedAtomTypes); + if (uninterpretedDiscoveredAtomCount == 0) { // 1 + return false; + } + // dont swap 1 and 2 + if (Discovering.solutions.size() == 1) { // 2 + return true; + } + + if (getUnmatchedExistingDiscoveringCount( + uninterpretedAtomTypes) == Discovering.discoveredAtoms.size() - 1) { + return true; + } + + if (getUnmatchedExistingReasoningCount( + unacceptedTupleTypes) == Discovering.reasonedTuples.size() - 1) { + return true; + } + return false; + } + + private int getUnmatchedExistingDiscoveringCount( + final Map> discoveredAtoms) { + int unMatchedExistingDiscoveringCount = 0; + for (int solutionNumber = 0; solutionNumber < Discovering.discoveredAtoms + .size(); solutionNumber++) { + if (solutionNumber == Discovering.currentSolutionIndex) { + continue; + } + final Map> existingDiscoveredAtoms = + Discovering.discoveredAtoms.get(solutionNumber); + for (final Entry> newDiscoveredAtomsEntry : discoveredAtoms + .entrySet()) { + final Integer sigId = newDiscoveredAtomsEntry.getKey(); + final List atomList_New = newDiscoveredAtomsEntry.getValue(); + if (existingDiscoveredAtoms.containsKey(sigId)) { + final List atomList_Old = existingDiscoveredAtoms.get(sigId); + if (atomList_New.size() != atomList_Old.size()) { + unMatchedExistingDiscoveringCount++; + break; + } + int matchedAtomCount = 0; + for (final AtomType atomType_New : atomList_New) { + final String atomLabel_New = atomType_New.getLabel(); + + final List> list = + Discovering.discoveredAtomLabels2OriginalAtomTypes + .get(Discovering.currentSolutionIndex).entrySet().stream() + .filter(e -> e.getValue().getLabel().equals(atomLabel_New)) + .collect(Collectors.toList()); + String atomLabel_New_D = ""; + if (list.size() > 0) { + atomLabel_New_D = list.get(0).getKey(); + } + final AtomType atomType_Old = Discovering.discoveredAtomLabels2OriginalAtomTypes + .get(solutionNumber).get(atomLabel_New_D); + if (atomType_Old != null) { + matchedAtomCount++; + } + } + if (matchedAtomCount != atomList_New.size()) { + unMatchedExistingDiscoveringCount++; + break; + } + } else { + unMatchedExistingDiscoveringCount++; + break; + } + } + } + return unMatchedExistingDiscoveringCount; + } + + private int getUnmatchedExistingReasoningCount( + final Map> reasonedTuples) { + int unMatchedExistingReasoningCount = 0; + for (int solutionNumber = 0; solutionNumber < Discovering.reasonedTuples + .size(); solutionNumber++) { + if (solutionNumber == Discovering.currentSolutionIndex) { + continue; + } + final Map> existingReasonedTuples = + Discovering.reasonedTuples.get(solutionNumber); + for (final Entry> newReasonsedTuplesEntry : reasonedTuples + .entrySet()) { + final Integer fieldId = newReasonsedTuplesEntry.getKey(); + final List tupleList_New = newReasonsedTuplesEntry.getValue(); + if (existingReasonedTuples.containsKey(fieldId)) { + final List tupleList_Old = existingReasonedTuples.get(fieldId); + if (tupleList_New.size() != tupleList_Old.size()) { + unMatchedExistingReasoningCount++; + break; + } + int matchedTupleCount = 0; + for (final TupleType tupleType_New : tupleList_New) { + final AtomType atomType0_New = tupleType_New.getAtom().get(0); + final String atom0Label_New = atomType0_New.getLabel(); + + final AtomType atomType1_New = tupleType_New.getAtom().get(1); + final String atom1Label_New = atomType1_New.getLabel(); + + final List> list = + Discovering.reasonedTupleStrings2OriginalTupleTypes + .get(Discovering.currentSolutionIndex).entrySet().stream() + .filter(e -> (e.getValue().getAtom().get(0).getLabel().equals(atom0Label_New) + && e.getValue().getAtom().get(1).getLabel().equals(atom1Label_New))) + .collect(Collectors.toList()); + String tupleString_New_D = ""; + if (list.size() > 0) { + tupleString_New_D = list.get(0).getKey(); + } + + final TupleType tupleType_Old = Discovering.reasonedTupleStrings2OriginalTupleTypes + .get(solutionNumber).get(tupleString_New_D); + if (tupleType_Old != null) { + matchedTupleCount++; + } + } + if (matchedTupleCount != tupleList_New.size()) { + unMatchedExistingReasoningCount++; + break; + } + } else { + unMatchedExistingReasoningCount++; + break; + } + } + } + return unMatchedExistingReasoningCount; + } + + private AtomType getOriginalAtomType(final String name_R) { + if (name_R.contains("/")) { + return null; + } + final String name = name_R.substring(0, name_R.lastIndexOf("_")); + final int id = + Integer.parseInt(name_R.substring(name_R.lastIndexOf("_") + 1, name_R.lastIndexOf("$"))); + + return AlloyUtilities.getSigTypeById(AlloyUtilities.getSigTypeIdByName(name)).getAtom().get(id); + } + + @Override + public void setFilterState(final boolean isOpen) { + Discovering.filterOpen = isOpen; + } + + @Override + public void setNextSolMaxTime(final int maxTime) { + Discovering.MAX_NEXT_SOLUTION_ATTEMPT_TIME = maxTime; + } +} diff --git a/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/alloy/analysis/discovering/InstanceTranslator4Discovering.java b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/alloy/analysis/discovering/InstanceTranslator4Discovering.java new file mode 100644 index 00000000..33985596 --- /dev/null +++ b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/alloy/analysis/discovering/InstanceTranslator4Discovering.java @@ -0,0 +1,269 @@ +package eu.modelwriter.configuration.alloy.analysis.discovering; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.eclipse.emf.common.util.EList; + +import eu.modelwriter.configuration.internal.AlloyUtilities; +import eu.modelwriter.traceability.core.persistence.AlloyType; +import eu.modelwriter.traceability.core.persistence.AtomType; +import eu.modelwriter.traceability.core.persistence.DocumentRoot; +import eu.modelwriter.traceability.core.persistence.FieldType; +import eu.modelwriter.traceability.core.persistence.SigType; +import eu.modelwriter.traceability.core.persistence.SourceType; +import eu.modelwriter.traceability.core.persistence.TupleType; +import eu.modelwriter.traceability.core.persistence.TypeType; +import eu.modelwriter.traceability.core.persistence.TypesType; + +public class InstanceTranslator4Discovering { + private final Map sig2oldValue = new HashMap<>(); + private final Map discoverSig2ExpectValue = new HashMap<>(); + private final Map sig2OldDiscoveredValue = new HashMap<>(); + + private final StringBuilder builder; + private final String baseFileDirectory; + private final String alsPath; + + public InstanceTranslator4Discovering(final String baseFileDirectory, final String alsPath) { + this.baseFileDirectory = baseFileDirectory; + this.alsPath = alsPath; + builder = new StringBuilder(); + } + + private void createFactPart(final DocumentRoot documentRoot, final List fields) { + builder.append("fact {\n"); + + final Map> discoverFields = new HashMap<>(); + final Map> allParents = new HashMap<>(); + + final Iterator iterator = discoverSig2ExpectValue.keySet().iterator(); + while (iterator.hasNext()) { + final String sigName = iterator.next(); + allParents.put(sigName, new ArrayList<>()); + final int id = AlloyUtilities.getSigTypeIdByName(sigName); + final ArrayList allParentIds = AlloyUtilities.getAllParentIds(id); + for (final Integer parentId : allParentIds) { + final String parentName = AlloyUtilities.getSigNameById(parentId); + allParents.get(sigName).add(parentName); + } + + for (final FieldType fieldType : fields) { + for (final TypesType typesType : fieldType.getTypes()) { + for (final TypeType typeType : typesType.getType()) { + String label = AlloyUtilities.getSigTypeById(typeType.getID()).getLabel(); + label = label.substring(label.lastIndexOf("/") + 1); + if (label.equals(sigName) || allParents.get(sigName).contains(label)) { + if (discoverFields.containsKey(sigName)) { + discoverFields.get(sigName).add(fieldType.getLabel()); + } else { + discoverFields.put(sigName, new ArrayList<>(Arrays.asList(fieldType.getLabel()))); + } + break; + } + } + } + } + } + + for (final FieldType fieldType : fields) { + final String fieldName = fieldType.getLabel(); + + final List allSources = new ArrayList<>(); + final List allTargets = new ArrayList<>(); + for (final TypesType typesType : fieldType.getTypes()) { + final EList typeType = typesType.getType(); + allSources.addAll(AlloyUtilities.getAllChildNames(typeType.get(0).getID())); + allTargets.addAll(AlloyUtilities.getAllChildNames(typeType.get(1).getID())); + } + + boolean accept = false; + for (final Entry> entry : discoverFields.entrySet()) { + final String discoverSig = entry.getKey(); + final List discoverFieldsOfSig = entry.getValue(); + if (discoverFieldsOfSig.contains(fieldName) + && (allSources.contains(discoverSig) || allTargets.contains(discoverSig))) { + accept = true; + break; + } + } + + String parentSigName = AlloyUtilities.getSigTypeById(fieldType.getParentID()).getLabel(); + parentSigName = parentSigName.substring(parentSigName.lastIndexOf("/") + 1); + + final EList tuples = fieldType.getTuple(); + for (int i = 0; i < tuples.size(); i++) { + + final String sigName1 = AlloyUtilities + .getAtomNameById(tuples.get(i).getAtom().get(0).getLabel()).replace("$", "_"); + final String sigName2 = AlloyUtilities + .getAtomNameById(tuples.get(i).getAtom().get(1).getLabel()).replace("$", "_"); + + builder.append(sigName1 + "->" + sigName2); + + if (i + 1 != tuples.size()) { + builder.append(" +\n"); + } else if (accept) { + builder.append(" in " + parentSigName + "<:" + fieldName + "\n"); + } else { + builder.append(" = " + parentSigName + "<:" + fieldName + "\n"); + } + } + + final List allRelations = new ArrayList<>(); + for (final List value : discoverFields.values()) { + allRelations.addAll(value); + } + + if (fieldType.getTuple().size() == 0 && !accept) { + builder.append(parentSigName + "." + fieldName + " = none\n"); + } + } + + builder.append("}\n"); + } + + private File createFile(final String filePath) { + File file = null; + try { + file = new File(filePath); + + if (!file.getParentFile().exists()) { + file.getParentFile().mkdirs(); + } + // if file doesn't exists, then create it + if (!file.exists()) { + file.createNewFile(); + } + } catch (final IOException e) { + e.printStackTrace(); + } + return file; + } + + private void createSigPart(final List sigs) { + for (final SigType sig : sigs) { + final String sigName = sig.getLabel().substring(sig.getLabel().lastIndexOf("/") + 1); + for (int i = 0; i < sig.getAtom().size(); i++) { + builder.append("one sig " + sigName + "_" + i + " extends " + sigName + "{ } \n"); + } + } + } + + private void createSourceFiles(final EList sources) { + boolean isFirst = false; + for (final SourceType source : sources) { + final String sourceFilePath = source.getFilename(); + final String fileName = sourceFilePath.substring( + sourceFilePath.lastIndexOf(System.getProperty("file.separator")) + 1, + sourceFilePath.lastIndexOf(".")); + if (!isFirst) { + builder.append("open " + fileName + "\n"); + isFirst = true; + } + final String newFilePath = baseFileDirectory + fileName + ".als"; + + final String content = removeDiscoveringParts(source.getContent()); + writeContentToFile(newFilePath, content); + } + } + + public Map getDiscoverSig2ExpectValue() { + return discoverSig2ExpectValue; + } + + private String removeDiscoveringParts(final String content) { + final List lines = Arrays.asList(content.split("\n")); + + final Pattern p = + Pattern.compile("(-)(-)(\\s*)(Discover|discover)(@)((?:[a-z0-9_]+))(\\s*)(\\d+)(\\s*)", + Pattern.CASE_INSENSITIVE | Pattern.DOTALL); + + for (final String line : lines) { + final Matcher matcher = p.matcher(line); + + if (!matcher.find()) { + continue; + } else { + final String discoveredSig = matcher.group(6); // it gets ((?:[a-z]+)) group + final int value = Integer.valueOf(matcher.group(8));// it gets (\\d+) group + + discoverSig2ExpectValue.put(discoveredSig, value); + } + } + + return content; + } + + public void translate() { + final DocumentRoot documentRoot = AlloyUtilities.getDocumentRoot(); + final AlloyType alloy = documentRoot.getAlloy(); + + calcOldSigValues(alloy.getInstance().getSig()); + createSourceFiles(alloy.getSource()); + createSigPart(alloy.getInstance().getSig()); + createFactPart(documentRoot, alloy.getInstance().getField()); + createRunPart(); + + writeContentToFile(alsPath, builder.toString()); + } + + private void createRunPart() { + builder.append("pred show{}\n"); + builder.append("run show for"); + + for (final Entry oldEntry : sig2oldValue.entrySet()) { + int oldValue = oldEntry.getValue(); + final String sigName = oldEntry.getKey(); + + if (discoverSig2ExpectValue.containsKey(sigName)) { + final int expectValue = discoverSig2ExpectValue.get(oldEntry.getKey()); + final int oldDiscoveredValue = sig2OldDiscoveredValue.get(sigName); + if (expectValue >= oldDiscoveredValue) { + oldValue += expectValue - oldDiscoveredValue; + } + } + builder.append("\n" + "exactly " + oldValue + " " + oldEntry.getKey() + ","); + } + + builder.replace(0, builder.length(), builder.substring(0, builder.length() - 1)); // to delete + // last ',' + } + + private void calcOldSigValues(final EList sigTypes) { + for (final SigType sigType : sigTypes) { + final String sigName = sigType.getLabel().substring(sigType.getLabel().lastIndexOf("/") + 1); + if (sigType.getID() > 3) { + sig2oldValue.put(sigName, sigType.getAbstract() != null ? 0 : sigType.getAtom().size()); + int discoveredAtomCount = 0; + for (final AtomType atomType : sigType.getAtom()) { + if (atomType.isReasoned()) { + discoveredAtomCount++; + } + } + sig2OldDiscoveredValue.put(sigName, discoveredAtomCount); + } + } + } + + private void writeContentToFile(final String filePath, final String content) { + try { + final File file = createFile(filePath); + final FileOutputStream out = new FileOutputStream(file); + out.write(content.getBytes()); + out.close(); + } catch (final IOException e) { + e.printStackTrace(); + } + } +} diff --git a/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/alloy/analysis/evaluator/Evaluator.java b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/alloy/analysis/evaluator/Evaluator.java new file mode 100644 index 00000000..ac5f7206 --- /dev/null +++ b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/alloy/analysis/evaluator/Evaluator.java @@ -0,0 +1,201 @@ +package eu.modelwriter.configuration.alloy.analysis.evaluator; + +import java.io.File; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.prefs.Preferences; + +import edu.mit.csail.sdg.alloy4.A4Reporter; +import edu.mit.csail.sdg.alloy4.Computer; +import edu.mit.csail.sdg.alloy4.Err; +import edu.mit.csail.sdg.alloy4.ErrorFatal; +import edu.mit.csail.sdg.alloy4.ErrorType; +import edu.mit.csail.sdg.alloy4.Util; +import edu.mit.csail.sdg.alloy4.Util.BooleanPref; +import edu.mit.csail.sdg.alloy4.Version; +import edu.mit.csail.sdg.alloy4.XMLNode; +import edu.mit.csail.sdg.alloy4compiler.ast.Expr; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprVar; +import edu.mit.csail.sdg.alloy4compiler.ast.Module; +import edu.mit.csail.sdg.alloy4compiler.ast.Sig; +import edu.mit.csail.sdg.alloy4compiler.parser.CompUtil; +import edu.mit.csail.sdg.alloy4compiler.sim.SimInstance; +import edu.mit.csail.sdg.alloy4compiler.sim.SimTuple; +import edu.mit.csail.sdg.alloy4compiler.sim.SimTupleset; +import edu.mit.csail.sdg.alloy4compiler.translator.A4Solution; +import edu.mit.csail.sdg.alloy4compiler.translator.A4SolutionReader; +import edu.mit.csail.sdg.alloy4compiler.translator.A4Tuple; +import edu.mit.csail.sdg.alloy4compiler.translator.A4TupleSet; +import kodkod.engine.fol2sat.HigherOrderDeclException; + +public class Evaluator implements Computer { + private String filename = null; + /** True if Alloy Analyzer should enable the new Implicit This name resolution. */ + private static final BooleanPref ImplicitThis = new BooleanPref("ImplicitThis"); + + /** This enum defines the set of possible message verbosity levels. */ + private enum Verbosity { + /** Level 0. */ + DEFAULT("0", "low"), + /** Level 1. */ + VERBOSE("1", "medium"), + /** Level 2. */ + DEBUG("2", "high"), + /** Level 3. */ + FULLDEBUG("3", "debug only"); + /** Returns true if it is greater than or equal to "other". */ + public boolean geq(final Verbosity other) { + return ordinal() >= other.ordinal(); + } + + /** This is a unique String for this value; it should be kept consistent in future versions. */ + private final String id; + /** This is the label that the toString() method will return. */ + private final String label; + + /** Constructs a new Verbosity value with the given id and label. */ + private Verbosity(final String id, final String label) { + this.id = id; + this.label = label; + } + + /** + * Given an id, return the enum value corresponding to it (if there's no match, then return + * DEFAULT). + */ + private static Verbosity parse(final String id) { + for (final Verbosity vb : Verbosity.values()) { + if (vb.id.equals(id)) { + return vb; + } + } + return DEFAULT; + } + + /** Returns the human-readable label for this enum value. */ + @Override + public final String toString() { + return label; + } + + /** Saves this value into the Java preference object. */ + private void set() { + Preferences.userNodeForPackage(Util.class).put("Verbosity", id); + } + + /** + * Reads the current value of the Java preference object (if it's not set, then return DEFAULT). + */ + private static Verbosity get() { + return Verbosity.parse(Preferences.userNodeForPackage(Util.class).get("Verbosity", "")); + } + }; + + @Override + public final String compute(final Object input) throws Exception { + if (input instanceof File) { + filename = ((File) input).getAbsolutePath(); + return ""; + } + if (!(input instanceof String)) { + return ""; + } + final String str = (String) input; + if (str.trim().length() == 0) { + return ""; // Empty line + } + Module root = null; + A4Solution ans = null; + try { + final Map fc = new LinkedHashMap<>(); + final XMLNode x = new XMLNode(new File(filename)); + if (!x.is("alloy")) { + throw new Exception(); + } + String mainname = null; + for (final XMLNode sub : x) { + if (sub.is("instance")) { + mainname = sub.getAttribute("filename"); + break; + } + } + if (mainname == null) { + throw new Exception(); + } + for (final XMLNode sub : x) { + if (sub.is("source")) { + final String name = sub.getAttribute("filename"); + final String content = sub.getAttribute("content"); + fc.put(name, content); + } + } + root = CompUtil.parseEverything_fromFile(A4Reporter.NOP, fc, mainname, + Version.experimental && Evaluator.ImplicitThis.get() ? 2 : 1); + ans = A4SolutionReader.read(root.getAllReachableSigs(), x); + for (final ExprVar a : ans.getAllAtoms()) { + root.addGlobal(a.label, a); + } + for (final ExprVar a : ans.getAllSkolems()) { + root.addGlobal(a.label, a); + } + } catch (final Throwable ex) { + throw new ErrorFatal("Failed to read or parse the XML file."); + } + try { + final Expr e = CompUtil.parseOneExpression_fromString(root, str); + if ("yes".equals(System.getProperty("debug")) && Verbosity.get() == Verbosity.FULLDEBUG) { + final SimInstance simInst = Evaluator.convert(root, ans); + return simInst.visitThis(e).toString() + (simInst.wasOverflow() ? " (OF)" : ""); + } else { + return ans.eval(e).toString(); + } + } catch (final HigherOrderDeclException ex) { + throw new ErrorType("Higher-order quantification is not allowed in the evaluator."); + } + } + + /** Converts an A4Solution into a SimInstance object. */ + private static SimInstance convert(final Module root, final A4Solution ans) throws Err { + final SimInstance ct = new SimInstance(root, ans.getBitwidth(), ans.getMaxSeq()); + for (final Sig s : ans.getAllReachableSigs()) { + if (!s.builtin) { + ct.init(s, Evaluator.convert(ans.eval(s))); + } + for (final edu.mit.csail.sdg.alloy4compiler.ast.Sig.Field f : s.getFields()) { + if (!f.defined) { + ct.init(f, Evaluator.convert(ans.eval(f))); + } + } + } + for (final ExprVar a : ans.getAllAtoms()) { + ct.init(a, Evaluator.convert(ans.eval(a))); + } + for (final ExprVar a : ans.getAllSkolems()) { + ct.init(a, Evaluator.convert(ans.eval(a))); + } + return ct; + } + + /** Converts an A4TupleSet into a SimTupleset object. */ + private static SimTupleset convert(final Object object) throws Err { + if (!(object instanceof A4TupleSet)) { + throw new ErrorFatal("Unexpected type error: expecting an A4TupleSet."); + } + final A4TupleSet s = (A4TupleSet) object; + if (s.size() == 0) { + return SimTupleset.EMPTY; + } + final List list = new ArrayList<>(s.size()); + final int arity = s.arity(); + for (final A4Tuple t : s) { + final String[] array = new String[arity]; + for (int i = 0; i < t.arity(); i++) { + array[i] = t.atom(i); + } + list.add(SimTuple.make(array)); + } + return SimTupleset.make(list); + } +} diff --git a/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/alloy/analysis/provider/AnalysisSourceProvider.java b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/alloy/analysis/provider/AnalysisSourceProvider.java new file mode 100644 index 00000000..5ecf037f --- /dev/null +++ b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/alloy/analysis/provider/AnalysisSourceProvider.java @@ -0,0 +1,142 @@ +package eu.modelwriter.configuration.alloy.analysis.provider; + +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.ui.AbstractSourceProvider; +import org.eclipse.ui.ISources; + +public class AnalysisSourceProvider extends AbstractSourceProvider { + + public static enum AnalysisState { + ACTIVE, PASSIVE, PROCESSING + } + + public static enum AnalysisType { + REASON_RELATION, DISCOVER_ATOM, REASON_RELATION_FOR_ATOM + } + + public static enum EvaluationState { + OPEN, CLOSE + } + + public static enum AnalysisFilter { + OPEN, CLOSE + } + + public static final String ANALYSIS_STATE = + "eu.modelwriter.configuration.alloy.analysissourceprovider.analysisState"; + public static final String ANALYSIS_TYPE = + "eu.modelwriter.configuration.alloy.analysissourceprovider.analysisType"; + public static final String EVALUATION_STATE = + "eu.modelwriter.configuration.alloy.analysissourceprovider.evaluationState"; + public static final String ANALYSIS_FILTER = + "eu.modelwriter.configuration.alloy.analysissourceprovider.analysisFilter"; + + private AnalysisState currentState = AnalysisState.PASSIVE; + + private AnalysisType analysisType; + + private EvaluationState evaluationState = EvaluationState.CLOSE; + + private AnalysisFilter analysisFilter = AnalysisFilter.OPEN; + + @Override + public void dispose() {} + + @SuppressWarnings("rawtypes") + @Override + public Map getCurrentState() { + final Map map = new HashMap<>(1); + + if (currentState == AnalysisState.ACTIVE) { + map.put(AnalysisSourceProvider.ANALYSIS_STATE, AnalysisState.ACTIVE.toString()); + if (analysisType == AnalysisType.DISCOVER_ATOM) { + map.put(AnalysisSourceProvider.ANALYSIS_TYPE, AnalysisType.DISCOVER_ATOM.toString()); + } else if (analysisType == AnalysisType.REASON_RELATION) { + map.put(AnalysisSourceProvider.ANALYSIS_TYPE, AnalysisType.REASON_RELATION.toString()); + } else if (analysisType == AnalysisType.REASON_RELATION_FOR_ATOM) { + map.put(AnalysisSourceProvider.ANALYSIS_TYPE, + AnalysisType.REASON_RELATION_FOR_ATOM.toString()); + } + } else if (currentState == AnalysisState.PASSIVE) { + map.put(AnalysisSourceProvider.ANALYSIS_STATE, AnalysisState.PASSIVE.toString()); + } else if (currentState == AnalysisState.PROCESSING) { + map.put(AnalysisSourceProvider.ANALYSIS_STATE, AnalysisState.PROCESSING.toString()); + } + + if (evaluationState == EvaluationState.OPEN) { + map.put(AnalysisSourceProvider.EVALUATION_STATE, EvaluationState.OPEN.toString()); + } else if (evaluationState == EvaluationState.CLOSE) { + map.put(AnalysisSourceProvider.EVALUATION_STATE, EvaluationState.CLOSE.toString()); + } + + if (analysisFilter == AnalysisFilter.OPEN) { + map.put(AnalysisSourceProvider.ANALYSIS_FILTER, AnalysisFilter.OPEN.toString()); + } else if (analysisFilter == AnalysisFilter.CLOSE) { + map.put(AnalysisSourceProvider.ANALYSIS_FILTER, AnalysisFilter.CLOSE.toString()); + } + + return map; + } + + @Override + public String[] getProvidedSourceNames() { + return new String[] {AnalysisSourceProvider.ANALYSIS_STATE, + AnalysisSourceProvider.ANALYSIS_TYPE, AnalysisSourceProvider.EVALUATION_STATE, + AnalysisSourceProvider.ANALYSIS_FILTER}; + } + + public void setActive(final AnalysisType TYPE) { + this.fireSourceChanged(ISources.WORKBENCH, AnalysisSourceProvider.ANALYSIS_STATE, + AnalysisState.ACTIVE.toString()); + currentState = AnalysisState.ACTIVE; + setAnalysisType(TYPE); + } + + public void setPassive() { + this.fireSourceChanged(ISources.WORKBENCH, AnalysisSourceProvider.ANALYSIS_STATE, + AnalysisState.PASSIVE.toString()); + currentState = AnalysisState.PASSIVE; + } + + public void setProcessing() { + this.fireSourceChanged(ISources.WORKBENCH, AnalysisSourceProvider.ANALYSIS_STATE, + AnalysisState.PROCESSING.toString()); + currentState = AnalysisState.PROCESSING; + } + + public AnalysisType getAnalysisType() { + return analysisType; + } + + public AnalysisState getAnalysisState() { + return currentState; + } + + public void setAnalysisType(final AnalysisType analysisType) { + this.fireSourceChanged(ISources.WORKBENCH, AnalysisSourceProvider.ANALYSIS_TYPE, + analysisType.toString()); + this.analysisType = analysisType; + } + + public EvaluationState getEvaluationState() { + return evaluationState; + } + + public void setEvaluationState(final EvaluationState evaluationState) { + this.fireSourceChanged(ISources.WORKBENCH, AnalysisSourceProvider.EVALUATION_STATE, + evaluationState.toString()); + this.evaluationState = evaluationState; + } + + public AnalysisFilter getAnalysisFilter() { + return analysisFilter; + } + + public void setAnalysisFilter(final AnalysisFilter analysisFilter) { + this.fireSourceChanged(ISources.WORKBENCH, AnalysisSourceProvider.ANALYSIS_FILTER, + analysisFilter.toString()); + this.analysisFilter = analysisFilter; + } +} diff --git a/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/alloy/analysis/reasoning/InstanceTranslator4Reasoning.java b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/alloy/analysis/reasoning/InstanceTranslator4Reasoning.java new file mode 100644 index 00000000..058c3302 --- /dev/null +++ b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/alloy/analysis/reasoning/InstanceTranslator4Reasoning.java @@ -0,0 +1,224 @@ +package eu.modelwriter.configuration.alloy.analysis.reasoning; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.eclipse.emf.common.util.EList; + +import eu.modelwriter.configuration.internal.AlloyUtilities; +import eu.modelwriter.traceability.core.persistence.AlloyType; +import eu.modelwriter.traceability.core.persistence.DocumentRoot; +import eu.modelwriter.traceability.core.persistence.FieldType; +import eu.modelwriter.traceability.core.persistence.SigType; +import eu.modelwriter.traceability.core.persistence.SourceType; +import eu.modelwriter.traceability.core.persistence.TupleType; +import eu.modelwriter.traceability.core.persistence.TypeType; +import eu.modelwriter.traceability.core.persistence.TypesType; + +public class InstanceTranslator4Reasoning { + private final Map sig2oldValue = new HashMap<>(); + private final Map> reasonRelations = new HashMap<>(); + + private final StringBuilder builder; + private final String baseFileDirectory; + private final String alsPath; + + public InstanceTranslator4Reasoning(final String baseFileDirectory, final String alsPath) { + this.baseFileDirectory = baseFileDirectory; + this.alsPath = alsPath; + builder = new StringBuilder(); + } + + private void createFactPart(final DocumentRoot documentRoot, final List fields) { + builder.append("fact {\n"); + + for (final FieldType field : fields) { + final String fieldName = field.getLabel(); + + final List allSources = new ArrayList<>(); + for (final TypesType typesType : field.getTypes()) { + final EList typeType = typesType.getType(); + allSources.addAll(AlloyUtilities.getAllChildNames(typeType.get(0).getID())); + } + + boolean accept = false; + for (final Entry> entry : reasonRelations.entrySet()) { + final String discoverSig = entry.getKey(); + final List discoverFieldsOfSig = entry.getValue(); + if (discoverFieldsOfSig.contains(fieldName) && allSources.contains(discoverSig)) { + accept = true; + break; + } + } + + int tupleCount = 0; + + String parentSigName = AlloyUtilities.getSigTypeById(field.getParentID()).getLabel(); + parentSigName = parentSigName.substring(parentSigName.indexOf("/") + 1); + + for (final TupleType tuple : field.getTuple()) { + tupleCount++; + + final String sigName1 = + AlloyUtilities.getAtomNameById(tuple.getAtom().get(0).getLabel()).replace("$", "_"); + final String sigName2 = + AlloyUtilities.getAtomNameById(tuple.getAtom().get(1).getLabel()).replace("$", "_"); + + builder.append(sigName1 + "->" + sigName2); + + if (tupleCount != field.getTuple().size()) { + builder.append(" +\n"); + } else if (accept) { + builder.append(" in " + parentSigName + "<:" + fieldName + "\n"); + } else { + builder.append(" = " + parentSigName + "<:" + fieldName + "\n"); + } + + } + + final List allRelations = new ArrayList<>(); + for (final List value : reasonRelations.values()) { + allRelations.addAll(value); + } + + if (field.getTuple().size() == 0 && !accept) { + builder.append(parentSigName + "." + fieldName + " = none\n"); + } + } + + builder.append("}\n"); + } + + private File createFile(final String filePath) { + File file = null; + try { + file = new File(filePath); + + if (!file.getParentFile().exists()) { + file.getParentFile().mkdirs(); + } + // if file doesn't exists, then create it + if (!file.exists()) { + file.createNewFile(); + } + } catch (final IOException e) { + e.printStackTrace(); + } + return file; + } + + private void createSigPart(final List sigs) { + for (final SigType sig : sigs) { + final String sigName = sig.getLabel().substring(sig.getLabel().lastIndexOf("/") + 1); + for (int i = 0; i < sig.getAtom().size(); i++) { + builder.append("one sig " + sigName + "_" + i + " extends " + sigName + "{ } \n"); + } + } + } + + private void createSourceFiles(final EList sources) { + boolean isFirst = false; + for (final SourceType source : sources) { + final String sourceFilePath = source.getFilename(); + final String fileName = sourceFilePath.substring( + sourceFilePath.lastIndexOf(System.getProperty("file.separator")) + 1, + sourceFilePath.lastIndexOf(".")); + if (!isFirst) { + builder.append("open " + fileName + "\n"); + isFirst = true; + } + final String newFilePath = baseFileDirectory + fileName + ".als"; + + final String content = removeReasoningParts(source.getContent()); + writeContentToFile(newFilePath, content); + } + } + + public Map> getReasonRelations() { + return reasonRelations; + } + + private String removeReasoningParts(final String content) { + final List lines = Arrays.asList(content.split("\n")); + + final Pattern p = + Pattern.compile("(-)(-)(\\s*)(Reason|reason)(@)((?:[a-z0-9_]+))(\\.)((?:[a-z0-9_]+))(\\s*)", + Pattern.CASE_INSENSITIVE | Pattern.DOTALL); + + for (final String line : lines) { + final Matcher matcher = p.matcher(line); + + if (!matcher.find()) { + continue; + } else { + final String sig = matcher.group(6); // it gets ((?:[a-z]+)) + // group + final String relation = matcher.group(8); + if (reasonRelations.containsKey(sig)) { + if (!reasonRelations.get(sig).contains(relation)) { + reasonRelations.get(sig).add(relation); + } + } else { + reasonRelations.put(sig, new ArrayList<>(Arrays.asList(relation))); + } + } + } + + return content; + } + + public void translate() { + final DocumentRoot documentRoot = AlloyUtilities.getDocumentRoot(); + final AlloyType alloy = documentRoot.getAlloy(); + + calcOldSigValues(alloy.getInstance().getSig()); + createSourceFiles(alloy.getSource()); + createSigPart(alloy.getInstance().getSig()); + createFactPart(documentRoot, alloy.getInstance().getField()); + createRunPart(); + + writeContentToFile(alsPath, builder.toString()); + } + + private void createRunPart() { + builder.append("pred show{}\n"); + builder.append("run show for"); + + for (final Entry oldEntry : sig2oldValue.entrySet()) { + final int value = oldEntry.getValue(); + builder.append("\nexactly " + value + " " + oldEntry.getKey() + ","); + } + + builder.replace(0, builder.length(), builder.substring(0, builder.length() - 1)); // to delete + // last ',' + } + + private void writeContentToFile(final String filePath, final String content) { + try { + final File file = createFile(filePath); + final FileOutputStream out = new FileOutputStream(file); + out.write(content.getBytes()); + out.close(); + } catch (final IOException e) { + e.printStackTrace(); + } + } + + private void calcOldSigValues(final EList sigTypes) { + for (final SigType sigType : sigTypes) { + final String sigName = sigType.getLabel().substring(sigType.getLabel().lastIndexOf("/") + 1); + if (sigType.getID() > 3) { + sig2oldValue.put(sigName, sigType.getAbstract() != null ? 0 : sigType.getAtom().size()); + } + } + } +} diff --git a/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/alloy/analysis/reasoning/Reasoning.java b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/alloy/analysis/reasoning/Reasoning.java new file mode 100644 index 00000000..fe2b8faf --- /dev/null +++ b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/alloy/analysis/reasoning/Reasoning.java @@ -0,0 +1,573 @@ +package eu.modelwriter.configuration.alloy.analysis.reasoning; + +import java.io.File; +import java.io.IOException; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import javax.swing.JOptionPane; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; + +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.emf.common.util.EList; +import org.eclipse.emf.common.util.URI; +import org.w3c.dom.Document; +import org.w3c.dom.Node; +import org.xml.sax.SAXException; + +import edu.mit.csail.sdg.alloy4.Err; +import edu.mit.csail.sdg.alloy4compiler.translator.A4Solution; +import eu.modelwriter.configuration.alloy.analysis.AlloySolutionFinder; +import eu.modelwriter.configuration.alloy.analysis.IAlloyAnalyzer; +import eu.modelwriter.configuration.alloy.analysis.RUN_TYPE; +import eu.modelwriter.configuration.internal.AlloyUtilities; +import eu.modelwriter.traceability.core.persistence.AtomType; +import eu.modelwriter.traceability.core.persistence.DocumentRoot; +import eu.modelwriter.traceability.core.persistence.FieldType; +import eu.modelwriter.traceability.core.persistence.TupleType; +import eu.modelwriter.traceability.core.persistence.persistenceFactory; +import eu.modelwriter.traceability.core.persistence.internal.ModelIO; + +public class Reasoning implements IAlloyAnalyzer { + + private static Reasoning instance; + private static String baseFileDirectory = ResourcesPlugin.getWorkspace().getRoot().getLocation() + + " .modelwriter reasoning ".replace(" ", System.getProperty("file.separator")); + private static final String alsPath = Reasoning.baseFileDirectory + "reasoning.als"; + private static final String xmlPath = Reasoning.baseFileDirectory + "reasoning.xml"; + private static Map> reasonRelations; + private static List solutions; + private static List>> reasonedTuples; + private static int currentSolutionIndex; + private static int MAX_NEXT_SOLUTION_ATTEMPT_TIME = 10; // 10 sec + private static long NEXT_SOLUTION_ATTEMPT_START_TIME; + private static int NEXT_SOLUTION_ATTEMPT_COUNT; + private static boolean filterOpen = true; + + private Reasoning() {} + + public static Reasoning getInstance() { + if (Reasoning.instance == null) { + Reasoning.instance = new Reasoning(); + Reasoning.reasonRelations = new HashMap<>(); + Reasoning.solutions = new ArrayList<>(); + Reasoning.reasonedTuples = new ArrayList<>(); + } + + return Reasoning.instance; + } + + @Override + public boolean start() throws Err { + final File reasoningXml = new File(Reasoning.xmlPath); + if (reasoningXml.exists()) { + reasoningXml.delete(); + } + final File reasoningAls = new File(Reasoning.alsPath); + if (reasoningAls.exists()) { + reasoningAls.delete(); + } + + final InstanceTranslator4Reasoning instanceTranslator = + new InstanceTranslator4Reasoning(Reasoning.baseFileDirectory, Reasoning.alsPath); + instanceTranslator.translate(); + Reasoning.reasonRelations = instanceTranslator.getReasonRelations(); + + final AlloySolutionFinder parser = new AlloySolutionFinder(Reasoning.alsPath); + final A4Solution solution = parser.find(); + + if (solution == null || !solution.satisfiable()) { + return false; + } + + solution.writeXML(Reasoning.xmlPath); + parse(Reasoning.xmlPath); + Reasoning.solutions.add(solution); + Reasoning.currentSolutionIndex = 0; + Reasoning.NEXT_SOLUTION_ATTEMPT_COUNT = 0; + + return reasoning(RUN_TYPE.START); + } + + @Override + public boolean next() throws Err { + A4Solution solution; + if (Reasoning.solutions.size() == Reasoning.currentSolutionIndex + 1) { + solution = Reasoning.solutions.get(Reasoning.currentSolutionIndex).next(); + if (solution.equals(Reasoning.solutions.get(Reasoning.currentSolutionIndex))) { + return false; + } else if (solution.satisfiable()) { + Reasoning.solutions.add(solution); + Reasoning.currentSolutionIndex++; + } else { + return false; + } + } else { + solution = Reasoning.solutions.get(Reasoning.currentSolutionIndex + 1); + Reasoning.currentSolutionIndex++; + } + + solution.writeXML(Reasoning.xmlPath); + parse(Reasoning.xmlPath); + + return reasoning(RUN_TYPE.NEXT); + } + + @Override + public boolean previous() throws Err { + A4Solution solution; + if (Reasoning.currentSolutionIndex == 0) { + return false; + } else { + solution = Reasoning.solutions.get(Reasoning.currentSolutionIndex - 1); + Reasoning.currentSolutionIndex--; + } + + solution.writeXML(Reasoning.xmlPath); + parse(Reasoning.xmlPath); + + return reasoning(RUN_TYPE.PREVIOUS); + } + + private void parse(final String xmlPath) { + final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + DocumentBuilder builder; + try { + builder = factory.newDocumentBuilder(); + final File file = new File(xmlPath); + final Document document = builder.parse(file); + final Node instance = document.getElementsByTagName("instance").item(0); + instance.getAttributes().removeNamedItem("command"); + + Transformer transformer; + try { + transformer = TransformerFactory.newInstance().newTransformer(); + final DOMSource source = new DOMSource(document); + final StreamResult result = new StreamResult(file); + transformer.transform(source, result); + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2"); + } catch (final Exception e) { + } + } catch (ParserConfigurationException | SAXException | IOException e) { + e.printStackTrace(); + } + } + + private void deleteOldReasoning(final RUN_TYPE runType) { + final DocumentRoot documentRoot = AlloyUtilities.getDocumentRoot(); + int solutionNumber = runType == RUN_TYPE.NEXT ? Reasoning.currentSolutionIndex - 1 + : runType == RUN_TYPE.PREVIOUS ? Reasoning.currentSolutionIndex + 1 : -1; + solutionNumber = solutionNumber >= Reasoning.reasonedTuples.size() ? -1 : solutionNumber; + if (solutionNumber == -1) { + return; + } + + final Iterator>> oldReasonedTuplesIterator = + Reasoning.reasonedTuples.get(solutionNumber).entrySet().iterator(); + final EList fieldTypes = documentRoot.getAlloy().getInstance().getField(); + while (oldReasonedTuplesIterator.hasNext()) { + final Entry> entry = oldReasonedTuplesIterator.next(); + for (final FieldType fieldType : fieldTypes) { + if (fieldType.getID() == entry.getKey()) { + for (final TupleType oldTupleType : entry.getValue()) { + final Iterator tupleIter = fieldType.getTuple().iterator(); + final AtomType oldAtomType0 = oldTupleType.getAtom().get(0); + final AtomType oldAtomType1 = oldTupleType.getAtom().get(1); + while (tupleIter.hasNext()) { + final TupleType tupleType = tupleIter.next(); + if (tupleType.isReasoned()) { + final AtomType atomType0 = tupleType.getAtom().get(0); + final AtomType atomType1 = tupleType.getAtom().get(1); + if (oldAtomType0.getLabel().equals(atomType0.getLabel()) + && oldAtomType1.getLabel().equals(atomType1.getLabel())) { + tupleIter.remove(); + } + } + } + } + } + } + } + + AlloyUtilities.writeDocumentRoot(documentRoot); + } + + @Override + public void finish() { + Reasoning.solutions.clear(); + Reasoning.currentSolutionIndex = 0; + Reasoning.reasonRelations.clear(); + Reasoning.reasonedTuples.clear(); + } + + public DocumentRoot getDocumentRoot() { + @SuppressWarnings("rawtypes") + final ModelIO modelIO = new ModelIO<>(); + @SuppressWarnings("rawtypes") + List list = null; + try { + list = modelIO.read(URI.createFileURI(Reasoning.xmlPath)); + } catch (final IOException e) { + return null; + } + if (list == null || list.isEmpty()) { + return null; + } + final DocumentRoot documentRoot = (DocumentRoot) list.get(0); + return documentRoot; + } + + private boolean reasoning(final RUN_TYPE runType) throws Err { + deleteOldReasoning(runType); + + int unacceptedReasonedTupleCount = 0, acceptedTupleCount = 0; + if (Reasoning.solutions.size() > Reasoning.reasonedTuples.size()) { + + final Map> newReasonedTuples = findReasonedTuples(); + Reasoning.reasonedTuples.add(newReasonedTuples); + + if (Reasoning.filterOpen) { + if (!isValidAndUniqueReason(newReasonedTuples)) { + final long nextSolutionProcessTime = + Instant.now().getEpochSecond() - Reasoning.NEXT_SOLUTION_ATTEMPT_START_TIME; + if (Reasoning.NEXT_SOLUTION_ATTEMPT_COUNT != 0 + && nextSolutionProcessTime > Reasoning.MAX_NEXT_SOLUTION_ATTEMPT_TIME) { + final boolean rollBackSucceed = rollBackNextAttemption(false); + if (rollBackSucceed) { + final Map> unacceptedTupleTypes = filterAcceptedTupleTypes( + Reasoning.reasonedTuples.get(Reasoning.currentSolutionIndex)); + writeReasonedTupleTypes(unacceptedTupleTypes); + unacceptedReasonedTupleCount = calcReasonedTupleCount(unacceptedTupleTypes); + } + return false; + } + if (Reasoning.NEXT_SOLUTION_ATTEMPT_COUNT == 0) { + Reasoning.NEXT_SOLUTION_ATTEMPT_START_TIME = Instant.now().getEpochSecond(); + } + Reasoning.NEXT_SOLUTION_ATTEMPT_COUNT++; + return next(); + } else { + if (Reasoning.NEXT_SOLUTION_ATTEMPT_COUNT > 0) { + rollBackNextAttemption(true); + } + writeReasonedTupleTypes(newReasonedTuples); + unacceptedReasonedTupleCount = calcReasonedTupleCount(newReasonedTuples); + } + } else { + final int reasonedTupleCount = calcReasonedTupleCount(newReasonedTuples); + final Map> unacceptedTupleTypes = + filterAcceptedTupleTypes(newReasonedTuples); + writeReasonedTupleTypes(unacceptedTupleTypes); + unacceptedReasonedTupleCount = calcReasonedTupleCount(unacceptedTupleTypes); + acceptedTupleCount = reasonedTupleCount - unacceptedReasonedTupleCount; + } + } else { + final Map> existingReasonedTuples = + Reasoning.reasonedTuples.get(Reasoning.currentSolutionIndex); + if (Reasoning.filterOpen) { + if (!isValidAndUniqueReason(existingReasonedTuples)) { + if (runType.equals(RUN_TYPE.NEXT)) { + return next(); + } else if (runType.equals(RUN_TYPE.PREVIOUS)) { + return previous(); + } + } + writeReasonedTupleTypes(existingReasonedTuples); + unacceptedReasonedTupleCount = calcReasonedTupleCount(existingReasonedTuples); + } else { + final int reasonedTupleCount = calcReasonedTupleCount(existingReasonedTuples); + final Map> unacceptedTupleTypes = + filterAcceptedTupleTypes(existingReasonedTuples); + writeReasonedTupleTypes(unacceptedTupleTypes); + unacceptedReasonedTupleCount = calcReasonedTupleCount(unacceptedTupleTypes); + acceptedTupleCount = reasonedTupleCount - unacceptedReasonedTupleCount; + } + } + + final String reasonedTupleCountMessage = unacceptedReasonedTupleCount + (acceptedTupleCount > 0 + ? "\nYou accepted " + acceptedTupleCount + " reasoned relation before." : ""); + + JOptionPane.showMessageDialog(null, + "Reasoning on relations successfully completed.\nReasoned relation count: " + + reasonedTupleCountMessage, + "Reasoning on Relations", JOptionPane.WARNING_MESSAGE); + return true; + } + + /** + * filters accepted tupleTypes and returns unaccepted ones. + * + * @param newReasonedTuples + * @return + */ + private Map> filterAcceptedTupleTypes( + final Map> newReasonedTuples) { + final DocumentRoot documentRoot = AlloyUtilities.getDocumentRoot(); + final Map> unacceptedTupleTypes = new HashMap<>(); + + final Iterator fieldIterator = + documentRoot.getAlloy().getInstance().getField().iterator(); + while (fieldIterator.hasNext()) { + final FieldType oldFieldType = fieldIterator.next(); + final int fieldId = oldFieldType.getID(); + final List newTupleTypes = newReasonedTuples.get(fieldId); + if (newTupleTypes != null) { + final List unacceptedTupleTypesList = new ArrayList<>(); + for (final TupleType newTupleType : newTupleTypes) { + if (!isTupleAccepted(fieldId, newTupleType)) { // IF REASONED TUPLE IS NOT ACCEPTED DURING + // ANALYSIS. + unacceptedTupleTypesList.add(newTupleType); + } + } + unacceptedTupleTypes.put(fieldId, unacceptedTupleTypesList); + } + } + + return unacceptedTupleTypes; + } + + /** + * checks if tupleType is accepted in persistence file. + * + * @param fieldId + * @param tupleType + * @return + */ + private boolean isTupleAccepted(final int fieldId, final TupleType tupleType) { + return AlloyUtilities.getDocumentRoot().getAlloy().getInstance().getField().stream() + .filter(f -> f.getID() == fieldId).findFirst().get().getTuple().stream() + .anyMatch(t -> (t.getAtom().get(0).getLabel().equals(tupleType.getAtom().get(0).getLabel()) + && t.getAtom().get(1).getLabel().equals(tupleType.getAtom().get(1).getLabel()))); + } + + private void writeReasonedTupleTypes(final Map> newReasonedTuples) { + final DocumentRoot documentRoot = AlloyUtilities.getDocumentRoot(); + + final Iterator fieldIterator = + documentRoot.getAlloy().getInstance().getField().iterator(); + while (fieldIterator.hasNext()) { + final FieldType oldFieldType = fieldIterator.next(); + final int fieldId = oldFieldType.getID(); + final List newTupleTypes = newReasonedTuples.get(fieldId); + if (newTupleTypes != null) { + oldFieldType.getTuple().addAll(newTupleTypes); + } + } + + AlloyUtilities.writeDocumentRoot(documentRoot); + } + + private boolean rollBackNextAttemption(final boolean lastSolutionIsValid) { + final int lastSolutionIndex = Reasoning.solutions.size() - 1; + int validSolutionIndex = lastSolutionIsValid ? lastSolutionIndex + : lastSolutionIndex - Reasoning.NEXT_SOLUTION_ATTEMPT_COUNT - 1; + Reasoning.currentSolutionIndex = + lastSolutionIsValid ? lastSolutionIndex - Reasoning.NEXT_SOLUTION_ATTEMPT_COUNT + : lastSolutionIndex - Reasoning.NEXT_SOLUTION_ATTEMPT_COUNT - 1; + Reasoning.NEXT_SOLUTION_ATTEMPT_COUNT = 0; + Reasoning.NEXT_SOLUTION_ATTEMPT_START_TIME = 0; + + validSolutionIndex = validSolutionIndex > 0 ? validSolutionIndex : 0; + Reasoning.currentSolutionIndex = + Reasoning.currentSolutionIndex > 0 ? Reasoning.currentSolutionIndex : 0; + + if (Reasoning.reasonedTuples.get(validSolutionIndex).size() == 0) { + finish(); + return false; + } + + Reasoning.solutions.set(Reasoning.currentSolutionIndex, + Reasoning.solutions.get(validSolutionIndex)); + Reasoning.reasonedTuples.set(Reasoning.currentSolutionIndex, + Reasoning.reasonedTuples.get(validSolutionIndex)); + + for (int i = lastSolutionIndex; i > Reasoning.currentSolutionIndex; i--) { + Reasoning.solutions.remove(i); + Reasoning.reasonedTuples.remove(i); + } + return true; + } + + private int calcReasonedTupleCount(final Map> newReasonedTuples) { + int reasonedTupleCount = 0; + for (final Entry> entry : newReasonedTuples.entrySet()) { + reasonedTupleCount += entry.getValue().size(); + } + return reasonedTupleCount; + } + + private Map> findReasonedTuples() throws Err { + final DocumentRoot documentRootReasoning = getDocumentRoot(); + final DocumentRoot documentRootOriginal = AlloyUtilities.getDocumentRoot(); + + final Map> newReasonedTuples = new HashMap<>(); + + for (final FieldType fieldType_R : documentRootReasoning.getAlloy().getInstance().getField()) { + for (final FieldType fieldType_O : documentRootOriginal.getAlloy().getInstance().getField()) { + if (!fieldType_R.getLabel().equals(fieldType_O.getLabel())) { + continue; + } + + final int sourceId_R = fieldType_R.getParentID(); + final String sourceSigName_R = + AlloyUtilities.getSigNameById(sourceId_R, documentRootReasoning); + if (!Reasoning.reasonRelations.containsKey(sourceSigName_R) + || !Reasoning.reasonRelations.get(sourceSigName_R).contains(fieldType_R.getLabel())) { + continue; + } + + final int sourceId_O = fieldType_O.getParentID(); + final String sourceSigName_O = + AlloyUtilities.getSigNameById(sourceId_O, documentRootOriginal); + if (!Reasoning.reasonRelations.containsKey(sourceSigName_O) + || !Reasoning.reasonRelations.get(sourceSigName_O).contains(fieldType_O.getLabel())) { + continue; + } + + if (!sourceSigName_O.equals(sourceSigName_R)) { + continue; + } + + if (fieldType_O.getTuple().size() == fieldType_R.getTuple().size()) { + continue; + } + + for (final TupleType tuple_R : fieldType_R.getTuple()) { + AtomType atomType0_R = getOriginalAtomType(tuple_R.getAtom().get(0).getLabel()); + final AtomType atomType1_R = getOriginalAtomType(tuple_R.getAtom().get(1).getLabel()); + + if (atomType0_R == null || atomType1_R == null) { + continue; + } + + boolean exists = false; + for (final TupleType tuple_O : fieldType_O.getTuple()) { + if (atomType0_R.getLabel().equals(tuple_O.getAtom().get(0).getLabel()) + && atomType1_R.getLabel().equals(tuple_O.getAtom().get(1).getLabel())) { + exists = true; + break; + } + } + + if (!exists || fieldType_O.getTuple().size() == 0) { + final TupleType tupleType_O = persistenceFactory.eINSTANCE.createTupleType(); + if (atomType0_R.equals(atomType1_R)) { + atomType0_R = AlloyUtilities.cloneAtomType(atomType0_R); + } + tupleType_O.getAtom().addAll(Arrays.asList(new AtomType[] {atomType0_R, atomType1_R})); + tupleType_O.setReasoned(true); + + if (newReasonedTuples.get(fieldType_O.getID()) == null) { + newReasonedTuples.put(fieldType_O.getID(), + new ArrayList<>(Arrays.asList(tupleType_O))); + } else { + newReasonedTuples.get(fieldType_O.getID()).add(tupleType_O); + } + } + } + } + } + return newReasonedTuples; + } + + private boolean isValidAndUniqueReason(final Map> reasonedTuples) { + final Map> unacceptedTupleTypes = + filterAcceptedTupleTypes(reasonedTuples); + final int unacceptedReasonedTupleCount = calcReasonedTupleCount(unacceptedTupleTypes); + + if (unacceptedReasonedTupleCount == 0) { // 1 + return false; + } + // dont swap 1 and 2 + if (Reasoning.solutions.size() == 1) { // 2 + return true; + } + + if (getUnmatchedExistingReasoningCount(unacceptedTupleTypes) == Reasoning.reasonedTuples.size() + - 1) { + return true; + } + return false; + } + + private int getUnmatchedExistingReasoningCount( + final Map> reasonedTuples) { + int unMatchedExistingReasoningCount = 0; + for (int solutionNumber = 0; solutionNumber < Reasoning.reasonedTuples + .size(); solutionNumber++) { + if (solutionNumber == Reasoning.currentSolutionIndex) { + continue; + } + final Map> existingReasonedTuples = + Reasoning.reasonedTuples.get(solutionNumber); + for (final Entry> newReasonsedTuplesEntry : reasonedTuples + .entrySet()) { + final Integer fieldId = newReasonsedTuplesEntry.getKey(); + final List tupleList_New = newReasonsedTuplesEntry.getValue(); + if (existingReasonedTuples.containsKey(fieldId)) { + final List tupleList_Old = existingReasonedTuples.get(fieldId); + if (tupleList_New.size() != tupleList_Old.size()) { + unMatchedExistingReasoningCount++; + break; + } + int matchedTupleCount = 0; + for (final TupleType tupleType_New : tupleList_New) { + final String atom0Label_New = tupleType_New.getAtom().get(0).getLabel(); + final String atom1Label_New = tupleType_New.getAtom().get(1).getLabel(); + + final boolean anyMatchOldTuple = tupleList_Old.stream().anyMatch( + tupleType_Old -> tupleType_Old.getAtom().get(0).getLabel().equals(atom0Label_New) + && tupleType_Old.getAtom().get(1).getLabel().equals(atom1Label_New)); + + if (anyMatchOldTuple) { + matchedTupleCount++; + } + } + if (matchedTupleCount != tupleList_New.size()) { + unMatchedExistingReasoningCount++; + break; + } + } else { + unMatchedExistingReasoningCount++; + break; + } + } + } + return unMatchedExistingReasoningCount; + } + + private AtomType getOriginalAtomType(final String name_R) { + if (name_R.contains("/")) { + return null; + } + final String name = name_R.substring(0, name_R.lastIndexOf("_")); + final int id = + Integer.parseInt(name_R.substring(name_R.lastIndexOf("_") + 1, name_R.lastIndexOf("$"))); + + return AlloyUtilities.getSigTypeById(AlloyUtilities.getSigTypeIdByName(name)).getAtom().get(id); + } + + @Override + public void setFilterState(final boolean isOpen) { + Reasoning.filterOpen = isOpen; + } + + @Override + public void setNextSolMaxTime(final int maxTime) { + Reasoning.MAX_NEXT_SOLUTION_ATTEMPT_TIME = maxTime; + } +} diff --git a/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/alloy/analysis/reasoningforatom/InstanceTranslator4ReasoningForAtom.java b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/alloy/analysis/reasoningforatom/InstanceTranslator4ReasoningForAtom.java new file mode 100644 index 00000000..53d777ed --- /dev/null +++ b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/alloy/analysis/reasoningforatom/InstanceTranslator4ReasoningForAtom.java @@ -0,0 +1,245 @@ +package eu.modelwriter.configuration.alloy.analysis.reasoningforatom; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import org.eclipse.emf.common.util.EList; + +import eu.modelwriter.configuration.internal.AlloyUtilities; +import eu.modelwriter.traceability.core.persistence.AlloyType; +import eu.modelwriter.traceability.core.persistence.DocumentRoot; +import eu.modelwriter.traceability.core.persistence.FieldType; +import eu.modelwriter.traceability.core.persistence.SigType; +import eu.modelwriter.traceability.core.persistence.SourceType; +import eu.modelwriter.traceability.core.persistence.TupleType; +import eu.modelwriter.traceability.core.persistence.TypeType; +import eu.modelwriter.traceability.core.persistence.TypesType; + +public class InstanceTranslator4ReasoningForAtom { + private final Map sig2oldValue = new HashMap<>(); + final Map> reasonRelations = new HashMap<>(); + + private final StringBuilder builder; + private final String baseFileDirectory; + private final String alsPath; + private final String atomType; + + public InstanceTranslator4ReasoningForAtom(final String baseFileDirectory, final String alsPath, + final String atomType) { + this.baseFileDirectory = baseFileDirectory; + this.alsPath = alsPath; + this.atomType = atomType; + builder = new StringBuilder(); + } + + private void createFactPart(final DocumentRoot documentRoot, final List fields) { + builder.append("fact {\n"); + + final Map> discoverFields = new HashMap<>(); + final Map> allParents = new HashMap<>(); + + final String sigName = atomType; + allParents.put(sigName, new ArrayList<>()); + final int id = AlloyUtilities.getSigTypeIdByName(sigName); + final ArrayList allParentIds = AlloyUtilities.getAllParentIds(id); + for (final Integer parentId : allParentIds) { + final String parentName = AlloyUtilities.getSigNameById(parentId); + allParents.get(sigName).add(parentName); + } + + for (final FieldType fieldType : fields) { + for (final TypesType typesType : fieldType.getTypes()) { + for (final TypeType typeType : typesType.getType()) { + String label = AlloyUtilities.getSigTypeById(typeType.getID()).getLabel(); + label = label.substring(label.lastIndexOf("/") + 1); + if (label.equals(sigName) || allParents.get(sigName).contains(label)) { + if (discoverFields.containsKey(sigName)) { + discoverFields.get(sigName).add(fieldType.getLabel()); + } else { + discoverFields.put(sigName, new ArrayList<>(Arrays.asList(fieldType.getLabel()))); + } + break; + } + } + } + } + + for (final FieldType fieldType : fields) { + for (final TypesType typesType : fieldType.getTypes()) { + String sourceLabel = + AlloyUtilities.getSigTypeById(typesType.getType().get(0).getID()).getLabel(); + sourceLabel = sourceLabel.substring(sourceLabel.lastIndexOf("/") + 1); + String targetLabel = + AlloyUtilities.getSigTypeById(typesType.getType().get(1).getID()).getLabel(); + targetLabel = targetLabel.substring(targetLabel.lastIndexOf("/") + 1); + + if (sourceLabel.equals(sigName) || allParents.get(sigName).contains(sourceLabel) + || targetLabel.equals(sigName) || allParents.get(sigName).contains(targetLabel)) { + if (reasonRelations.containsKey(sourceLabel)) { + reasonRelations.get(sourceLabel).add(fieldType.getLabel()); + } else { + reasonRelations.put(sourceLabel, new ArrayList<>(Arrays.asList(fieldType.getLabel()))); + } + } + } + } + + for (final FieldType fieldType : fields) { + final String fieldName = fieldType.getLabel(); + + final List allSources = new ArrayList<>(); + final List allTargets = new ArrayList<>(); + for (final TypesType typesType : fieldType.getTypes()) { + final EList typeType = typesType.getType(); + allSources.addAll(AlloyUtilities.getAllChildNames(typeType.get(0).getID())); + allTargets.addAll(AlloyUtilities.getAllChildNames(typeType.get(1).getID())); + } + + boolean accept = false; + for (final Entry> entry : discoverFields.entrySet()) { + final String discoverSig = entry.getKey(); + final List discoverFieldsOfSig = entry.getValue(); + if (discoverFieldsOfSig.contains(fieldName) + && (allSources.contains(discoverSig) || allTargets.contains(discoverSig))) { + accept = true; + break; + } + } + + String parentSigName = AlloyUtilities.getSigTypeById(fieldType.getParentID()).getLabel(); + parentSigName = parentSigName.substring(parentSigName.lastIndexOf("/") + 1); + + final EList tuples = fieldType.getTuple(); + for (int i = 0; i < tuples.size(); i++) { + + final String sigName1 = AlloyUtilities + .getAtomNameById(tuples.get(i).getAtom().get(0).getLabel()).replace("$", "_"); + final String sigName2 = AlloyUtilities + .getAtomNameById(tuples.get(i).getAtom().get(1).getLabel()).replace("$", "_"); + + builder.append(sigName1 + "->" + sigName2); + + if (i + 1 != tuples.size()) { + builder.append(" +\n"); + } else if (accept) { + builder.append(" in " + parentSigName + "<:" + fieldName + "\n"); + } else { + builder.append(" = " + parentSigName + "<:" + fieldName + "\n"); + } + } + + final List allRelations = new ArrayList<>(); + for (final List value : discoverFields.values()) { + allRelations.addAll(value); + } + + if (fieldType.getTuple().size() == 0 && !accept) { + builder.append(parentSigName + "." + fieldName + " = none\n"); + } + } + + builder.append("}\n"); + } + + private File createFile(final String filePath) { + File file = null; + try { + file = new File(filePath); + + if (!file.getParentFile().exists()) { + file.getParentFile().mkdirs(); + } + // if file doesn't exists, then create it + if (!file.exists()) { + file.createNewFile(); + } + } catch (final IOException e) { + e.printStackTrace(); + } + return file; + } + + private void createSigPart(final List sigs) { + for (final SigType sig : sigs) { + final String sigName = sig.getLabel().substring(sig.getLabel().lastIndexOf("/") + 1); + for (int i = 0; i < sig.getAtom().size(); i++) { + builder.append("one sig " + sigName + "_" + i + " extends " + sigName + "{ } \n"); + } + } + } + + private void createSourceFiles(final EList sources) { + boolean isFirst = false; + for (final SourceType source : sources) { + final String sourceFilePath = source.getFilename(); + final String fileName = sourceFilePath.substring( + sourceFilePath.lastIndexOf(System.getProperty("file.separator")) + 1, + sourceFilePath.lastIndexOf(".")); + if (!isFirst) { + builder.append("open " + fileName + "\n"); + isFirst = true; + } + final String newFilePath = baseFileDirectory + fileName + ".als"; + + final String content = source.getContent(); + writeContentToFile(newFilePath, content); + } + } + + public void translate() { + final DocumentRoot documentRoot = AlloyUtilities.getDocumentRoot(); + final AlloyType alloy = documentRoot.getAlloy(); + + calcOldSigValues(alloy.getInstance().getSig()); + createSourceFiles(alloy.getSource()); + createSigPart(alloy.getInstance().getSig()); + createFactPart(documentRoot, alloy.getInstance().getField()); + createRunPart(); + + writeContentToFile(alsPath, builder.toString()); + } + + private void createRunPart() { + builder.append("pred show{}\n"); + builder.append("run show for"); + + for (final Entry oldEntry : sig2oldValue.entrySet()) { + final int value = oldEntry.getValue(); + builder.append("\nexactly " + value + " " + oldEntry.getKey() + ","); + } + + builder.replace(0, builder.length(), builder.substring(0, builder.length() - 1)); // to delete + // last ',' + } + + private void writeContentToFile(final String filePath, final String content) { + try { + final File file = createFile(filePath); + final FileOutputStream out = new FileOutputStream(file); + out.write(content.getBytes()); + out.close(); + } catch (final IOException e) { + e.printStackTrace(); + } + } + + private void calcOldSigValues(final EList sigTypes) { + for (final SigType sigType : sigTypes) { + final String sigName = sigType.getLabel().substring(sigType.getLabel().lastIndexOf("/") + 1); + if (sigType.getID() > 3) { + sig2oldValue.put(sigName, sigType.getAbstract() != null ? 0 : sigType.getAtom().size()); + } + } + } + + public Map> getReasonRelations() { + return reasonRelations; + } +} diff --git a/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/alloy/analysis/reasoningforatom/ReasoningForAtom.java b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/alloy/analysis/reasoningforatom/ReasoningForAtom.java new file mode 100644 index 00000000..ce19ddb9 --- /dev/null +++ b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/alloy/analysis/reasoningforatom/ReasoningForAtom.java @@ -0,0 +1,588 @@ +package eu.modelwriter.configuration.alloy.analysis.reasoningforatom; + +import java.io.File; +import java.io.IOException; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import javax.swing.JOptionPane; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; + +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.emf.common.util.EList; +import org.eclipse.emf.common.util.URI; +import org.w3c.dom.Document; +import org.w3c.dom.Node; +import org.xml.sax.SAXException; + +import edu.mit.csail.sdg.alloy4.Err; +import edu.mit.csail.sdg.alloy4compiler.translator.A4Solution; +import eu.modelwriter.configuration.alloy.analysis.AlloySolutionFinder; +import eu.modelwriter.configuration.alloy.analysis.IAlloyAnalyzer; +import eu.modelwriter.configuration.alloy.analysis.RUN_TYPE; +import eu.modelwriter.configuration.internal.AlloyUtilities; +import eu.modelwriter.traceability.core.persistence.AtomType; +import eu.modelwriter.traceability.core.persistence.DocumentRoot; +import eu.modelwriter.traceability.core.persistence.FieldType; +import eu.modelwriter.traceability.core.persistence.TupleType; +import eu.modelwriter.traceability.core.persistence.persistenceFactory; +import eu.modelwriter.traceability.core.persistence.internal.ModelIO; + +public class ReasoningForAtom implements IAlloyAnalyzer { + + private static ReasoningForAtom instance; + private static String baseFileDirectory = ResourcesPlugin.getWorkspace().getRoot().getLocation() + + " .modelwriter reasoningForAtom ".replace(" ", System.getProperty("file.separator")); + private static final String alsPath = ReasoningForAtom.baseFileDirectory + "reasoningForAtom.als"; + private static final String xmlPath = ReasoningForAtom.baseFileDirectory + "reasoningForAtom.xml"; + private static String atomName; + private static String atomType; + private static Map> reasonRelations; + private static List solutions; + private static List>> reasonedTuples; + private static int currentSolutionIndex; + private static int MAX_NEXT_SOLUTION_ATTEMPT_TIME = 10; // 10 sec + private static long NEXT_SOLUTION_ATTEMPT_START_TIME; + private static int NEXT_SOLUTION_ATTEMPT_COUNT; + private static boolean filterOpen = true; + + private ReasoningForAtom() {} + + public static ReasoningForAtom getInstance(final String atomName, final String atomType) { + ReasoningForAtom.atomName = atomName; + ReasoningForAtom.atomType = atomType; + if (ReasoningForAtom.instance == null) { + ReasoningForAtom.instance = new ReasoningForAtom(); + ReasoningForAtom.reasonRelations = new HashMap<>(); + ReasoningForAtom.solutions = new ArrayList<>(); + ReasoningForAtom.reasonedTuples = new ArrayList<>(); + } + + return ReasoningForAtom.instance; + } + + @Override + public boolean start() throws Err { + final File reasoningXml = new File(ReasoningForAtom.xmlPath); + if (reasoningXml.exists()) { + reasoningXml.delete(); + } + final File reasoningAls = new File(ReasoningForAtom.alsPath); + if (reasoningAls.exists()) { + reasoningAls.delete(); + } + + final InstanceTranslator4ReasoningForAtom instanceTranslator = + new InstanceTranslator4ReasoningForAtom(ReasoningForAtom.baseFileDirectory, + ReasoningForAtom.alsPath, ReasoningForAtom.atomType); + instanceTranslator.translate(); + ReasoningForAtom.reasonRelations = instanceTranslator.getReasonRelations(); + + final AlloySolutionFinder parser = new AlloySolutionFinder(ReasoningForAtom.alsPath); + final A4Solution solution = parser.find(); + + if (solution == null || !solution.satisfiable()) { + return false; + } + + solution.writeXML(ReasoningForAtom.xmlPath); + parse(ReasoningForAtom.xmlPath); + ReasoningForAtom.solutions.add(solution); + ReasoningForAtom.currentSolutionIndex = 0; + ReasoningForAtom.NEXT_SOLUTION_ATTEMPT_COUNT = 0; + + return reasoning(RUN_TYPE.START); + } + + @Override + public boolean next() throws Err { + A4Solution solution; + if (ReasoningForAtom.solutions.size() == ReasoningForAtom.currentSolutionIndex + 1) { + solution = ReasoningForAtom.solutions.get(ReasoningForAtom.currentSolutionIndex).next(); + if (solution.equals(ReasoningForAtom.solutions.get(ReasoningForAtom.currentSolutionIndex))) { + return false; + } else if (solution.satisfiable()) { + ReasoningForAtom.solutions.add(solution); + ReasoningForAtom.currentSolutionIndex++; + } else { + return false; + } + } else { + solution = ReasoningForAtom.solutions.get(ReasoningForAtom.currentSolutionIndex + 1); + ReasoningForAtom.currentSolutionIndex++; + } + + solution.writeXML(ReasoningForAtom.xmlPath); + parse(ReasoningForAtom.xmlPath); + + return reasoning(RUN_TYPE.NEXT); + } + + @Override + public boolean previous() throws Err { + A4Solution solution; + if (ReasoningForAtom.currentSolutionIndex == 0) { + return false; + } else { + solution = ReasoningForAtom.solutions.get(ReasoningForAtom.currentSolutionIndex - 1); + ReasoningForAtom.currentSolutionIndex--; + } + + solution.writeXML(ReasoningForAtom.xmlPath); + parse(ReasoningForAtom.xmlPath); + + return reasoning(RUN_TYPE.PREVIOUS); + } + + private void parse(final String xmlPath) { + final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + DocumentBuilder builder; + try { + builder = factory.newDocumentBuilder(); + final File file = new File(xmlPath); + final Document document = builder.parse(file); + final Node instance = document.getElementsByTagName("instance").item(0); + instance.getAttributes().removeNamedItem("command"); + + Transformer transformer; + try { + transformer = TransformerFactory.newInstance().newTransformer(); + final DOMSource source = new DOMSource(document); + final StreamResult result = new StreamResult(file); + transformer.transform(source, result); + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2"); + } catch (final Exception e) { + } + } catch (ParserConfigurationException | SAXException | IOException e) { + e.printStackTrace(); + } + } + + private void deleteOldReasoning(final RUN_TYPE runType) { + final DocumentRoot documentRoot = AlloyUtilities.getDocumentRoot(); + int solutionNumber = runType == RUN_TYPE.NEXT ? ReasoningForAtom.currentSolutionIndex - 1 + : runType == RUN_TYPE.PREVIOUS ? ReasoningForAtom.currentSolutionIndex + 1 : -1; + solutionNumber = solutionNumber >= ReasoningForAtom.reasonedTuples.size() ? -1 : solutionNumber; + if (solutionNumber == -1) { + return; + } + + final Iterator>> oldReasonedTuplesIterator = + ReasoningForAtom.reasonedTuples.get(solutionNumber).entrySet().iterator(); + final EList fieldTypes = documentRoot.getAlloy().getInstance().getField(); + while (oldReasonedTuplesIterator.hasNext()) { + final Entry> entry = oldReasonedTuplesIterator.next(); + for (final FieldType fieldType : fieldTypes) { + if (fieldType.getID() == entry.getKey()) { + for (final TupleType oldTupleType : entry.getValue()) { + final Iterator tupleIter = fieldType.getTuple().iterator(); + final AtomType oldAtomType0 = oldTupleType.getAtom().get(0); + final AtomType oldAtomType1 = oldTupleType.getAtom().get(1); + while (tupleIter.hasNext()) { + final TupleType tupleType = tupleIter.next(); + if (tupleType.isReasoned()) { + final AtomType atomType0 = tupleType.getAtom().get(0); + final AtomType atomType1 = tupleType.getAtom().get(1); + if (oldAtomType0.getLabel().equals(atomType0.getLabel()) + && oldAtomType1.getLabel().equals(atomType1.getLabel())) { + tupleIter.remove(); + } + } + } + } + } + } + } + + AlloyUtilities.writeDocumentRoot(documentRoot); + } + + @Override + public void finish() { + ReasoningForAtom.solutions.clear(); + ReasoningForAtom.currentSolutionIndex = 0; + ReasoningForAtom.reasonRelations.clear(); + ReasoningForAtom.reasonedTuples.clear(); + } + + public DocumentRoot getDocumentRoot() { + @SuppressWarnings("rawtypes") + final ModelIO modelIO = new ModelIO<>(); + @SuppressWarnings("rawtypes") + List list = null; + try { + list = modelIO.read(URI.createFileURI(ReasoningForAtom.xmlPath)); + } catch (final IOException e) { + return null; + } + if (list == null || list.isEmpty()) { + return null; + } + final DocumentRoot documentRoot = (DocumentRoot) list.get(0); + return documentRoot; + } + + private boolean reasoning(final RUN_TYPE runType) throws Err { + deleteOldReasoning(runType); + + int unacceptedReasonedTupleCount = 0, acceptedTupleCount = 0; + if (ReasoningForAtom.solutions.size() > ReasoningForAtom.reasonedTuples.size()) { + + final Map> newReasonedTuples = + findReasonedTuples(ReasoningForAtom.atomName); + ReasoningForAtom.reasonedTuples.add(newReasonedTuples); + + if (ReasoningForAtom.filterOpen) { + if (!isValidAndUniqueReason(newReasonedTuples)) { + final long nextSolutionProcessTime = + Instant.now().getEpochSecond() - ReasoningForAtom.NEXT_SOLUTION_ATTEMPT_START_TIME; + if (ReasoningForAtom.NEXT_SOLUTION_ATTEMPT_COUNT != 0 + && nextSolutionProcessTime > ReasoningForAtom.MAX_NEXT_SOLUTION_ATTEMPT_TIME) { + final boolean rollBackSucceed = rollBackNextAttemption(false); + if (rollBackSucceed) { + final Map> unacceptedTupleTypes = filterAcceptedTupleTypes( + ReasoningForAtom.reasonedTuples.get(ReasoningForAtom.currentSolutionIndex)); + writeReasonedTupleTypes(unacceptedTupleTypes); + unacceptedReasonedTupleCount = calcReasonedTupleCount(unacceptedTupleTypes); + } + return false; + } + if (ReasoningForAtom.NEXT_SOLUTION_ATTEMPT_COUNT == 0) { + ReasoningForAtom.NEXT_SOLUTION_ATTEMPT_START_TIME = Instant.now().getEpochSecond(); + } + ReasoningForAtom.NEXT_SOLUTION_ATTEMPT_COUNT++; + return next(); + } else { + if (ReasoningForAtom.NEXT_SOLUTION_ATTEMPT_COUNT > 0) { + rollBackNextAttemption(true); + } + writeReasonedTupleTypes(newReasonedTuples); + unacceptedReasonedTupleCount = calcReasonedTupleCount(newReasonedTuples); + } + } else { + final int reasonedTupleCount = calcReasonedTupleCount(newReasonedTuples); + final Map> unacceptedTupleTypes = + filterAcceptedTupleTypes(newReasonedTuples); + writeReasonedTupleTypes(unacceptedTupleTypes); + unacceptedReasonedTupleCount = calcReasonedTupleCount(unacceptedTupleTypes); + acceptedTupleCount = reasonedTupleCount - unacceptedReasonedTupleCount; + } + } else { + final Map> existingReasonedTuples = + ReasoningForAtom.reasonedTuples.get(ReasoningForAtom.currentSolutionIndex); + if (ReasoningForAtom.filterOpen) { + if (!isValidAndUniqueReason(existingReasonedTuples)) { + if (runType.equals(RUN_TYPE.NEXT)) { + return next(); + } else if (runType.equals(RUN_TYPE.PREVIOUS)) { + return previous(); + } + } + writeReasonedTupleTypes(existingReasonedTuples); + unacceptedReasonedTupleCount = calcReasonedTupleCount(existingReasonedTuples); + } else { + final int reasonedTupleCount = calcReasonedTupleCount(existingReasonedTuples); + final Map> unacceptedTupleTypes = + filterAcceptedTupleTypes(existingReasonedTuples); + writeReasonedTupleTypes(unacceptedTupleTypes); + unacceptedReasonedTupleCount = calcReasonedTupleCount(unacceptedTupleTypes); + acceptedTupleCount = reasonedTupleCount - unacceptedReasonedTupleCount; + } + } + + final String reasonedTupleCountMessage = unacceptedReasonedTupleCount + (acceptedTupleCount > 0 + ? "\nYou accepted " + acceptedTupleCount + " reasoned relation before." : ""); + + JOptionPane.showMessageDialog(null, + "Reasoning on relations successfully completed.\nReasoned relation count: " + + reasonedTupleCountMessage, + "Reasoning on Relations of Selected Atom", JOptionPane.WARNING_MESSAGE); + return true; + } + + /** + * filters accepted tupleTypes and returns unaccepted ones. + * + * @param newReasonedTuples + * @return + */ + private Map> filterAcceptedTupleTypes( + final Map> newReasonedTuples) { + final DocumentRoot documentRoot = AlloyUtilities.getDocumentRoot(); + final Map> unacceptedTupleTypes = new HashMap<>(); + + final Iterator fieldIterator = + documentRoot.getAlloy().getInstance().getField().iterator(); + while (fieldIterator.hasNext()) { + final FieldType oldFieldType = fieldIterator.next(); + final int fieldId = oldFieldType.getID(); + final List newTupleTypes = newReasonedTuples.get(fieldId); + if (newTupleTypes != null) { + final List unacceptedTupleTypesList = new ArrayList<>(); + for (final TupleType newTupleType : newTupleTypes) { + if (!isTupleAccepted(fieldId, newTupleType)) { // IF REASONED TUPLE IS NOT ACCEPTED DURING + // ANALYSIS. + unacceptedTupleTypesList.add(newTupleType); + } + } + unacceptedTupleTypes.put(fieldId, unacceptedTupleTypesList); + } + } + + return unacceptedTupleTypes; + } + + /** + * checks if tupleType is accepted in persistence file. + * + * @param fieldId + * @param tupleType + * @return + */ + private boolean isTupleAccepted(final int fieldId, final TupleType tupleType) { + return AlloyUtilities.getDocumentRoot().getAlloy().getInstance().getField().stream() + .filter(f -> f.getID() == fieldId).findFirst().get().getTuple().stream() + .anyMatch(t -> (t.getAtom().get(0).getLabel().equals(tupleType.getAtom().get(0).getLabel()) + && t.getAtom().get(1).getLabel().equals(tupleType.getAtom().get(1).getLabel()))); + } + + private void writeReasonedTupleTypes(final Map> newReasonedTuples) { + final DocumentRoot documentRoot = AlloyUtilities.getDocumentRoot(); + + final Iterator fieldIterator = + documentRoot.getAlloy().getInstance().getField().iterator(); + while (fieldIterator.hasNext()) { + final FieldType oldFieldType = fieldIterator.next(); + final int fieldId = oldFieldType.getID(); + final List newTupleTypes = newReasonedTuples.get(fieldId); + if (newTupleTypes != null) { + oldFieldType.getTuple().addAll(newTupleTypes); + } + } + + AlloyUtilities.writeDocumentRoot(documentRoot); + } + + private boolean rollBackNextAttemption(final boolean lastSolutionIsValid) { + final int lastSolutionIndex = ReasoningForAtom.solutions.size() - 1; + int validSolutionIndex = lastSolutionIsValid ? lastSolutionIndex + : lastSolutionIndex - ReasoningForAtom.NEXT_SOLUTION_ATTEMPT_COUNT - 1; + ReasoningForAtom.currentSolutionIndex = + lastSolutionIsValid ? lastSolutionIndex - ReasoningForAtom.NEXT_SOLUTION_ATTEMPT_COUNT + : lastSolutionIndex - ReasoningForAtom.NEXT_SOLUTION_ATTEMPT_COUNT - 1; + ReasoningForAtom.NEXT_SOLUTION_ATTEMPT_COUNT = 0; + ReasoningForAtom.NEXT_SOLUTION_ATTEMPT_START_TIME = 0; + + validSolutionIndex = validSolutionIndex > 0 ? validSolutionIndex : 0; + ReasoningForAtom.currentSolutionIndex = + ReasoningForAtom.currentSolutionIndex > 0 ? ReasoningForAtom.currentSolutionIndex : 0; + + if (ReasoningForAtom.reasonedTuples.get(validSolutionIndex).size() == 0) { + finish(); + return false; + } + + ReasoningForAtom.solutions.set(ReasoningForAtom.currentSolutionIndex, + ReasoningForAtom.solutions.get(validSolutionIndex)); + ReasoningForAtom.reasonedTuples.set(ReasoningForAtom.currentSolutionIndex, + ReasoningForAtom.reasonedTuples.get(validSolutionIndex)); + + for (int i = lastSolutionIndex; i > ReasoningForAtom.currentSolutionIndex; i--) { + ReasoningForAtom.solutions.remove(i); + ReasoningForAtom.reasonedTuples.remove(i); + } + return true; + } + + private int calcReasonedTupleCount(final Map> newReasonedTuples) { + int reasonedTupleCount = 0; + for (final Entry> entry : newReasonedTuples.entrySet()) { + reasonedTupleCount += entry.getValue().size(); + } + return reasonedTupleCount; + } + + private Map> findReasonedTuples(final String atomName) throws Err { + final DocumentRoot documentRootReasoning = getDocumentRoot(); + final DocumentRoot documentRootOriginal = AlloyUtilities.getDocumentRoot(); + + final Map> newReasonedTuples = new HashMap<>(); + + for (final FieldType fieldType_R : documentRootReasoning.getAlloy().getInstance().getField()) { + for (final FieldType fieldType_O : documentRootOriginal.getAlloy().getInstance().getField()) { + if (!fieldType_R.getLabel().equals(fieldType_O.getLabel())) { + continue; + } + + final int sourceId_R = fieldType_R.getParentID(); + final String sourceSigName_R = + AlloyUtilities.getSigNameById(sourceId_R, documentRootReasoning); + if (!ReasoningForAtom.reasonRelations.containsKey(sourceSigName_R) + || !ReasoningForAtom.reasonRelations.get(sourceSigName_R) + .contains(fieldType_R.getLabel())) { + continue; + } + + final int sourceId_O = fieldType_O.getParentID(); + final String sourceSigName_O = + AlloyUtilities.getSigNameById(sourceId_O, documentRootOriginal); + if (!ReasoningForAtom.reasonRelations.containsKey(sourceSigName_O) + || !ReasoningForAtom.reasonRelations.get(sourceSigName_O) + .contains(fieldType_O.getLabel())) { + continue; + } + + if (!sourceSigName_O.equals(sourceSigName_R)) { + continue; + } + + if (fieldType_O.getTuple().size() == fieldType_R.getTuple().size()) { + continue; + } + + for (final TupleType tuple_R : fieldType_R.getTuple()) { + AtomType atomType0_R = getOriginalAtomType(tuple_R.getAtom().get(0).getLabel()); + final AtomType atomType1_R = getOriginalAtomType(tuple_R.getAtom().get(1).getLabel()); + + if (atomType0_R == null || atomType1_R == null) { + continue; + } + + boolean exists = false; + for (final TupleType tuple_O : fieldType_O.getTuple()) { + if (atomType0_R.getLabel().equals(tuple_O.getAtom().get(0).getLabel()) + && atomType1_R.getLabel().equals(tuple_O.getAtom().get(1).getLabel())) { + exists = true; + break; + } + } + + if (!exists || fieldType_O.getTuple().size() == 0) { + final TupleType tupleType = persistenceFactory.eINSTANCE.createTupleType(); + if (atomType0_R.equals(atomType1_R)) { + atomType0_R = AlloyUtilities.cloneAtomType(atomType0_R); + } + tupleType.getAtom().add(atomType0_R); + tupleType.getAtom().add(atomType1_R); + tupleType.setReasoned(true); + + if (!AlloyUtilities.getAtomNameById(atomType0_R.getLabel()).replace("$", "") + .equals(atomName) + && !AlloyUtilities.getAtomNameById(atomType1_R.getLabel()).replace("$", "") + .equals(atomName)) { + continue; + } + + if (newReasonedTuples.get(fieldType_O.getID()) == null) { + newReasonedTuples.put(fieldType_O.getID(), new ArrayList<>(Arrays.asList(tupleType))); + } else { + newReasonedTuples.get(fieldType_O.getID()).add(tupleType); + } + } + } + } + } + return newReasonedTuples; + } + + private boolean isValidAndUniqueReason(final Map> reasonedTuples) { + final Map> unacceptedTupleTypes = + filterAcceptedTupleTypes(reasonedTuples); + final int unacceptedReasonedTupleCount = calcReasonedTupleCount(unacceptedTupleTypes); + + if (unacceptedReasonedTupleCount == 0) { // 1 + return false; + } + // dont swap 1 and 2 + if (ReasoningForAtom.solutions.size() == 1) { // 2 + return true; + } + + if (getUnmatchedExistingReasoningCount( + unacceptedTupleTypes) == ReasoningForAtom.reasonedTuples.size() - 1) { + return true; + } + return false; + } + + private int getUnmatchedExistingReasoningCount( + final Map> reasonedTuples) { + int unMatchedExistingReasoningCount = 0; + for (int solutionNumber = 0; solutionNumber < ReasoningForAtom.reasonedTuples + .size(); solutionNumber++) { + if (solutionNumber == ReasoningForAtom.currentSolutionIndex) { + continue; + } + final Map> existingReasonedTuples = + ReasoningForAtom.reasonedTuples.get(solutionNumber); + for (final Entry> newReasonsedTuplesEntry : reasonedTuples + .entrySet()) { + final Integer fieldId = newReasonsedTuplesEntry.getKey(); + final List tupleList_New = newReasonsedTuplesEntry.getValue(); + if (existingReasonedTuples.containsKey(fieldId)) { + final List tupleList_Old = existingReasonedTuples.get(fieldId); + if (tupleList_New.size() != tupleList_Old.size()) { + unMatchedExistingReasoningCount++; + break; + } + int matchedTupleCount = 0; + for (final TupleType tupleType_New : tupleList_New) { + final String atom0Label_New = tupleType_New.getAtom().get(0).getLabel(); + final String atom1Label_New = tupleType_New.getAtom().get(1).getLabel(); + + final boolean anyMatchOldTuple = tupleList_Old.stream().anyMatch( + tupleType_Old -> tupleType_Old.getAtom().get(0).getLabel().equals(atom0Label_New) + && tupleType_Old.getAtom().get(1).getLabel().equals(atom1Label_New)); + + if (anyMatchOldTuple) { + matchedTupleCount++; + } + } + if (matchedTupleCount != tupleList_New.size()) { + unMatchedExistingReasoningCount++; + break; + } + } else { + unMatchedExistingReasoningCount++; + break; + } + } + } + return unMatchedExistingReasoningCount; + } + + private AtomType getOriginalAtomType(final String name_R) { + if (name_R.contains("/")) { + return null; + } + final String name = name_R.substring(0, name_R.lastIndexOf("_")); + final int id = + Integer.parseInt(name_R.substring(name_R.lastIndexOf("_") + 1, name_R.lastIndexOf("$"))); + + return AlloyUtilities.getSigTypeById(AlloyUtilities.getSigTypeIdByName(name)).getAtom().get(id); + } + + @Override + public void setFilterState(final boolean isOpen) { + ReasoningForAtom.filterOpen = isOpen; + } + + @Override + public void setNextSolMaxTime(final int maxTime) { + ReasoningForAtom.MAX_NEXT_SOLUTION_ATTEMPT_TIME = maxTime; + } +} diff --git a/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/alloy/trace/LoadItem.java b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/alloy/trace/LoadItem.java new file mode 100644 index 00000000..ad51ddab --- /dev/null +++ b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/alloy/trace/LoadItem.java @@ -0,0 +1,119 @@ +package eu.modelwriter.configuration.alloy.trace; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.core.resources.IFile; +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EClassifier; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EPackage; +import org.eclipse.emf.ecore.resource.Resource; + +import eu.modelwriter.configuration.internal.EcoreUtilities; +import eu.modelwriter.configuration.internal.Utilities; + +public class LoadItem { + /** + * alias from -- loadAlias + */ + private String alias; + /** + * Paths from -- loadModel & -- loadInstace + */ + private IFile modelFile, instanceFile; + /** + * Root EObjects + */ + private EObject modelRoot = null, instanceRoot = null; + + private Resource instanceResource = null; + + private Map allEClasses; + + public LoadItem(String alias, String modelFilePath, String instanceFilePath) + throws TraceException, NullPointerException { + this.alias = alias; + allEClasses = new HashMap<>(); + try { + modelFile = Utilities.getIFileFromPath(modelFilePath); + modelRoot = EcoreUtilities.getRootObject(modelFile.getFullPath().toString()); + } catch (IOException | IllegalArgumentException e) { + System.err.println("Tarski: EMF Model file can't loaded, load alias: " + alias); + } + try { + instanceFile = Utilities.getIFileFromPath(instanceFilePath); + instanceResource = EcoreUtilities.loadInstanceRoot(instanceFile.getFullPath().toString()); + instanceRoot = instanceResource.getContents().get(0); + for (Object pack : instanceResource.getResourceSet().getPackageRegistry().values()) { + loadEClasses((EPackage) pack); + } + } catch (IOException | IllegalArgumentException e) { + System.err.println("Tarski: EMF Instance file can't loaded, load alias: " + alias); + } + + if (modelRoot == null && instanceRoot == null) { + throw new TraceException("Both model and instace files can't loaded for alias " + alias); + } + } + + + private void loadEClasses(EPackage packageee) { + for (EClassifier classifier : packageee.getEClassifiers()) { + allEClasses.put(classifier.getName(), (EClass) classifier); + } + for (EPackage subPackage : packageee.getESubpackages()) { + loadEClasses(subPackage); + } + } + + public String getAlias() { + return alias; + } + + public void setAlias(String alias) { + this.alias = alias; + } + + public IFile getModelFile() { + return modelFile; + } + + public void setModelFile(IFile modelFile) { + this.modelFile = modelFile; + } + + public IFile getInstanceFile() { + return instanceFile; + } + + public void setInstanceFile(IFile instanceFile) { + this.instanceFile = instanceFile; + } + + public EObject getModelRoot() { + return modelRoot; + } + + public void setModelRoot(EObject modelRoot) { + this.modelRoot = modelRoot; + } + + public EObject getInstanceRoot() { + return instanceRoot; + } + + public void setInstanceRoot(EObject instanceRoot) { + this.instanceRoot = instanceRoot; + } + + public Map getAllEClasses() { + return allEClasses; + } + + public EClass getEClass(String eClassName) { + return allEClasses.get(eClassName); + } + +} diff --git a/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/alloy/trace/RelationTrace.java b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/alloy/trace/RelationTrace.java new file mode 100644 index 00000000..fc2a7d81 --- /dev/null +++ b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/alloy/trace/RelationTrace.java @@ -0,0 +1,66 @@ +package eu.modelwriter.configuration.alloy.trace; + +public class RelationTrace extends Trace { + private String relationName; + private String referenceName; + private String className; + private SigTrace source = null; + private SigTrace target = null; + + public RelationTrace(String alias, String className, String relationName, String referenceName) { + super(alias); + this.className = className; + this.relationName = relationName; + this.referenceName = referenceName; + } + + public RelationTrace(String alias, String relationName, String referenceName, SigTrace source, + SigTrace target) { + super(alias); + setRelationName(relationName); + setReferenceName(referenceName); + setSource(source); + setTarget(target); + } + + public String getRelationName() { + return relationName; + } + + public void setRelationName(String relationName) { + this.relationName = relationName; + } + + public String getReferenceName() { + return referenceName; + } + + public void setReferenceName(String referenceName) { + this.referenceName = referenceName; + } + + public SigTrace getSource() { + return source; + } + + public void setSource(SigTrace source) { + this.source = source; + } + + public SigTrace getTarget() { + return target; + } + + public void setTarget(SigTrace target) { + this.target = target; + } + + public String getClassName() { + return className; + } + + public void setClassName(String className) { + this.className = className; + } + +} diff --git a/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/alloy/trace/SigTrace.java b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/alloy/trace/SigTrace.java new file mode 100644 index 00000000..3e90190c --- /dev/null +++ b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/alloy/trace/SigTrace.java @@ -0,0 +1,57 @@ +package eu.modelwriter.configuration.alloy.trace; + +import org.eclipse.emf.ecore.EClass; + +public class SigTrace extends Trace { + private String sigType; + private String className; + private LoadItem load; + private EClass eClass; + + public SigTrace(String alias, String sigName, String className) { + super(alias); + setSigType(sigName); + setClassName(className); + } + + public SigTrace(String alias, String sigType, String className, LoadItem load, EClass eClass) { + super(alias); + this.sigType = sigType; + this.className = className; + this.load = load; + this.eClass = eClass; + } + + public String getClassName() { + return className; + } + + public EClass getEClass() { + return eClass; + } + + public LoadItem getLoad() { + return load; + } + + public String getSigType() { + return sigType; + } + + public void setClassName(String className) { + this.className = className; + } + + public void seteClass(EClass eClass) { + this.eClass = eClass; + } + + public void setLoad(LoadItem load) { + this.load = load; + } + + public void setSigType(String sigType) { + this.sigType = sigType; + } + +} diff --git a/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/alloy/trace/Trace.java b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/alloy/trace/Trace.java new file mode 100644 index 00000000..e7249607 --- /dev/null +++ b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/alloy/trace/Trace.java @@ -0,0 +1,17 @@ +package eu.modelwriter.configuration.alloy.trace; + +public abstract class Trace { + private String alias; + + public Trace(String alias) { + setAlias(alias); + } + + public String getAlias() { + return alias; + } + + public void setAlias(String alias) { + this.alias = alias; + } +} diff --git a/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/alloy/trace/TraceException.java b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/alloy/trace/TraceException.java new file mode 100644 index 00000000..1ae8ea9b --- /dev/null +++ b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/alloy/trace/TraceException.java @@ -0,0 +1,20 @@ +package eu.modelwriter.configuration.alloy.trace; + +public class TraceException extends Exception { + + /** + * + */ + private static final long serialVersionUID = -4595785190626648593L; + + private final String message; + + public TraceException(String message) { + this.message = message; + } + + @Override + public String getMessage() { + return message; + } +} diff --git a/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/alloy/trace/TraceManager.java b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/alloy/trace/TraceManager.java new file mode 100644 index 00000000..1a33f0ec --- /dev/null +++ b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/alloy/trace/TraceManager.java @@ -0,0 +1,373 @@ +package eu.modelwriter.configuration.alloy.trace; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Scanner; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.eclipse.core.resources.IMarker; +import org.eclipse.emf.common.util.EList; +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EReference; +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.emf.ecore.util.EcoreUtil; + +import eu.modelwriter.configuration.internal.EcoreUtilities; +import eu.modelwriter.configuration.internal.Utilities; +import eu.modelwriter.marker.internal.MarkUtilities; +import eu.modelwriter.marker.internal.MarkerFactory; + +public class TraceManager { + + private static TraceManager instance = null; + + private String alloyPath; + + private final List sigTraces = new ArrayList(); + private final List relationTraces = new ArrayList(); + private final List loads = new ArrayList(); + + /** + * + * @return @TraceManager instance + */ + public static TraceManager get() { + if (instance == null) + instance = new TraceManager(); + return instance; + } + + private TraceManager() {} + + /** + * Loads traces from given path + * + * @param alloyPath Path to load alloy file + * @throws TraceException on any error + */ + public void loadSpec(String alloyPath) throws TraceException { + this.alloyPath = alloyPath; + loads.clear(); + sigTraces.clear(); + relationTraces.clear(); + scanFile(alloyPath, true); + } + + public void reload() throws TraceException { + if (!alloyPath.isEmpty()) + loadSpec(alloyPath); + else + throw new TraceException("Alloy file is not set!"); + } + + public boolean hasTraces() { + return !sigTraces.isEmpty() && !relationTraces.isEmpty(); + } + + public boolean hasInstance() { + for (LoadItem loadItem : loads) { + if (loadItem.getInstanceRoot() != null) + return true; + } + return false; + } + + public boolean hasSigTrace(String sigTypeName) { + try { + return getSigTraceByType(sigTypeName).getLoad().getInstanceRoot() != null; + } catch (TraceException e) { + return false; + } + } + + public List getLoads() { + return loads; + } + + private void scanFile(String alloyPath, boolean scanTraces) throws TraceException { + final Pattern sigTracePattern = Pattern.compile( + "(\\s*)(-)(-)(\\s*)(Trace|trace)(@)((?:[a-z0-9_]+))(\\.)((?:[a-z0-9_]+))(\\s*)", + Pattern.CASE_INSENSITIVE | Pattern.DOTALL); + final Pattern relationTracePattern = Pattern.compile( + "(\\s*)(-)(-)(\\s*)(Trace|trace)(@)((?:[a-z0-9_]+))(\\.)((?:[a-z0-9_]+))(\\.)((?:[a-z0-9_]+))(\\s*)", + Pattern.CASE_INSENSITIVE | Pattern.DOTALL); + + Scanner scanner = null; + try { + final File file = new File(alloyPath); + scanner = new Scanner(file); + } catch (final FileNotFoundException e) { + throw new TraceException("File not found. Path: " + alloyPath); + } + + while (scanner.hasNextLine()) { + String line = Utilities.getNextLine(scanner); + + if (line.toLowerCase().startsWith("--loadalias") + || line.toLowerCase().startsWith("-- loadalias")) { + findLoads(scanner, line); + } + + if (scanTraces) { + final Matcher sigTrace = sigTracePattern.matcher(line); + final Matcher relationTrace = relationTracePattern.matcher(line); + + if (relationTrace.find()) { + findRelationTrace(scanner, line, relationTrace); + } else if (sigTrace.find()) { + findSigTrace(scanner, line, sigTrace); + } + } + } + scanner.close(); + } + + private void findSigTrace(Scanner scanner, String line, Matcher sigTraceMatcher) + throws TraceException { + String traceAlias = sigTraceMatcher.group(7); + String className = sigTraceMatcher.group(9); + do { + line = Utilities.getNextLine(scanner); + } while (!line.contains("sig ")); + + if (line.contains("sig ")) { + final int start = line.indexOf("sig ") + 4; + int stop = line.indexOf(" ", start); + stop = stop == -1 ? line.indexOf("{", start) : stop; + stop = stop == -1 ? line.indexOf("\n", start) : stop; + final String sigType = line.substring(start, stop).trim(); + LoadItem loadItem = getLoadByAlias(traceAlias); + if (loadItem == null) + throw new TraceException("Load traces must be above than other traces!"); + SigTrace sigTrace = + new SigTrace(traceAlias, sigType, className, loadItem, loadItem.getEClass(className)); + sigTraces.add(sigTrace); + } + } + + private void findRelationTrace(Scanner scanner, String line, Matcher relationTraceMatcher) + throws TraceException { + String traceAlias = relationTraceMatcher.group(7); + String traceClass = relationTraceMatcher.group(9); + String traceReference = relationTraceMatcher.group(11); + + do { + line = Utilities.getNextLine(scanner); + } while (!line.contains(":")); + + if (line.contains(":")) { + final String[] strings = line.split(" "); + final String relation = strings[0].replaceAll(":", "").trim(); + @SuppressWarnings("unused") + final String targetSig = + strings[strings.length - 1].replaceAll(",", "").replaceAll("}", "").trim(); + RelationTrace relationTrace = + new RelationTrace(traceAlias, traceClass, relation, traceReference); + relationTrace.setSource(getSigTraceByClassName(traceClass)); + relationTraces.add(relationTrace); + } + } + + private void findLoads(Scanner scanner, String line) throws TraceException { + String alias = "", modelFilePath = "", instanceFilePath = ""; + alias = line.substring(line.indexOf("@") + 1).trim(); + for (int i = 0; i < 2; i++) { + String nextLine = Utilities.getNextLine(scanner); + if (nextLine.toLowerCase().contains("loadmodel@")) { + modelFilePath = nextLine.substring(nextLine.indexOf("@") + 1); + } else if (nextLine.toLowerCase().contains("loadinstance@")) { + instanceFilePath = nextLine.substring(nextLine.indexOf("@") + 1); + } + } + if (!alias.isEmpty() /* && !modelFilePath.isEmpty() && !instanceFilePath.isEmpty() */) { + loads.add(new LoadItem(alias, modelFilePath, instanceFilePath)); + } else { + throw new TraceException("Can't load EMF models!"); + } + } + + public SigTrace getSigTraceByType(String type) throws TraceException { + return sigTraces.stream().filter(sigTrace -> sigTrace.getSigType().equals(type)).findFirst() + .orElseThrow(() -> new TraceException("There is no trace for the sig: " + type)); + } + + public SigTrace getSigTraceByClassName(String name) throws TraceException { + return sigTraces.stream().filter(sigTrace -> sigTrace.getClassName().equals(name)).findFirst() + .orElseThrow(() -> new TraceException("There is no trace for the EClass: " + name)); + } + + public RelationTrace getRelationTraceByReferenceName(String reference) { + return relationTraces.stream().filter(rt -> rt.getReferenceName().equals(reference)).findFirst() + .orElse(null); + } + + public RelationTrace getRelationTrace(String className, String refName) { + return relationTraces.stream() + .filter( + rt -> (rt.getClassName().equals(className) && rt.getReferenceName().equals(refName))) + .findFirst().orElse(null); + } + + public RelationTrace getRelationTrace2(String className, String relName) { + return relationTraces.stream() + .filter(rt -> (rt.getClassName().equals(className) && rt.getRelationName().equals(relName))) + .findFirst().orElse(null); + } + + @SuppressWarnings("unused") + private RelationTrace getRelationTraceBySigType(String sourceSigType, String relationName) { + return relationTraces.stream().filter(t -> (t.getSource().getSigType().equals(sourceSigType) + && t.getRelationName().equals(relationName))).findFirst().orElse(null); + } + + public RelationTrace getRelationTraceByRelationName(String rel) { + return relationTraces.stream().filter(rt -> rt.getRelationName().equals(rel)).findFirst() + .orElse(null); + } + + public LoadItem getLoadByAlias(String alias) { + return loads.stream().filter(load -> load.getAlias().equals(alias)).findFirst().orElse(null); + } + + public LoadItem getLoadByInstance(EObject instance) { + return loads.stream().filter(load -> load.getInstanceRoot().equals(instance)).findFirst() + .orElse(null); + } + + public boolean deleteEObject(String sigType, String relativeObjectURI) + throws IOException, TraceException { + EObject eObject = findEObject(sigType, relativeObjectURI); + if (eObject != null) { + Resource eResource = eObject.eResource(); + EcoreUtil.delete(eObject); + eResource.save(null); + return true; + } + return false; + } + + private EObject findEObject(String sigTypeName, String relativeObjectURI) throws TraceException { + if (relativeObjectURI != null && sigTypeName != null) { + SigTrace trace = getSigTraceByType(sigTypeName); + return EcoreUtil.getEObject(trace.getLoad().getInstanceRoot(), relativeObjectURI); + } + return null; + } + + public EObject findEObject(IMarker marker) throws TraceException { + if (marker == null) + return null; + String relURI = marker.getAttribute(MarkUtilities.RELATIVE_URI, ""); + String sigTypeName = marker.getAttribute(MarkUtilities.MARKER_TYPE, ""); + return findEObject(sigTypeName, relURI); + } + + @SuppressWarnings("unused") + private EObject findEObject(String uri) { + String rootURI = uri.split("#")[0]; + String relURI = uri.split("#")[1]; + EObject root = findInstanceRootByURI(rootURI); + + return EcoreUtil.getEObject(root, relURI); + } + + private EObject findInstanceRootByURI(String rootURI) { + for (LoadItem loadItem : loads) { + if (loadItem.getInstanceRoot().eResource().getURI().toString().equals(rootURI)) { + return loadItem.getInstanceRoot(); + } + } + return null; + } + + public EObject createEObject(String sigTypeName, String atomName, IMarker source) + throws TraceException { + SigTrace trace = getSigTraceByType(sigTypeName); + EClass eClass = trace.getEClass(); + EObject eObject = EcoreUtil.create(eClass); + EcoreUtilities.eSetAttributeByName(eObject, "name", atomName); + EcoreUtilities.putIntoContainer( + source == null ? trace.getLoad().getInstanceRoot() : findEObject(source), eObject); + EcoreUtilities.saveResource(trace.getLoad().getInstanceRoot()); + return eObject; + } + + public IMarker createMarkerForEObject(EObject eObject) throws TraceException { + SigTrace trace = getSigTraceByClassName(eObject.eClass().getName()); + return MarkerFactory.createInstanceMarker(eObject, trace.getLoad().getInstanceFile(), + trace.getSigType()); + } + + public Set getContainerSigTypes(String sigTypeName) { + try { + SigTrace trace = getSigTraceByType(sigTypeName); + return findContainers(trace.getLoad().getAllEClasses().values(), sigTypeName); + } catch (TraceException e) { + return new HashSet(); + } + } + + public String getContainmentRelation(IMarker fromMarker, IMarker toMarker) throws TraceException { + EObject source = findEObject(fromMarker); + EObject target = findEObject(toMarker); + EReference eReference = EcoreUtilities.getContainmentEReference(source, target); + if (eReference != null) + return getRelationTrace(((EClass) eReference.eContainer()).getName(), eReference.getName()) + .getRelationName(); + return null; + } + + public void createReference(IMarker fromMarker, IMarker toMarker, String relationName) + throws TraceException { + EObject source = findEObject(fromMarker); + EObject target = findEObject(toMarker); + EReference ref = null; + for (EReference eReference : source.eClass().getEAllReferences()) { + RelationTrace relationTrace = + getRelationTrace(((EClass) eReference.eContainer()).getName(), eReference.getName()); + if (relationTrace != null && relationTrace.getRelationName().equals(relationName)) { + ref = eReference; + break; + } + } + if (ref != null) { + EcoreUtilities.eSetReferenceByName(source, ref.getName(), target); + EcoreUtilities.saveResource(source); + } + } + + public Set findContainers(Collection collection, String selectedType) { + Set result = new HashSet(); + SigTrace typeTrace; + try { + typeTrace = getSigTraceByType(selectedType); + } catch (TraceException e1) { + return result; + } + EList superTypes = typeTrace.getEClass().getEAllSuperTypes(); + for (EClass eClass : collection) { + for (EReference eReference : eClass.getEAllReferences()) { + if (eReference.isContainment()) { + try { + if (superTypes.stream() + .anyMatch(s -> s.getName().equals(eReference.getEReferenceType().getName()))) { + result.add(getSigTraceByClassName(eClass.getName()).getSigType()); + } + } catch (TraceException e) { + // no need to handle + } + } + } + } + return result; + } + +} diff --git a/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/alloy2emf/AlloyRunCommandsPage.java b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/alloy2emf/AlloyRunCommandsPage.java new file mode 100644 index 00000000..b5acde7c --- /dev/null +++ b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/alloy2emf/AlloyRunCommandsPage.java @@ -0,0 +1,78 @@ +package eu.modelwriter.configuration.alloy2emf; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.layout.FillLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.List; + +import edu.mit.csail.sdg.alloy4.ConstList; +import edu.mit.csail.sdg.alloy4compiler.ast.Command; +import eu.modelwriter.configuration.alloy.trace.TraceException; + +public class AlloyRunCommandsPage extends AlloyToEMFWizardPage { + + private final ConstList commandList; + private List list; + + public AlloyRunCommandsPage(final ConstList commands) { + super("Run Commands"); + setTitle("Select Run Command"); + resetMessage(); + commandList = commands; + } + + private void resetMessage() { + setDescription("Select a command to start."); + } + + @Override + public void createControl(final Composite parent) { + final Composite container = new Composite(parent, SWT.NULL); + container.setLayout(new FillLayout(SWT.HORIZONTAL)); + + list = new org.eclipse.swt.widgets.List(container, SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL); + + for (final Command command : commandList) { + if (!command.check) + list.add("run " + command.label); + } + + list.add("Create new run"); + list.setSelection(0); + list.addSelectionListener(new SelectionListener() { + + @Override + public void widgetSelected(SelectionEvent e) { + setPageComplete(true); + resetMessage(); + } + + @Override + public void widgetDefaultSelected(SelectionEvent e) { + + } + }); + setControl(container); + } + + @Override + public boolean nextPressed() throws TraceException { + + getAlloyToEMF().setSelectedCommand(getSelection()); + BoundSelectionPage nextPage = (BoundSelectionPage) getNextPage(); + nextPage.setSelectedCommand(getSelection()); + + return true; + } + + public Command getSelection() { + // Create new run command + if (list.getSelectionIndex() == commandList.size()) + return null; + + return commandList.get(list.getSelectionIndex()); + } + +} diff --git a/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/alloy2emf/AlloySolutionSelectionPage.java b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/alloy2emf/AlloySolutionSelectionPage.java new file mode 100644 index 00000000..9cc9eba6 --- /dev/null +++ b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/alloy2emf/AlloySolutionSelectionPage.java @@ -0,0 +1,244 @@ +package eu.modelwriter.configuration.alloy2emf; + +import java.awt.Frame; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import javax.swing.JPanel; +import javax.swing.UIManager; +import javax.swing.UnsupportedLookAndFeelException; + +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.swt.SWT; +import org.eclipse.swt.awt.SWT_AWT; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; + +import edu.mit.csail.sdg.alloy4.Err; +import edu.mit.csail.sdg.alloy4compiler.translator.A4Solution; +import edu.mit.csail.sdg.alloy4viz.AlloyInstance; +import edu.mit.csail.sdg.alloy4viz.StaticInstanceReader; +import edu.mit.csail.sdg.alloy4viz.VizGraphPanel; +import edu.mit.csail.sdg.alloy4viz.VizState; +import eu.modelwriter.configuration.internal.AlloyUtilities; + +public class AlloySolutionSelectionPage extends AlloyToEMFWizardPage { + + private List solutions = new ArrayList(); + private int currentSolutionIndex = 0; + + private static VizState myState = null; + private static VizGraphPanel graph; + private static Frame frame; + private static File f = null; + public static Composite container; + private static String xmlFileLocation = + ".modelwriter alloy_example_output0.xml".replace(" ", System.getProperty("file.separator")); + + public AlloySolutionSelectionPage() { + this("AlloySolutionSelectionPage"); + } + + private static String getLocation() { + return ResourcesPlugin.getWorkspace().getRoot().getLocation() + "/" + xmlFileLocation; + } + + protected AlloySolutionSelectionPage(String pageName) { + super(pageName); + setTitle("Alloy Solutions"); + // setDescription("Select a Alloy solution to continue"); + } + + @Override + public void createControl(Composite parent) { + final Composite container = new Composite(parent, SWT.NULL); + container.setLayout(new GridLayout(1, false)); + container.setLayoutData(new GridData(SWT.FILL)); + + AlloySolutionSelectionPage.container = + new Composite(container, SWT.EMBEDDED | SWT.NO_BACKGROUND); + AlloySolutionSelectionPage.container + .setLayoutData(new GridData(GridData.FILL, GridData.FILL, true, true)); + AlloySolutionSelectionPage.frame = null; + AlloySolutionSelectionPage.f = null; + AlloySolutionSelectionPage.graph = null; + AlloySolutionSelectionPage.myState = null; + + final Composite buttonContainer = new Composite(container, SWT.NULL); + buttonContainer.setLayout(new GridLayout(3, false)); + buttonContainer.setLayoutData(new GridData(SWT.CENTER, SWT.TOP, true, false, 1, 1)); + + Button prevButton = new Button(buttonContainer, SWT.PUSH); + prevButton.setText("Previous Solution"); + prevButton.addSelectionListener(new SelectionListener() { + + @Override + public void widgetSelected(SelectionEvent e) { + if (currentSolutionIndex > 0) { + currentSolutionIndex = currentSolutionIndex - 1; + showSolution(); + } + } + + @Override + public void widgetDefaultSelected(SelectionEvent e) {} + }); + + Button nextButton = new Button(buttonContainer, SWT.PUSH); + nextButton.setText("Next Solution"); + nextButton.addSelectionListener(new SelectionListener() { + + @Override + public void widgetSelected(SelectionEvent e) { + try { + A4Solution nextSol = currentSolution().next(); + if (nextSol.satisfiable()) { + solutions.add(nextSol); + currentSolutionIndex = currentSolutionIndex < (solutions.size() - 1) + ? currentSolutionIndex + 1 : (solutions.size() - 1); + showSolution(); + } + } catch (Err e1) { + e1.printStackTrace(); + setErrorMessage("An error has occured while getting next solution."); + } + + } + + @Override + public void widgetDefaultSelected(SelectionEvent e) {} + }); + + setControl(container); + if (!solutions.isEmpty()) { + showSolution(); + } + } + + private void showViz() { + if (AlloySolutionSelectionPage.container == null) { + return; + } + + if (!AlloyUtilities.isExists()) { + if (AlloySolutionSelectionPage.frame != null) { + if (AlloySolutionSelectionPage.frame.getComponentCount() > 0) { + AlloySolutionSelectionPage.frame.removeAll(); + } + AlloySolutionSelectionPage.frame.add(new JPanel()); + } else if (AlloySolutionSelectionPage.frame == null) { + AlloySolutionSelectionPage.frame = SWT_AWT.new_Frame(AlloySolutionSelectionPage.container); + AlloySolutionSelectionPage.frame.add(new JPanel()); + } + return; + } + AlloySolutionSelectionPage.f = new File(getLocation()); + try { + if (!AlloySolutionSelectionPage.f.exists()) { + throw new IOException("File " + getLocation() + " does not exist."); + } + final AlloyInstance instance = + StaticInstanceReader.parseInstance(AlloySolutionSelectionPage.f); + + AlloyUtilities.setAllImpactsAndChanges(instance); + + AlloySolutionSelectionPage.myState = new VizState(instance); + + // FE + AlloySolutionSelectionPage.myState.mergeArrows.put(null, false); + + if (AlloySolutionSelectionPage.frame == null) { + AlloySolutionSelectionPage.frame = SWT_AWT.new_Frame(AlloySolutionSelectionPage.container); + } + + if (AlloySolutionSelectionPage.graph != null + && AlloySolutionSelectionPage.frame.getComponent(0) != null) { + AlloySolutionSelectionPage.frame.remove(AlloySolutionSelectionPage.graph); + } + + try { + /* + * TODO BUG + * + * A Fatal Error occurs while setting GTK look and feel on Ubuntu 16.04 + * (com.sun.java.swing.plaf.gtk.GTKLookAndFeel). + * + */ + final String LaF = UIManager.getSystemLookAndFeelClassName(); + if ("com.sun.java.swing.plaf.gtk.GTKLookAndFeel".equals(LaF)) { + UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName()); + } else { + UIManager.setLookAndFeel(LaF); + } + } catch (ClassNotFoundException | InstantiationException | IllegalAccessException + | UnsupportedLookAndFeelException e1) { + e1.printStackTrace(); + } + AlloySolutionSelectionPage.graph = + new VizGraphPanel(AlloySolutionSelectionPage.myState, false); + AlloySolutionSelectionPage.frame.removeAll(); + AlloySolutionSelectionPage.frame.add(AlloySolutionSelectionPage.graph); + AlloySolutionSelectionPage.frame.setVisible(true); + AlloySolutionSelectionPage.frame.setAlwaysOnTop(true); + AlloySolutionSelectionPage.graph.alloyGetViewer().alloyRepaint(); + + } catch (final Err | IOException e) { + e.printStackTrace(); + setErrorMessage("An error has occured while Alloy visualization."); + } + } + + @Override + public void dispose() { + container = null; + super.dispose(); + } + + public void setFirstSolution(A4Solution firstSolution) { + if (solutions.isEmpty()) { + solutions.add(firstSolution); + currentSolutionIndex = 0; + showSolution(); + } else { + solutions.clear(); + solutions.add(firstSolution); + currentSolutionIndex = 0; + showSolution(); + } + } + + public A4Solution currentSolution() { + return solutions.get(currentSolutionIndex); + } + + public void showSolution() { + try { + setDescription("Current solution: " + currentSolutionIndex + "\n"); + currentSolution().writeXML(getLocation()); + showViz(); + } catch (final Err e) { + e.printStackTrace(); + } + } + + @Override + public boolean backPressed() { + solutions.clear(); + currentSolutionIndex = 0; + return true; + } + + @Override + public boolean nextPressed() throws Exception { + AlloyToEMFWizard wizard = (AlloyToEMFWizard) getWizard(); + wizard.getAlloyToEmf().setSolution(currentSolution()); + return super.nextPressed(); + } + +} diff --git a/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/alloy2emf/AlloyToEMF.java b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/alloy2emf/AlloyToEMF.java new file mode 100644 index 00000000..a5d3d561 --- /dev/null +++ b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/alloy2emf/AlloyToEMF.java @@ -0,0 +1,449 @@ +package eu.modelwriter.configuration.alloy2emf; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Scanner; +import java.util.Set; +import java.util.regex.Pattern; + +import org.eclipse.core.resources.IFile; +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EPackage; +import org.eclipse.emf.ecore.EReference; +import org.eclipse.jface.dialogs.MessageDialog; + +import edu.mit.csail.sdg.alloy4.ConstList; +import edu.mit.csail.sdg.alloy4.Err; +import edu.mit.csail.sdg.alloy4.SafeList; +import edu.mit.csail.sdg.alloy4compiler.ast.Command; +import edu.mit.csail.sdg.alloy4compiler.ast.ExprVar; +import edu.mit.csail.sdg.alloy4compiler.ast.Sig; +import edu.mit.csail.sdg.alloy4compiler.ast.Sig.Field; +import edu.mit.csail.sdg.alloy4compiler.translator.A4Solution; +import edu.mit.csail.sdg.alloy4compiler.translator.A4Tuple; +import eu.modelwriter.configuration.alloy.trace.LoadItem; +import eu.modelwriter.configuration.alloy.trace.RelationTrace; +import eu.modelwriter.configuration.alloy.trace.SigTrace; +import eu.modelwriter.configuration.alloy.trace.TraceException; +import eu.modelwriter.configuration.alloy.trace.TraceManager; +import eu.modelwriter.configuration.generation.AbstractGeneration; +import eu.modelwriter.configuration.generation.GenerationWizardDialog; +import eu.modelwriter.configuration.internal.AlloyExecuter; +import eu.modelwriter.configuration.internal.EcoreUtilities; +import eu.modelwriter.configuration.internal.Utilities; +import eu.modelwriter.configuration.synthesis.AutomatedTraceCreator; + + +class AlloyToEMFItem { + String saveLocation = null; + String containerClassName; + EObject container; + EObject modelRoot; +} + + +public class AlloyToEMF extends AbstractGeneration { + + private final String alloyFilePath; + private A4Solution solution = null; + private Command selectedCommand = null; + private AlloyToEMFWizard alloyToEMFWizard; + private GenerationWizardDialog dialog; + private AlloyExecuter alloyExecuter; + + private final HashMap alias2Item = new HashMap<>(); + private final HashMap atom2EClass = new HashMap<>(); + + public AlloyToEMF(String alloyFilePath) { + this.alloyFilePath = alloyFilePath; + } + + /** + * Starts the process + * + * @throws TraceException + * + */ + public void start() throws TraceException { + if (!TraceManager.get().hasTraces()) + throw new TraceException("No trace has been found."); + + for (LoadItem load : TraceManager.get().getLoads()) { + if (load.getModelRoot() == null) { + throw new TraceException("There is no loaded model for alias: " + load.getAlias()); + } + AlloyToEMFItem alloyToEMFItem = new AlloyToEMFItem(); + alloyToEMFItem.modelRoot = load.getModelRoot(); + alias2Item.put(load.getAlias(), alloyToEMFItem); + } + + try { + alloyExecuter = new AlloyExecuter(alloyFilePath); + } catch (Err e) { + throw new TraceException(e.getMessage()); + } + + alloyToEMFWizard = new AlloyToEMFWizard(this, "Alloy To EMF"); + dialog = new GenerationWizardDialog(null, alloyToEMFWizard); + dialog.open(); + } + + /** + * + * @return list of executable commands + */ + public ConstList getRunCommands() { + return alloyExecuter.getRunCommands(); + } + + /** + * Disposes wizard + */ + public void closeWizard() { + alloyToEMFWizard.performCancel(); + } + + /** + * + * @param emfAlias alias to find emf model + * @param selectedClass class name to create a instance + */ + public void setSelectedEClass(String emfAlias, String selectedClass) { + alias2Item.get(emfAlias).containerClassName = selectedClass; + } + + /** + * + * @param emfAlias + * @return list of EClass names + */ + public List getEClasses(String emfAlias) { + return EcoreUtilities.getAllEClassNames(alias2Item.get(emfAlias).modelRoot); + } + + public Set getAliases() { + return alias2Item.keySet(); + } + + public SafeList getCurrentSigs() { + return alloyExecuter.getWorld().getAllSigs(); + } + + public String getAlloyFilePath() { + return alloyFilePath; + } + + /** + * Real job + * + * @throws TraceException + */ + public boolean run() throws TraceException { + setState(RUNNING); + atom2EClass.clear(); + + for (String alias : alias2Item.keySet()) { + EObject eObject = alias2Item.get(alias).modelRoot; + if (eObject instanceof EPackage) { + EPackage ePackage = (EPackage) eObject; + final String selectedClass = alias2Item.get(alias).containerClassName; + EClass refClass = EcoreUtilities.findEClass(alias2Item.get(alias).modelRoot, selectedClass); + if (refClass != null) { + EObject container = ePackage.getEFactoryInstance().create(refClass); + alias2Item.get(alias).container = container; + } + } + } + createEClassesFromAtoms(); + createEReferencesFromSigs(); + setState(FINISHED); + return true; + } + + private void createEReferencesFromSigs() throws TraceException { + for (Sig sig : solution.getAllReachableSigs()) { + for (Field field : sig.getFields()) { + final String relName = field.label; + SigTrace sigTrace = TraceManager.get().getSigTraceByType(sig.label.replace("this/", "")); + RelationTrace relTrace = + TraceManager.get().getRelationTrace2(sigTrace.getClassName(), relName); + if (relTrace != null) { + String refName = relTrace.getReferenceName(); + for (A4Tuple a4Tuple : solution.eval(field)) { + String fromAtom = a4Tuple.atom(0); + String toAtom = a4Tuple.atom(1); + createEReferenceByAtoms(refName, fromAtom, toAtom); + } + } else { + closeWizard(); + throw new TraceException("No trace has been found for the relation: " + relName); + } + } + } + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + private void createEReferenceByAtoms(String refName, String fromAtom, String toAtom) + throws TraceException { + EObject sourceAtom = atom2EClass.get(fromAtom); + EObject targetAtom = atom2EClass.get(toAtom); + for (EReference eReference : sourceAtom.eClass().getEAllReferences()) { + if (eReference.getName().equals(refName)) { + if (eReference.isMany()) { + ((List) sourceAtom.eGet(eReference)).add(targetAtom); + } else { + sourceAtom.eSet(eReference, targetAtom); + } + break; + } + } + } + + private void createEClassesFromAtoms() throws TraceException { + Iterator it = solution.getAllAtoms().iterator(); + while (it.hasNext()) { + ExprVar atom = it.next(); + String sigName = atom.label.substring(0, atom.label.indexOf("$")); + SigTrace sigTrace = TraceManager.get().getSigTraceByType(sigName); + if (sigTrace != null) { + EClass refClass = EcoreUtilities.findEClass(alias2Item.get(sigTrace.getAlias()).modelRoot, + sigTrace.getClassName()); + EObject atomObject = refClass.getEPackage().getEFactoryInstance().create(refClass); + EcoreUtilities.eSetAttributeByName(atomObject, "name", atom.label); + putIntoContainer(alias2Item.get(sigTrace.getAlias()).container, atomObject); + atom2EClass.put(atom.label, atomObject); + } else { + closeWizard(); + throw new TraceException("There is no trace for the sig: " + sigName); + } + } + } + + /** + * Puts given dynamic EObject to corresponding container + * + * @param container + * @param eObject + */ + @SuppressWarnings({"unchecked", "rawtypes"}) + public static void putIntoContainer(EObject container, EObject eObject) { + for (EReference eReference : container.eClass().getEAllReferences()) { + if (eObject.eClass().getName().equals(eReference.getEReferenceType().getName())) { + if (eReference.isMany()) + ((List) container.eGet(eReference)).add(eObject); + else + container.eSet(eReference, eObject); + break; + } + } + } + + public void setSolution(A4Solution solution) { + this.solution = solution; + } + + public A4Solution getSolution() { + return solution; + } + + public void onException(TraceException e) { + final MessageDialog warningdialog = new MessageDialog(null, "Alloy To EMF", null, + e.getMessage(), MessageDialog.WARNING, new String[] {"OK"}, 0); + warningdialog.open(); + } + + public void setSaveLocation(String alias, String selectedPath) { + alias2Item.get(alias).saveLocation = selectedPath; + } + + public boolean isAllLocationsSelected() { + for (AlloyToEMFItem alloyToEMFItem : alias2Item.values()) { + if (alloyToEMFItem.saveLocation == null) + return false; + } + return true; + } + + public boolean performFinish(boolean appendChecked, boolean startATC) { + for (AlloyToEMFItem item : alias2Item.values()) { + EcoreUtilities.saveResource(item.container, item.saveLocation); + } + if (appendChecked) { + appendNewInstances(); + } + if (startATC) { + hideWizard(); + runATC(); + } + return true; + } + + public void runATC() { + try { + // TODO: Load the spec + TraceManager.get().loadSpec(alloyFilePath); + AutomatedTraceCreator automatedTraceCreator = new AutomatedTraceCreator(); + automatedTraceCreator.setUser(true); + automatedTraceCreator.schedule(); + } catch (TraceException e) { + // TODO: Delete created markers + } + } + + private void hideWizard() { + dialog.getShell().setAlpha(0); + } + + private void appendNewInstances() { + Pattern instanceRegex = Pattern.compile("^-- loadinstance@", Pattern.CASE_INSENSITIVE); + try { + File f = new File(alloyFilePath); + List fileContent = + new ArrayList<>(Files.readAllLines(Paths.get(f.toURI()), StandardCharsets.UTF_8)); + for (String alias : alias2Item.keySet()) { + Pattern aliasRegex = Pattern.compile("^-- loadalias@" + alias, Pattern.CASE_INSENSITIVE); + for (int i = 0; i < fileContent.size(); i++) { + String line = fileContent.get(i); + if (aliasRegex.matcher(line.trim()).find()) { + IFile iFile = Utilities.getIFileFromPath(alias2Item.get(alias).saveLocation); + String path = + iFile == null ? alias2Item.get(alias).saveLocation : iFile.getFullPath().toString(); + if (instanceRegex.matcher(fileContent.get(i + 2).trim()).find()) + fileContent.remove(i + 2); + fileContent.add(i + 2, "-- loadInstance@" + path); + } + } + } + Files.write(Paths.get(f.toURI()), fileContent, StandardCharsets.UTF_8); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public void resetRun() { + atom2EClass.clear(); + setState(NOT_STARTED); + } + + public boolean executeCommand(Command selection) { + try { + A4Solution sol = alloyExecuter.executeCommand(selection); + if (sol != null) { + solution = sol; + return true; + } + } catch (Err e) { + e.printStackTrace(); + } + return false; + } + + @Override + public void onException(Exception e) { + + } + + public EObject getEMFModel(String alias) { + return alias2Item.get(alias).modelRoot; + } + + public void setSelectedCommand(Command selection) { + selectedCommand = selection; + } + + public Command getSelectedCommand() { + return selectedCommand; + } + + public List getPredLines(String label) { + List predLines = new ArrayList<>(); + Scanner scanner; + try { + scanner = new Scanner(new File(alloyFilePath)); + while (scanner.hasNextLine()) { + String line = scanner.nextLine(); + if (line.trim().equals("pred " + label + " {")) { + while (!line.contains("}") && scanner.hasNextLine()) { + line = scanner.nextLine(); + predLines.add(line.trim()); + } + } + } + scanner.close(); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } + return predLines; + } + + public boolean replacePred(String filePath, String predName, String predAndRun) + throws IOException { + File f = new File(filePath); + // FIXME possible charset problem + List fileContent = + new ArrayList<>(Files.readAllLines(Paths.get(f.toURI()), StandardCharsets.UTF_8)); + String[] lines = predAndRun.split(BoundSelectionPage.NEW_LINE); + int predIndex = -1; + + for (int i = 0; i < fileContent.size(); i++) { + if (fileContent.get(i).equals("//" + predName)) { + predIndex = i; + break; + } + } + if (predIndex == -1) + return false; + + int x = predIndex; + while (!fileContent.get(x).equals("//end")) + fileContent.remove(x); + fileContent.remove(x); + + for (int i = 0; i < lines.length; i++) { + fileContent.add(predIndex++, lines[i]); + } + Files.write(Paths.get(f.toURI()), fileContent, StandardCharsets.UTF_8); + return true; + } + + public A4Solution executePred(String predName, String predAndRun, boolean saveToFile) { + File tempFile = null; + try { + tempFile = File.createTempFile("temp_tarski_pred", ".mw"); + Utilities.copyFileContent(alloyFilePath, tempFile.getAbsolutePath()); + if (!replacePred(tempFile.getAbsolutePath(), predName, predAndRun)) + Utilities.appendToFile(tempFile.getAbsolutePath(), predAndRun); + alloyExecuter.parse(tempFile.getAbsolutePath()); + + if (saveToFile) + Utilities.copyFileContent(tempFile.getAbsolutePath(), alloyFilePath); + + for (Command command : alloyExecuter.getRunCommands()) { + if (command.label.equals(predName)) { + A4Solution sol = alloyExecuter.executeCommand(command); + if (sol != null) { + setSolution(sol); + return sol; + } + } + } + } catch (IOException e) { + e.printStackTrace(); + } catch (Err e) { + e.printStackTrace(); + } finally { + if (tempFile != null && tempFile.exists()) + tempFile.delete(); + } + return null; + } +} diff --git a/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/alloy2emf/AlloyToEMFWizard.java b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/alloy2emf/AlloyToEMFWizard.java new file mode 100644 index 00000000..e2ec8dda --- /dev/null +++ b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/alloy2emf/AlloyToEMFWizard.java @@ -0,0 +1,74 @@ +package eu.modelwriter.configuration.alloy2emf; + +import org.eclipse.jface.wizard.IWizardPage; + +import eu.modelwriter.configuration.alloy.trace.TraceException; +import eu.modelwriter.configuration.generation.AbstractGeneration; +import eu.modelwriter.configuration.generation.GenerationWizard; + +public class AlloyToEMFWizard extends GenerationWizard { + + private AlloyRunCommandsPage alloyRunCommandsPage; + private FinishPage finishPage; + + public AlloyToEMFWizard(AbstractGeneration converter, String title) { + super(converter, title); + } + + public AlloyToEMF getAlloyToEmf() { + return getConverter(); + } + + @Override + public AlloyToEMF getConverter() { + return (AlloyToEMF) super.getConverter(); + } + + @Override + public void addPages() { + super.addPages(); + alloyRunCommandsPage = new AlloyRunCommandsPage(getAlloyToEmf().getRunCommands()); + addPage(alloyRunCommandsPage); + for (String alias : getAlloyToEmf().getAliases()) { + addPage(new BoundSelectionPage(getAlloyToEmf().getEMFModel(alias), + getAlloyToEmf().getCurrentSigs())); + } + addPage(new AlloySolutionSelectionPage()); + for (String alias : getAlloyToEmf().getAliases()) { + addPage(new EMFContainerSelectionPage(alias, getAlloyToEmf().getEClasses(alias))); + } + finishPage = new FinishPage(); + addPage(finishPage); + finishPage.updateConverter(); + } + + @Override + public boolean performFinish() { + return getAlloyToEmf().performFinish(finishPage.appendToFileChecked(), finishPage.startATC()); + } + + @Override + public boolean canFinish() { + IWizardPage page = getContainer().getCurrentPage(); + boolean canFinish = false; + boolean isLastPage = page.getName().equals("lastpage"); + if (isLastPage && getAlloyToEmf().getState() == AbstractGeneration.NOT_STARTED) { + FinishPage finishPage = (FinishPage) page; + try { + getAlloyToEmf().run(); + finishPage.success(); + canFinish = getAlloyToEmf().isAllLocationsSelected(); + } catch (TraceException e) { + canFinish = false; + finishPage.fail(); + getAlloyToEmf().onException(e); + } + } else { + if (isLastPage && getAlloyToEmf().getState() == AbstractGeneration.FINISHED) { + canFinish = getAlloyToEmf().isAllLocationsSelected(); + } + } + return canFinish; + } + +} diff --git a/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/alloy2emf/AlloyToEMFWizardPage.java b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/alloy2emf/AlloyToEMFWizardPage.java new file mode 100644 index 00000000..d0deb41c --- /dev/null +++ b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/alloy2emf/AlloyToEMFWizardPage.java @@ -0,0 +1,25 @@ +package eu.modelwriter.configuration.alloy2emf; + +import org.eclipse.jface.wizard.IWizard; + +import eu.modelwriter.configuration.generation.GenerationWizardPage; + +public class AlloyToEMFWizardPage extends GenerationWizardPage { + + protected AlloyToEMF alloyToEMF = null; + + protected AlloyToEMFWizardPage(String pageName) { + super(pageName); + } + + @Override + public void setWizard(IWizard newWizard) { + super.setWizard(newWizard); + alloyToEMF = (AlloyToEMF) getConverter(); + } + + public AlloyToEMF getAlloyToEMF() { + return alloyToEMF; + } + +} diff --git a/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/alloy2emf/BoundSelectionPage.java b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/alloy2emf/BoundSelectionPage.java new file mode 100644 index 00000000..1966df85 --- /dev/null +++ b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/alloy2emf/BoundSelectionPage.java @@ -0,0 +1,571 @@ +package eu.modelwriter.configuration.alloy2emf; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.Container; +import java.awt.Frame; +import java.awt.GridLayout; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JSpinner; +import javax.swing.SpinnerNumberModel; +import javax.swing.UIManager; +import javax.swing.UnsupportedLookAndFeelException; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EReference; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.swt.SWT; +import org.eclipse.swt.awt.SWT_AWT; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.layout.FillLayout; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Text; + +import edu.mit.csail.sdg.alloy4.SafeList; +import edu.mit.csail.sdg.alloy4compiler.ast.Command; +import edu.mit.csail.sdg.alloy4compiler.ast.Sig; +import edu.mit.csail.sdg.alloy4compiler.translator.A4Solution; +import eu.modelwriter.configuration.internal.EcoreUtilities; + +public class BoundSelectionPage extends AlloyToEMFWizardPage { + + class BoundItem { + String sigName; + private int initLower, lower; + private int initUpper, upper; + EClass eClass; + private JSpinner lowerSpinner; + private JSpinner upperSpinner; + + public BoundItem(String sigName, int initLower, int initUpper) { + this.sigName = sigName; + this.initLower = initLower; + this.initUpper = initUpper; + setLower(initLower); + setUpper(initUpper); + } + + public EClass getEClass() { + return eClass; + } + + public void setEClass(EClass eClass) { + this.eClass = eClass; + } + + public int getLower() { + return lower;// lowerSpinner == null ? lower : (int) lowerSpinner.getValue(); + } + + public int getUpper() { + return upper; // upperSpinner == null ? upper : (int) upperSpinner.getValue(); + } + + public int getSpinnerLower() { + return lowerSpinner == null ? lower : (int) lowerSpinner.getValue(); + } + + public int getSpinnerUpper() { + return upperSpinner == null ? upper : (int) upperSpinner.getValue(); + } + + + public void setLower(int lower) { + this.lower = lower; + } + + void setUpper(int upper) { + this.upper = upper; + } + + public void reset() { + setUpper(initUpper); + setLower(initLower); + } + + JSpinner getLowerSpinner() { + return lowerSpinner; + } + + void setLowerSpinner(JSpinner lowerModel) { + lowerSpinner = lowerModel; + lowerModel.setValue(lower); + } + + JSpinner getUpperSpinner() { + return upperSpinner; + } + + void setUpperSpinner(JSpinner upperModel) { + upperSpinner = upperModel; + upperModel.setValue(upper); + } + + void updateValues() { + upperSpinner.setValue(upper); + lowerSpinner.setValue(lower); + } + } + + public static final String NEW_LINE = System.getProperty("line.separator"); + private static final int DEFAULT_UPPER = 3; + private static final String PRED_FORMAT = "\t#%s >= %d && #%s <= %d" + NEW_LINE; + private static final String BOUND_PRED_REGEX = "#(.*) >= (\\d*) && #(.*) <= (\\d*)"; + + private EObject modelRoot; + private SafeList sigs; + private boolean analyzeEnabled; + private boolean saveEnabled; + private int intPower = 1; + private Text predText; + private Button saveCheck; + private Map sig2item = new HashMap<>(); + private Map traceCache = new HashMap<>(); + private Composite buttonContainer; + private Text scopeSize; + private Button scopeCheck; + private Composite boundContainer; + private Frame frame; + private JPanel panel; + + protected BoundSelectionPage(EObject modelRoot, SafeList sigs) { + super("SelectEMFContainer"); + this.modelRoot = modelRoot; + this.sigs = sigs; + setTitle("Bound Selection"); + setDescription("Enter sig bounds"); + initSigBounds(); + } + + private void initSigBounds() { + for (Sig sig : sigs) { + String sigName = sig.label.replace("this/", ""); + int lower = 0, upper = DEFAULT_UPPER; + if (sig.isOne != null) { + lower = upper = 1; + } else if (sig.isLone != null) { + lower = 0; + upper = 1; + } else if (sig.isSome != null) { + lower = 1; + upper = DEFAULT_UPPER; + } + EClass sigClass = EcoreUtilities.findEClass(modelRoot, sigName); + if (sigClass != null && !sigClass.isAbstract()) { + BoundItem item = new BoundItem(sigName, lower, upper); + item.setEClass(sigClass); + sig2item.put(sigName, item); + traceCache.put(sigClass.getName(), sigName); + } + } + } + + @Override + public void createControl(Composite parent) { + final Composite topContainer = new Composite(parent, SWT.NONE); + topContainer.setLayout(new org.eclipse.swt.layout.GridLayout(1, false)); + // Checkbox: save this pred + saveCheck = new Button(topContainer, SWT.CHECK); + saveCheck.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false)); + saveCheck.setText("Save this bounds as"); + saveCheck.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + saveEnabled = saveCheck.getSelection(); + predText.setEnabled(saveEnabled); + } + }); + saveCheck.setSelection(false); + + // Textbox: pred name + predText = new Text(topContainer, SWT.BORDER); + predText.setMessage("Enter a name"); + predText.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + predText.setEnabled(false); + predText.addModifyListener(new ModifyListener() { + + @Override + public void modifyText(ModifyEvent e) { + setPageComplete(true); + } + }); + scopeCheck = new Button(topContainer, SWT.CHECK); + scopeCheck.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false)); + scopeCheck.setText("A generic model for number of elements:"); + scopeCheck.setSelection(false); + scopeSize = new Text(topContainer, SWT.BORDER); + scopeSize.setMessage("Enter number of elements"); + scopeSize.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + scopeSize.setEnabled(false); + + // Append the grid + makeSigTable(topContainer); + makeBottomButtons(topContainer); + + scopeCheck.addSelectionListener(new SelectionAdapter() { + + @Override + public void widgetSelected(SelectionEvent e) { + scopeSize.setEnabled(scopeCheck.getSelection()); + boolean enabled = !scopeCheck.getSelection(); + setEnabledRecursive(panel, enabled); + setEnabledRecursive(buttonContainer, enabled); + } + + }); + setControl(topContainer); + } + + public static void setEnabledRecursive(final Composite composite, final boolean enabled) { + Control[] children = composite.getChildren(); + for (int i = 0; i < children.length; i++) { + if (children[i] instanceof Composite) { + setEnabledRecursive((Composite) children[i], enabled); + } else { + children[i].setEnabled(enabled); + } + } + composite.setEnabled(enabled); + } + + public static void setEnabledRecursive(final Container container, final boolean enabled) { + Component[] children = container.getComponents(); + for (int i = 0; i < children.length; i++) { + if (children[i] instanceof Container) { + setEnabledRecursive((Container) children[i], enabled); + } else { + children[i].setEnabled(enabled); + } + } + container.setEnabled(enabled); + } + + private void makeBottomButtons(Composite container) { + buttonContainer = new Composite(container, SWT.NONE); + buttonContainer.setLayout(new org.eclipse.swt.layout.GridLayout(3, false)); + Button restoreButton = new Button(buttonContainer, SWT.PUSH); + // restoreButton.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false)); + restoreButton.setText("Restore Lower Bounds"); + restoreButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + for (BoundItem boundItem : sig2item.values()) { + boundItem.setLower(boundItem.initLower); + boundItem.setUpper(boundItem.initUpper); + boundItem.updateValues(); + } + } + }); + + Button incButton = new Button(buttonContainer, SWT.PUSH); + // incButton.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false)); + incButton.setText("Increment Lower Bounds"); + incButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + for (BoundItem boundItem : sig2item.values()) { + boundItem.setLower(boundItem.getLower() + 1); + boundItem.updateValues(); + } + } + }); + + Button decButton = new Button(buttonContainer, SWT.PUSH); + // decButton.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false)); + decButton.setText("Decrement Lower Bounds"); + decButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + for (BoundItem boundItem : sig2item.values()) { + boundItem.setLower(boundItem.getLower() - 1); + boundItem.updateValues(); + } + } + }); + Button restoreUpperButton = new Button(buttonContainer, SWT.PUSH); + // restoreButton.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false)); + restoreUpperButton.setText("Restore Upper Bounds"); + restoreUpperButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + for (BoundItem boundItem : sig2item.values()) { + boundItem.setLower(boundItem.initLower); + boundItem.setUpper(boundItem.initUpper); + boundItem.updateValues(); + } + } + }); + + Button incUpperButton = new Button(buttonContainer, SWT.PUSH); + // incButton.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false)); + incUpperButton.setText("Increment Upper Bounds"); + incUpperButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + for (BoundItem boundItem : sig2item.values()) { + boundItem.setUpper(boundItem.getUpper() + 1); + boundItem.updateValues(); + } + } + }); + + Button decUpperButton = new Button(buttonContainer, SWT.PUSH); + // decButton.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false)); + decUpperButton.setText("Decrement Upper Bounds"); + decUpperButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + for (BoundItem boundItem : sig2item.values()) { + boundItem.setUpper(boundItem.getUpper() - 1); + boundItem.updateValues(); + } + } + }); + } + + private void makeSigTable(final Composite topContainer) { + boundContainer = new Composite(topContainer, SWT.EMBEDDED | SWT.NO_BACKGROUND); + boundContainer.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + boundContainer.setLayout(new FillLayout()); + setTheme(); + frame = SWT_AWT.new_Frame(boundContainer); + frame.setBackground(null); + GridLayout layout = new GridLayout(0, 3); + panel = new JPanel(layout); + JLabel labelSig = new JLabel("Sig"); + JLabel labelLower = new JLabel("Lower Bound"); + JLabel labelUpper = new JLabel("Upper Bound"); + panel.add(labelSig); + panel.add(labelLower); + panel.add(labelUpper); + for (Entry entry : sig2item.entrySet()) { + BoundItem boundItem = entry.getValue(); + String sigName = entry.getKey(); + JLabel sigLabel = new JLabel(sigName); + SpinnerNumberModel lowerModel = + new SpinnerNumberModel(boundItem.getLower(), boundItem.initLower, 1000, 1); + SpinnerNumberModel upperModel = + new SpinnerNumberModel(boundItem.getUpper(), boundItem.initLower, 1000, 1); + JSpinner upper = new JSpinner(upperModel); + JSpinner lower = new JSpinner(lowerModel); + panel.add(sigLabel); + panel.add(lower); + panel.add(upper); + lowerModel.addChangeListener(new ChangeListener() { + int prevValue = boundItem.initLower; + + @Override + public void stateChanged(ChangeEvent e) { + int l = (int) lower.getValue(); + if (l > (int) upper.getValue()) { + lowerModel.setValue(--l); + return; + } + if (analyzeEnabled) + analyzeBounds(sigName, l - prevValue, true); + prevValue = l; + } + }); + upperModel.addChangeListener(new ChangeListener() { + int prevValue = boundItem.initUpper; + + @Override + public void stateChanged(ChangeEvent e) { + int u = (int) upper.getValue(); + if (u < (int) lower.getValue()) { + upperModel.setValue(++u); + return; + } + updateInt(u); + if (analyzeEnabled) + analyzeBounds(sigName, u - prevValue, false); + prevValue = u; + } + }); + boundItem.setLowerSpinner(lower); + boundItem.setUpperSpinner(upper); + } + frame.add(panel, BorderLayout.CENTER); + } + + private void analyzeBounds(String sigName, int inc, boolean lowerChanged) { + BoundItem item = sig2item.get(sigName); + if (item == null || inc == 0) + return; + + for (EReference eRef : item.getEClass().getEReferences()) { + String relSigName = traceCache.get(eRef.getEReferenceType().getName()); + BoundItem relBoundItem = sig2item.get(relSigName); + int newUpper = relBoundItem.getUpper(); + int newLower = relBoundItem.getLower(); + + if (lowerChanged && eRef.getLowerBound() > 0) { + int change = eRef.getLowerBound() * inc; + newLower = relBoundItem.getLower() + change; + // TODO check this + // if (relBoundItem.getLower() < (eRef.getLowerBound() * item.getSpinnerLower())) { + // if (eRef.isContainment()) { + // relBoundItem.setUpper(item.getSpinnerUpper() * inc + // * (eRef.getUpperBound() < 1 ? 1 : eRef.getUpperBound())); + // relBoundItem.setLower(item.getSpinnerLower() * change); + // } else { + // relBoundItem.setUpper(relBoundItem.getUpper() + change); + // relBoundItem.setLower(relBoundItem.getLower() + change); + // } + // } + } else if (!lowerChanged) { + int change = eRef.getUpperBound() < 1 ? 1 : eRef.getUpperBound() * inc; + newUpper = relBoundItem.getUpper() + change; + // relBoundItem.setUpper(relBoundItem.getUpper() + change); + } + relBoundItem.setUpper(newUpper); + relBoundItem.setLower(newLower); + relBoundItem.updateValues(); + } + } + + private void updateInt(int upperBound) { + if (upperBound > Math.pow(2, intPower) - 1) { + intPower = 32 - Integer.numberOfLeadingZeros(upperBound - 1); + } + } + + private void setTheme() { + try { + /* + * TODO BUG + * + * A Fatal Error occurs while setting GTK look and feel on Ubuntu 16.04 + * (com.sun.java.swing.plaf.gtk.GTKLookAndFeel). + * + */ + final String LaF = UIManager.getSystemLookAndFeelClassName(); + if ("com.sun.java.swing.plaf.gtk.GTKLookAndFeel".equals(LaF)) { + UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName()); + } else { + UIManager.setLookAndFeel(LaF); + } + } catch (ClassNotFoundException | InstantiationException | IllegalAccessException + | UnsupportedLookAndFeelException e1) { + e1.printStackTrace(); + } + } + + public void setSelectedCommand(Command selectedCommand) { + final Pattern boundPredPattern = Pattern.compile(BOUND_PRED_REGEX, Pattern.CASE_INSENSITIVE); + predText.setText(""); + if (selectedCommand == null) + return; + + predText.setText(selectedCommand.label); + // saveCheck.setSelection(true); + // saveEnabled = true; + List pred = getAlloyToEMF().getPredLines(selectedCommand.label); + for (String line : pred) { + Matcher matcher = boundPredPattern.matcher(line); + if (matcher.matches() && matcher.group(1).equals(matcher.group(3))) { + int lower = -1, upper = -1; + String sigName = matcher.group(1); + lower = Integer.parseInt(matcher.group(2)); + upper = Integer.parseInt(matcher.group(4)); + BoundItem item = sig2item.get(sigName); + if (item != null) { + item.setLower(lower); + item.setUpper(upper); + } + } + } + } + + public String generatePredAndRun(String predName) { + StringBuilder sb = new StringBuilder(); + sb.append("//" + predName + NEW_LINE); + sb.append("pred " + predName + " {" + NEW_LINE); + String scope = ""; + if (!scopeCheck.getSelection()) { + for (Entry entry : sig2item.entrySet()) { + BoundItem boundItem = entry.getValue(); + if (boundItem.getLower() != boundItem.initLower + || boundItem.getUpper() != boundItem.initUpper) { + sb.append(String.format(PRED_FORMAT, boundItem.sigName, boundItem.getLower(), + boundItem.sigName, boundItem.getUpper())); + if (boundItem.getUpper() > DEFAULT_UPPER) { + if (!scope.isEmpty()) + scope += ", "; + scope += boundItem.getUpper() + " " + boundItem.sigName; + } + } + } + } + sb.append("}" + NEW_LINE); + if (!scopeCheck.getSelection()) + sb.append("run " + predName + " for " + DEFAULT_UPPER); + else + sb.append("run " + predName + " for " + Integer.parseInt(scopeSize.getText())); + + if (!scope.isEmpty()) { + sb.append(" but " + scope); + if (intPower > 1) + sb.append(" ," + intPower + " Int"); + } + sb.append(NEW_LINE + "//end" + NEW_LINE); + return sb.toString(); + } + + @Override + public boolean backPressed() { + for (Entry entry : sig2item.entrySet()) { + entry.getValue().reset(); + } + return super.backPressed(); + } + + @Override + public boolean nextPressed() throws Exception { + // if save enabled, make sure pred has a name that not starts with number + if (saveEnabled && (Character.isDigit(getPredName().charAt(0)) || getPredName().isEmpty())) { + predText.forceFocus(); + return false; + } + + String predName = getPredName(); + if (!saveEnabled) + predName = "temp_pred_name"; + + A4Solution solution = + getAlloyToEMF().executePred(predName, generatePredAndRun(predName), saveEnabled); + if (solution != null) { + AlloySolutionSelectionPage nextPage = (AlloySolutionSelectionPage) getNextPage(); + nextPage.setFirstSolution(solution); + return true; + } else { + MessageDialog messageDialog = new MessageDialog(getShell(), "Error", null, + "No instance found, try changing the bounds.", MessageDialog.INFORMATION, 0, + new String[] {"OK"}); + messageDialog.open(); + return false; + } + } + + private String getPredName() { + return predText.getText(); + } + +} diff --git a/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/alloy2emf/EMFContainerSelectionPage.java b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/alloy2emf/EMFContainerSelectionPage.java new file mode 100644 index 00000000..ca86727b --- /dev/null +++ b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/alloy2emf/EMFContainerSelectionPage.java @@ -0,0 +1,72 @@ +package eu.modelwriter.configuration.alloy2emf; + +import java.util.List; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.layout.FillLayout; +import org.eclipse.swt.widgets.Composite; + +import eu.modelwriter.configuration.alloy.trace.TraceException; + +public class EMFContainerSelectionPage extends AlloyToEMFWizardPage { + + private org.eclipse.swt.widgets.List classList; + private List emfClasses; + private String emfAlias; + + protected EMFContainerSelectionPage(String emfAlias, List classes) { + super("Select EMF Container"); + this.emfAlias = emfAlias; + emfClasses = classes; + setTitle("Select EMF Class"); + setDescription("Select a class to create a \"" + emfAlias + "\" EMF instace"); + } + + @Override + public void createControl(Composite parent) { + final Composite container = new Composite(parent, SWT.NULL); + container.setLayout(new FillLayout(SWT.HORIZONTAL)); + + classList = + new org.eclipse.swt.widgets.List(container, SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL); + + for (String clazz : emfClasses) { + classList.add(clazz); + } + classList.setSelection(0); + classList.addSelectionListener(new SelectionListener() { + + @Override + public void widgetSelected(SelectionEvent e) { + setPageComplete(true); + } + + @Override + public void widgetDefaultSelected(SelectionEvent e) { + + } + }); + setControl(container); + } + + + @Override + public boolean nextPressed() throws TraceException { + ((AlloyToEMF) getConverter()).setSelectedEClass(emfAlias, getSelectedClass()); + return true; + } + + public String getEmfAlias() { + return emfAlias; + } + + public void setEmfAlias(String emfAlias) { + this.emfAlias = emfAlias; + } + + public String getSelectedClass() { + return emfClasses.get(classList.getSelectionIndex()); + } +} diff --git a/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/alloy2emf/FinishPage.java b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/alloy2emf/FinishPage.java new file mode 100644 index 00000000..0eb3a32d --- /dev/null +++ b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/alloy2emf/FinishPage.java @@ -0,0 +1,125 @@ +package eu.modelwriter.configuration.alloy2emf; + +import org.eclipse.jface.wizard.IWizardPage; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.FileDialog; +import org.eclipse.swt.widgets.Label; + +import eu.modelwriter.configuration.internal.Utilities; + +public class FinishPage extends AlloyToEMFWizardPage { + + private boolean backButtonEnabled = true; + private Composite container; + private Button checkbox; + private Button atcCheckbox; + + protected FinishPage() { + super("lastpage"); + setTitle("Finish"); + } + + @Override + public void createControl(Composite parent) { + container = new Composite(parent, SWT.NULL); + container.setLayout(new GridLayout(2, false)); + for (String alias : alloyToEMF.getAliases()) { + Label label = new Label(container, SWT.NONE); + label.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false)); + label.setText("Select save location for " + alias + " \n"); + Button button = new Button(container, SWT.PUSH); + button.setLayoutData(new GridData(SWT.RIGHT, SWT.TOP, true, false)); + button.setText("Browse"); + button.addSelectionListener(new SelectionListener() { + + @Override + public void widgetSelected(SelectionEvent e) { + FileDialog fd = new FileDialog(button.getShell(), SWT.SAVE); + fd.setText("Save"); + fd.setFileName(alias + ".xmi"); + String path = alloyToEMF.getAlloyFilePath().substring(0, + alloyToEMF.getAlloyFilePath().lastIndexOf(Utilities.FILE_SEPERATOR)); + fd.setFilterPath(path); + String selectedPath = fd.open(); + if (selectedPath != null) { + String message = ("Select save location for " + alias + " \n\"" + selectedPath + "\""); + label.setText(message.length() >= 70 ? (message.substring(0, 70) + "...") : message); + label.setToolTipText(selectedPath); + label.getParent().layout(); + alloyToEMF.setSaveLocation(alias, selectedPath); + getContainer().updateButtons(); + } + } + + @Override + public void widgetDefaultSelected(SelectionEvent e) {} + }); + } + Composite checksCon = new Composite(container, SWT.NULL); + checksCon.setLayout(new GridLayout(1, false)); + checkbox = new Button(checksCon, SWT.CHECK); + checkbox.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false)); + checkbox.setText("Add trace of this instance to alloy specification"); + checkbox.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) {} + }); + atcCheckbox = new Button(checksCon, SWT.CHECK); + atcCheckbox.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false)); + atcCheckbox.setText("Start 'Automated Trace Creation'"); + atcCheckbox.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + + } + }); + + setControl(container); + } + + public void setBackButtonEnabled(boolean enabled) { + backButtonEnabled = enabled; + getContainer().updateButtons(); + } + + @Override + public IWizardPage getPreviousPage() { + if (!backButtonEnabled) { + return null; + } + return super.getPreviousPage(); + } + + @Override + public boolean backPressed() { + alloyToEMF.resetRun(); + return true; + } + + public void success() { + setMessage("Operation succeed without any error."); + } + + public void fail() { + setErrorMessage("Something went wrong!"); + } + + public boolean appendToFileChecked() { + return checkbox.getSelection(); + } + + public boolean startATC() { + return atcCheckbox.getSelection(); + } + + public void updateConverter() { + alloyToEMF = (AlloyToEMF) getConverter(); + } +} diff --git a/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/emf2alloy/AliasSelectionPage.java b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/emf2alloy/AliasSelectionPage.java new file mode 100644 index 00000000..dec6d710 --- /dev/null +++ b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/emf2alloy/AliasSelectionPage.java @@ -0,0 +1,47 @@ +package eu.modelwriter.configuration.emf2alloy; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Text; + +public class AliasSelectionPage extends EMFToAlloyWizardPage { + + private String alias; + private Text text; + + public AliasSelectionPage(String alias) { + super("SelectAliasPage"); + this.alias = alias; + setTitle("Alias"); + setDescription("Select an alias for Ecore file"); + } + + + @Override + public void createControl(Composite parent) { + Composite container = new Composite(parent, SWT.NULL); + container.setLayout(new GridLayout(2, false)); + Label label = new Label(container, SWT.NONE); + label.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false)); + label.setText("Select an alias for Ecore file "); + text = new Text(container, SWT.BORDER); + text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + text.setText(alias); + setControl(container); + } + + public String getAlias() { + alias = text.getText(); + return alias; + } + + @Override + public boolean nextPressed() throws Exception { + emfToAlloy.setAlias(alias); + return super.nextPressed(); + } + +} diff --git a/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/emf2alloy/ContainerSelectionPage.java b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/emf2alloy/ContainerSelectionPage.java new file mode 100644 index 00000000..d3c63e6a --- /dev/null +++ b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/emf2alloy/ContainerSelectionPage.java @@ -0,0 +1,90 @@ +package eu.modelwriter.configuration.emf2alloy; + +import java.util.List; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; + +public class ContainerSelectionPage extends EMFToAlloyWizardPage { + + private org.eclipse.swt.widgets.List classList; + private List emfClasses; + private String emfAlias; + private Button checkbox; + + protected ContainerSelectionPage(String emfAlias, List classes) { + super("SelectEMFContainer"); + this.emfAlias = emfAlias; + emfClasses = classes; + setTitle("Select EMF Class"); + setDescription("Select the container class to continue"); + } + + @Override + public void createControl(Composite parent) { + final Composite container = new Composite(parent, SWT.NULL); + container.setLayout(new GridLayout(1, true)); + container.setLayoutData(new GridData(SWT.FILL)); + + classList = + new org.eclipse.swt.widgets.List(container, SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL); + classList.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + + for (String clazz : emfClasses) { + classList.add(clazz); + } + classList.setSelection(0); + classList.addSelectionListener(new SelectionListener() { + + @Override + public void widgetSelected(SelectionEvent e) { + setPageComplete(true); + } + + @Override + public void widgetDefaultSelected(SelectionEvent e) { + + } + }); + checkbox = new Button(container, SWT.CHECK); + checkbox.setLayoutData(new GridData(SWT.BEGINNING, SWT.TOP, true, false)); + checkbox.setText("Don't include container class."); + checkbox.setSelection(true); + setControl(container); + } + + public List getEmfClasses() { + return emfClasses; + } + + public void setEmfClasses(List emfClasses) { + this.emfClasses = emfClasses; + for (String clazz : emfClasses) { + classList.add(clazz); + } + } + + public String getEmfAlias() { + return emfAlias; + } + + public void setEmfAlias(String emfAlias) { + this.emfAlias = emfAlias; + } + + public String getSelectedClass() { + return emfClasses.get(classList.getSelectionIndex()); + } + + @Override + public boolean nextPressed() throws Exception { + getEmfToAlloy().setContainerClass(getSelectedClass(), checkbox.getSelection()); + return super.nextPressed(); + } + +} diff --git a/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/emf2alloy/EMFToAlloy.java b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/emf2alloy/EMFToAlloy.java new file mode 100644 index 00000000..c350ee9e --- /dev/null +++ b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/emf2alloy/EMFToAlloy.java @@ -0,0 +1,318 @@ +package eu.modelwriter.configuration.emf2alloy; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; + +import org.eclipse.core.resources.IFile; +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EReference; + +import eu.modelwriter.configuration.alloy.trace.TraceException; +import eu.modelwriter.configuration.alloy.trace.TraceManager; +import eu.modelwriter.configuration.alloy2emf.AlloyToEMF; +import eu.modelwriter.configuration.generation.AbstractGeneration; +import eu.modelwriter.configuration.generation.GenerationWizardDialog; +import eu.modelwriter.configuration.internal.EcoreUtilities; +import eu.modelwriter.configuration.internal.Utilities; + +public class EMFToAlloy extends AbstractGeneration { + + class ContainmentFact { + String sig; + char shortName; + List conRels = new ArrayList<>(); + + public ContainmentFact(String sig) { + this.sig = sig; + shortName = sig.toLowerCase().charAt(0); + } + + public void addConRel(String domainName, String relName) { + conRels.add(domainName + "<:(" + shortName + ".~" + relName + ")"); + } + + public String getFact() { + String result = ""; + result += "all " + shortName + ":" + sig + " | "; + result += "one " + getUnionSet(conRels); + return result; + } + + public String getUnionSet(List rels) { + String result = "("; + for (Iterator it = rels.iterator(); it.hasNext();) { + result += it.next(); + if (it.hasNext()) + result += " + "; + } + result += ")"; + return result; + } + } + + private IFile ecoreFile = null; + private EObject ecoreRoot = null; + private StringBuilder builder; + private String saveLocation = null; + private String alias = null; + private String containerClassName; + private boolean ignoreContainer; + private boolean startEmfInstanceGeneration; + private GenerationWizardDialog dialog; + private EClass containerClass = null; + private List facts = new ArrayList<>(); + private int intPower = 1; + private HashMap containmentFacts = new HashMap<>(); + private String existingInstancePath; + + public EMFToAlloy(IFile ecoreFile) { + this.ecoreFile = ecoreFile; + } + + public void start() throws IOException { + try { + ecoreRoot = EcoreUtilities.getRootObject(ecoreFile.getFullPath().toString()); + } catch (IOException e) { + e.printStackTrace(); + throw new IOException("Error while loading Ecore file."); + } + builder = new StringBuilder(); + dialog = new GenerationWizardDialog(null, new EMFToAlloyWizard(this, "EMF to Alloy")); + dialog.open(); + } + + public void run() { + setState(RUNNING); + if (ecoreRoot != null) { + // appendModuleAndLoad(alias); + List contents = EcoreUtilities.getAllEClass(ecoreRoot); + for (EClass eClass : contents) { + appendSig(alias, eClass); + } + appendFacts(); + appendPredAndRun(); + } + setState(FINISHED); + } + + public String getModelFilePath() { + return ecoreFile.getLocation().toOSString(); + } + + private void appendFacts() { + for (ContainmentFact fact : containmentFacts.values()) { + builder.append("fact {\n"); + builder.append("\t" + fact.getFact()); + builder.append("\n}\n\n"); + } + for (String fact : facts) { + builder.append("fact {\n"); + builder.append("\t" + fact); + builder.append("\n}\n\n"); + } + } + + private void appendPredAndRun() { + builder.append("pred show{}\n"); + builder.append("run show"); + + if (intPower > 1) + builder.append(" for " + (intPower + 1) + " Int"); + + builder.append("\n"); + } + + @SuppressWarnings("unused") + private void appendModuleAndLoad(String alias) { + builder.append("module " + getFileName() + "\n\n"); + builder.append("-- loadAlias@" + alias); + builder.append("\n"); + builder.append("-- loadModel@" + ecoreFile.getFullPath().toString()); + builder.append("\n\n"); + } + + private String generateModuleAndLoad(String alias, String instancePath) { + StringBuilder builder = new StringBuilder(); + builder.append("module " + getFileName() + "\n\n"); + builder.append("-- loadAlias@" + alias); + builder.append("\n"); + builder.append("-- loadModel@" + ecoreFile.getFullPath().toString()); + if (instancePath != null) + builder.append("\n-- loadInstance@" + instancePath); + builder.append("\n\n"); + return builder.toString(); + } + + private void appendSig(String alias, EClass eClass) { + if (eClass.getName().equals(containerClassName) && ignoreContainer) + return; + + // append sig trace + builder.append("-- trace@" + alias + "." + eClass.getName() + "\n"); + + EReference containerRef = getRefToContainer(eClass); + if (containerRef != null) { + String mul = getMultiplicity(containerRef); + if ("set ".equals(mul)) + mul = ""; + builder.append(mul); + } + + // append sig + if (eClass.isAbstract()) { + builder.append("abstract sig " + eClass.getName()); + } else { + builder.append("sig " + eClass.getName()); + } + + // extends parts + if (!eClass.getESuperTypes().isEmpty()) { + builder.append(" extends "); + } + for (Iterator it = eClass.getESuperTypes().iterator(); it.hasNext();) { + EClass superClass = it.next(); + builder.append(superClass.getName()); + if (it.hasNext()) + builder.append(", "); + } + builder.append(" {\n"); + + // relations + for (Iterator iterator = eClass.getEReferences().iterator(); iterator.hasNext();) { + EReference eReference = iterator.next(); + + String sig = eReference.getEType().getName(); + if (eReference.isContainment()) { + if (containmentFacts.get(sig) == null) + containmentFacts.put(sig, new ContainmentFact(sig)); + containmentFacts.get(sig).addConRel(eClass.getName(), eReference.getName()); + } + + String multiplicity = getMultiplicity(eReference); + if (multiplicity.isEmpty()) { + multiplicity = "set "; + char c = eClass.getName().toLowerCase().charAt(0); + String fact = "all " + c + ":" + eClass.getName() + " | "; + if (eReference.getLowerBound() != -1) + fact += "#" + c + "." + eReference.getName() + " >= " + eReference.getLowerBound(); + if (eReference.getUpperBound() != -1) { + fact += " and #" + c + "." + eReference.getName() + " <= " + eReference.getUpperBound(); + } + facts.add(fact); + updateInt(Math.max(eReference.getLowerBound(), eReference.getUpperBound())); + } + + // append trace + builder.append( + "\t-- trace@" + alias + "." + eClass.getName() + "." + eReference.getName() + "\n"); + + // append relation + builder.append( + "\t" + eReference.getName() + ": " + multiplicity + eReference.getEType().getName()); + if (iterator.hasNext()) + builder.append(","); + builder.append("\n"); + } + builder.append("}\n\n"); + } + + private void updateInt(int upperBound) { + if (upperBound > Math.pow(2, intPower) - 1) { + intPower = 32 - Integer.numberOfLeadingZeros(upperBound - 1); + } + } + + private EReference getRefToContainer(EClass eClass) { + for (EReference eReference : containerClass.getEReferences()) { + if (eReference.getEReferenceType().getName().equals(eClass.getName())) + return eReference; + } + return null; + } + + private String getMultiplicity(EReference eReference) { + int l = eReference.getLowerBound(); + int u = eReference.getUpperBound(); + if (l == 0 && u == 1) + return "lone "; + if (l == 0 && u == -1) + return "set "; + if (l == 1 && u == 1) + return "one "; + if (l == 1 && u == -1) + return "some "; + + return ""; + } + + public void save(String selectedPath) { + saveLocation = selectedPath; + } + + public boolean isSaveLocationSet() { + return saveLocation != null; + } + + public String getFileName() { + return ecoreFile.getName().substring(0, ecoreFile.getName().indexOf(".")); + } + + public void performFinish() { + try { + builder.insert(0, generateModuleAndLoad(alias, existingInstancePath)); + Utilities.writeToFile(saveLocation, builder); + + if (startEmfInstanceGeneration) { + hideWizard(); + TraceManager.get().loadSpec(saveLocation); + AlloyToEMF alloyToEMF = new AlloyToEMF(saveLocation); + alloyToEMF.start(); + } + } catch (TraceException e) { + e.printStackTrace(); + } + } + + private void hideWizard() { + dialog.getShell().setAlpha(0); + } + + public void setAlias(String alias) { + this.alias = alias; + } + + public String getAlias() { + return alias; + } + + public EObject getEcoreRoot() { + return ecoreRoot; + } + + public void setContainerClass(String selectedClass, boolean ignore) { + containerClassName = selectedClass; + ignoreContainer = ignore; + containerClass = EcoreUtilities.findEClass(ecoreRoot, selectedClass); + } + + public void setEmfInstanceStarter(boolean selection) { + startEmfInstanceGeneration = selection; + } + + public void reset() { + setState(NOT_STARTED); + } + + @Override + public void onException(Exception e) { + + } + + public void existingInstancePath(String existingInstancePath) { + this.existingInstancePath = existingInstancePath; + } +} diff --git a/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/emf2alloy/EMFToAlloyWizard.java b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/emf2alloy/EMFToAlloyWizard.java new file mode 100644 index 00000000..d40808d8 --- /dev/null +++ b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/emf2alloy/EMFToAlloyWizard.java @@ -0,0 +1,49 @@ +package eu.modelwriter.configuration.emf2alloy; + +import org.eclipse.jface.wizard.IWizardPage; + +import eu.modelwriter.configuration.generation.AbstractGeneration; +import eu.modelwriter.configuration.generation.GenerationWizard; +import eu.modelwriter.configuration.internal.EcoreUtilities; + +public class EMFToAlloyWizard extends GenerationWizard { + + public EMFToAlloyWizard(AbstractGeneration converter, String title) { + super(converter, title); + } + + @Override + public void addPages() { + super.addPages(); + addPage(new AliasSelectionPage(getConverter().getFileName())); + addPage(new ContainerSelectionPage(getConverter().getAlias(), + EcoreUtilities.getAllEClassNames(getConverter().getEcoreRoot()))); + addPage(new FinishPage()); + } + + @Override + public boolean performFinish() { + getConverter().performFinish(); + return true; + } + + @Override + public boolean canFinish() { + IWizardPage page = getContainer().getCurrentPage(); + boolean isLastPage = page.getName().equals("lastpage"); + if (isLastPage && getConverter().getState() == AbstractGeneration.NOT_STARTED) { + getConverter().run(); + return false; + } + if (isLastPage && getConverter().getState() == AbstractGeneration.FINISHED) { + return getConverter().isSaveLocationSet(); + } + return false; + } + + @Override + public EMFToAlloy getConverter() { + return (EMFToAlloy) super.getConverter(); + } + +} diff --git a/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/emf2alloy/EMFToAlloyWizardPage.java b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/emf2alloy/EMFToAlloyWizardPage.java new file mode 100644 index 00000000..d31a4d27 --- /dev/null +++ b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/emf2alloy/EMFToAlloyWizardPage.java @@ -0,0 +1,24 @@ +package eu.modelwriter.configuration.emf2alloy; + +import org.eclipse.jface.wizard.IWizard; + +import eu.modelwriter.configuration.generation.GenerationWizardPage; + +public class EMFToAlloyWizardPage extends GenerationWizardPage { + + protected EMFToAlloy emfToAlloy = null; + + protected EMFToAlloyWizardPage(String pageName) { + super(pageName); + } + + @Override + public void setWizard(IWizard newWizard) { + super.setWizard(newWizard); + emfToAlloy = (EMFToAlloy) getConverter(); + } + + public EMFToAlloy getEmfToAlloy() { + return emfToAlloy; + } +} diff --git a/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/emf2alloy/FinishPage.java b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/emf2alloy/FinishPage.java new file mode 100644 index 00000000..b5fe76ad --- /dev/null +++ b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/emf2alloy/FinishPage.java @@ -0,0 +1,131 @@ +package eu.modelwriter.configuration.emf2alloy; + +import org.eclipse.core.resources.IFile; +import org.eclipse.jface.wizard.IWizardPage; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.FileDialog; +import org.eclipse.swt.widgets.Label; + +import eu.modelwriter.configuration.internal.Utilities; + +public class FinishPage extends EMFToAlloyWizardPage { + + private boolean backButtonEnabled = true; + private Composite container; + private Button checkbox; + + protected FinishPage() { + super("lastpage"); + setTitle("Finish"); + setDescription("Save the file"); + } + + @Override + public void createControl(Composite parent) { + container = new Composite(parent, SWT.NULL); + container.setLayout(new GridLayout(2, false)); + Label label = new Label(container, SWT.NONE); + label.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false)); + label.setText("Select save location \n"); + Button button = new Button(container, SWT.PUSH); + button.setLayoutData(new GridData(SWT.RIGHT, SWT.TOP, true, false)); + button.setText("Browse"); + button.addSelectionListener(new SelectionListener() { + + @Override + public void widgetSelected(SelectionEvent e) { + FileDialog fd = new FileDialog(button.getShell(), SWT.SAVE); + fd.setText("Save"); + fd.setFileName(getEmfToAlloy().getFileName() + ".mw"); + String path = emfToAlloy.getModelFilePath().substring(0, + emfToAlloy.getModelFilePath().lastIndexOf(Utilities.FILE_SEPERATOR)); + fd.setFilterPath(path); + String selectedPath = fd.open(); + if (selectedPath != null) { + String message = ("Select save location for \n\"" + selectedPath + "\""); + label.setText(message.length() >= 70 ? (message.substring(0, 70) + "...") : message); + label.setToolTipText(selectedPath); + label.getParent().layout(); + getEmfToAlloy().save(selectedPath); + getContainer().updateButtons(); + } + } + + @Override + public void widgetDefaultSelected(SelectionEvent e) {} + }); + checkbox = new Button(container, SWT.CHECK); + checkbox.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false)); + checkbox.setText("Start EMF Instance generation"); + + checkbox.setSelection(false); + new Label(container, SWT.NO).setVisible(false); + Button browseInstanceCheck = new Button(container, SWT.CHECK); + browseInstanceCheck.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false)); + browseInstanceCheck.setText("Load an existing instance file"); + final Button browseButton = new Button(container, SWT.PUSH); + browseButton.setLayoutData(new GridData(SWT.RIGHT, SWT.TOP, true, false)); + browseButton.setText("Browse for Instance"); + browseButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + FileDialog fd = new FileDialog(button.getShell(), SWT.SAVE); + fd.setText("Select"); + String path = emfToAlloy.getModelFilePath().substring(0, + emfToAlloy.getModelFilePath().lastIndexOf(Utilities.FILE_SEPERATOR)); + fd.setFilterPath(path); + String selectedPath = fd.open(); + if (selectedPath != null) { + IFile iFile = Utilities.getIFileFromPath(selectedPath); + getEmfToAlloy() + .existingInstancePath(iFile == null ? selectedPath : iFile.getFullPath().toString()); + getContainer().updateButtons(); + } + } + }); + browseInstanceCheck.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + browseButton.setEnabled(browseInstanceCheck.getSelection()); + checkbox.setEnabled(!browseInstanceCheck.getSelection()); + } + }); + browseInstanceCheck.setSelection(false); + browseButton.setEnabled(false); + checkbox.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + getEmfToAlloy().setEmfInstanceStarter(checkbox.getSelection()); + browseInstanceCheck.setEnabled(!checkbox.getSelection()); + } + }); + setControl(container); + } + + public void setBackButtonEnabled(boolean enabled) { + backButtonEnabled = enabled; + getContainer().updateButtons(); + } + + @Override + public IWizardPage getPreviousPage() { + if (!backButtonEnabled) { + return null; + } + return super.getPreviousPage(); + } + + @Override + public boolean backPressed() { + getEmfToAlloy().reset(); + return super.backPressed(); + } + +} diff --git a/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/generation/AbstractGeneration.java b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/generation/AbstractGeneration.java new file mode 100644 index 00000000..df023924 --- /dev/null +++ b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/generation/AbstractGeneration.java @@ -0,0 +1,19 @@ +package eu.modelwriter.configuration.generation; + +public abstract class AbstractGeneration { + + // states + public static final int RUNNING = 1, NOT_STARTED = 0, FINISHED = 2; + private int state = NOT_STARTED; + + public int getState() { + return state; + } + + public void setState(int state) { + this.state = state; + } + + abstract public void onException(Exception e); + +} diff --git a/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/generation/GenerationWizard.java b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/generation/GenerationWizard.java new file mode 100644 index 00000000..4523da7b --- /dev/null +++ b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/generation/GenerationWizard.java @@ -0,0 +1,24 @@ +package eu.modelwriter.configuration.generation; + +import org.eclipse.jface.wizard.Wizard; + +public class GenerationWizard extends Wizard { + + private AbstractGeneration converter; + + public GenerationWizard(AbstractGeneration converter, String title) { + super(); + this.converter = converter; + setWindowTitle(title); + } + + @Override + public boolean performFinish() { + return true; + } + + public AbstractGeneration getConverter() { + return converter; + } + +} diff --git a/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/generation/GenerationWizardDialog.java b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/generation/GenerationWizardDialog.java new file mode 100644 index 00000000..3689980c --- /dev/null +++ b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/generation/GenerationWizardDialog.java @@ -0,0 +1,42 @@ +package eu.modelwriter.configuration.generation; + +import org.eclipse.jface.wizard.IWizard; +import org.eclipse.jface.wizard.IWizardPage; +import org.eclipse.jface.wizard.WizardDialog; +import org.eclipse.swt.widgets.Shell; + +public class GenerationWizardDialog extends WizardDialog { + + public GenerationWizardDialog(Shell parentShell, IWizard newWizard) { + super(parentShell, newWizard); + } + + @Override + protected void backPressed() { + IWizardPage currentActivePage = getCurrentPage(); + + /* notify current page if it wants to do any validation on input */ + if (!((GenerationWizardPage) currentActivePage).backPressed()) + return; + + /* delegate backPressed processing to super */ + super.backPressed(); + } + + @Override + protected void nextPressed() { + IWizardPage currentActivePage = getCurrentPage(); + + /* notify current page if it wants to do any validation on input */ + try { + if (!((GenerationWizardPage) currentActivePage).nextPressed()) + return; + } catch (Exception e) { + GenerationWizard wizard = (GenerationWizard) getWizard(); + wizard.getConverter().onException(e); + } + + /* delegate nextPressed processing to super */ + super.nextPressed(); + } +} diff --git a/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/generation/GenerationWizardPage.java b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/generation/GenerationWizardPage.java new file mode 100644 index 00000000..0f542135 --- /dev/null +++ b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/generation/GenerationWizardPage.java @@ -0,0 +1,57 @@ +package eu.modelwriter.configuration.generation; + +import org.eclipse.jface.wizard.IWizard; +import org.eclipse.jface.wizard.WizardPage; +import org.eclipse.swt.widgets.Composite; + +import eu.modelwriter.configuration.alloy.trace.TraceException; + +public class GenerationWizardPage extends WizardPage { + + private AbstractGeneration converter = null; + + protected GenerationWizardPage(String pageName) { + super(pageName); + } + + /** + * This triggers when next button clicked + * + * @return true to go next page + * @throws TraceException + * @throws Exception + */ + public boolean nextPressed() throws Exception { + return true; + } + + /** + * This triggers when back button clicked + * + * @return true to go previous page + */ + public boolean backPressed() { + return true; + } + + @Override + public void createControl(Composite parent) { + setControl(parent); + } + + @Override + public void setWizard(IWizard newWizard) { + super.setWizard(newWizard); + try { + GenerationWizard wizard = (GenerationWizard) newWizard; + converter = wizard.getConverter(); + } catch (ClassCastException e) { + e.printStackTrace(); + } + } + + public AbstractGeneration getConverter() { + return converter; + } + +} diff --git a/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/internal/AlloyExecuter.java b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/internal/AlloyExecuter.java new file mode 100644 index 00000000..ccbe41ee --- /dev/null +++ b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/internal/AlloyExecuter.java @@ -0,0 +1,83 @@ +package eu.modelwriter.configuration.internal; + +import edu.mit.csail.sdg.alloy4.A4Reporter; +import edu.mit.csail.sdg.alloy4.ConstList; +import edu.mit.csail.sdg.alloy4.Err; +import edu.mit.csail.sdg.alloy4.ErrorWarning; +import edu.mit.csail.sdg.alloy4compiler.ast.Command; +import edu.mit.csail.sdg.alloy4compiler.parser.CompModule; +import edu.mit.csail.sdg.alloy4compiler.parser.CompUtil; +import edu.mit.csail.sdg.alloy4compiler.translator.A4Options; +import edu.mit.csail.sdg.alloy4compiler.translator.A4Solution; +import edu.mit.csail.sdg.alloy4compiler.translator.TranslateAlloyToKodkod; + +public class AlloyExecuter { + + private CompModule world = null; + private A4Reporter rep = null; + + /** + * + * @param filePath alloy file path to parse + * @return + * @throws Err + */ + public AlloyExecuter(String filePath) throws Err { + rep = new A4Reporter() { + @Override + public void warning(final ErrorWarning msg) { + System.out.println("Relevance Warning:\n" + msg.toString().trim() + "\n\n"); + } + }; + parse(filePath); + } + + public AlloyExecuter() { + rep = new A4Reporter() { + @Override + public void warning(final ErrorWarning msg) { + System.out.println("Relevance Warning:\n" + msg.toString().trim() + "\n\n"); + } + }; + } + + public void parse(String filePath) throws Err { + world = CompUtil.parseEverything_fromFile(rep, null, filePath); + } + + /** + * Executes given command + * + * @param command to run + * @return solution if it is satisfiable else null + * @throws Err + */ + public A4Solution executeCommand(Command command) throws Err { + if (world == null) + return null; + + final A4Options options = new A4Options(); + options.solver = A4Options.SatSolver.SAT4J; + A4Solution solution = + TranslateAlloyToKodkod.execute_command(rep, world.getAllReachableSigs(), command, options); + if (solution.satisfiable()) { + return solution; + } + + return null; + } + + /** + * + * @return list of executable commands + */ + public ConstList getRunCommands() { + if (world == null) + return null; + return world.getAllCommands(); + } + + public CompModule getWorld() { + return world; + } +} diff --git a/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/internal/AlloyUtilities.java b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/internal/AlloyUtilities.java new file mode 100644 index 00000000..363fb25d --- /dev/null +++ b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/internal/AlloyUtilities.java @@ -0,0 +1,1675 @@ +/******************************************************************************* + * Copyright (c) 2015 UNIT Information Technologies R&D Ltd All rights reserved. This program and + * the accompanying materials are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: Ferhat Erata - initial API and implementation H. Emre Kirmizi - initial API and + * implementation Serhat Celik - initial API and implementation U. Anil Ozturk - initial API and + * implementation + *******************************************************************************/ +package eu.modelwriter.configuration.internal; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.stream.Collectors; + +import org.eclipse.core.resources.IMarker; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.Path; +import org.eclipse.emf.common.util.EList; +import org.eclipse.emf.common.util.URI; + +import edu.mit.csail.sdg.alloy4viz.AlloyAtom; +import edu.mit.csail.sdg.alloy4viz.AlloyInstance; +import edu.mit.csail.sdg.alloy4viz.AlloyRelation; +import edu.mit.csail.sdg.alloy4viz.AlloyTuple; +import eu.modelwriter.marker.internal.AnnotationFactory; +import eu.modelwriter.marker.internal.MarkUtilities; +import eu.modelwriter.marker.internal.MarkerFactory; +import eu.modelwriter.traceability.core.persistence.AlloyType; +import eu.modelwriter.traceability.core.persistence.AtomType; +import eu.modelwriter.traceability.core.persistence.DocumentRoot; +import eu.modelwriter.traceability.core.persistence.EntryType; +import eu.modelwriter.traceability.core.persistence.FieldType; +import eu.modelwriter.traceability.core.persistence.ItemType; +import eu.modelwriter.traceability.core.persistence.RelationType; +import eu.modelwriter.traceability.core.persistence.SigType; +import eu.modelwriter.traceability.core.persistence.SourceType; +import eu.modelwriter.traceability.core.persistence.TupleType; +import eu.modelwriter.traceability.core.persistence.TypeType; +import eu.modelwriter.traceability.core.persistence.TypesType; +import eu.modelwriter.traceability.core.persistence.persistenceFactory; +import eu.modelwriter.traceability.core.persistence.internal.ModelIO; + +public class AlloyUtilities { + + final public static String GROUP_ID = "groupId"; + final public static String LEADER_ID = "leaderId"; + final public static String MARKER_URI = "uri"; + final public static String OFFSET = "offset"; + final public static String RESOURCE = "resource"; + final public static String TEXT = "text"; + + public static Map typeHashMap = new HashMap<>(); + public static String xmlFileLocation = + ".modelwriter persistence.xml".replace(" ", System.getProperty("file.separator")); + + public static int getTotalTargetCount(final IMarker marker) { + final Map fieldsTargets = AlloyUtilities.getRelationsOfFirstSideMarker(marker); + final ArrayList relationsTargets = + AlloyUtilities.getTargetsOfMarkerAtRelations(marker); + + return fieldsTargets.size() + relationsTargets.size(); + } + + public static void addMapping2RelationType(IMarker fromMarker, IMarker toMarker) { + fromMarker = MarkUtilities.getLeaderOfMarker(fromMarker); + toMarker = MarkUtilities.getLeaderOfMarker(toMarker); + + final DocumentRoot documentRoot = AlloyUtilities.getDocumentRoot(); + + final RelationType relationType = documentRoot.getAlloy().getRelation(); + + final TupleType tupleType = persistenceFactory.eINSTANCE.createTupleType(); + relationType.getTuple().add(tupleType); + + final AtomType fromAtom = persistenceFactory.eINSTANCE.createAtomType(); + tupleType.getAtom().add(fromAtom); + fromAtom.setLabel(MarkUtilities.getSourceId(fromMarker)); + + final AtomType toAtom = persistenceFactory.eINSTANCE.createAtomType(); + tupleType.getAtom().add(toAtom); + toAtom.setLabel(MarkUtilities.getSourceId(toMarker)); + + AlloyUtilities.writeDocumentRoot(documentRoot); + } + + public static void addMarkerToRepository(final IMarker marker) { + final DocumentRoot documentRoot = AlloyUtilities.getDocumentRoot(); + + if (AlloyUtilities.findItemTypeInRepository(marker) == -1) { + final ItemType itemType = persistenceFactory.eINSTANCE.createItemType(); + documentRoot.getAlloy().getRepository().getItem().add(itemType); + itemType.setId(MarkUtilities.getSourceId(marker)); + + AlloyUtilities.setEntries(itemType, marker); + } + AlloyUtilities.writeDocumentRoot(documentRoot); + } + + public static void addRelation2Markers(IMarker fromMarker, IMarker toMarker, + final String relation) { + fromMarker = MarkUtilities.getLeaderOfMarker(fromMarker); + toMarker = MarkUtilities.getLeaderOfMarker(toMarker); + + final ArrayList allParentIdsOfFromMarkerType = AlloyUtilities + .getAllParentIds(AlloyUtilities.getSigTypeIdByName(MarkUtilities.getType(fromMarker))); + final ArrayList allParentIdsOfToMarkerType = AlloyUtilities + .getAllParentIds(AlloyUtilities.getSigTypeIdByName(MarkUtilities.getType(toMarker))); + + final DocumentRoot documentRoot = AlloyUtilities.getDocumentRoot(); + + final AtomType fromAtom = persistenceFactory.eINSTANCE.createAtomType(); + fromAtom.setLabel(MarkUtilities.getSourceId(fromMarker)); + + final AtomType toAtom = persistenceFactory.eINSTANCE.createAtomType(); + toAtom.setLabel(MarkUtilities.getSourceId(toMarker)); + + final TupleType tuple = persistenceFactory.eINSTANCE.createTupleType(); + tuple.getAtom().add(fromAtom); + tuple.getAtom().add(toAtom); + + final EList fields = documentRoot.getAlloy().getInstance().getField(); + + for (final FieldType fieldType : fields) { + if (!fieldType.getLabel().equals(relation)) { + continue; + } + for (final TypesType typesType : fieldType.getTypes()) { + if (allParentIdsOfFromMarkerType.contains(typesType.getType().get(0).getID()) + && allParentIdsOfToMarkerType.contains(typesType.getType().get(1).getID())) { + if (!AlloyUtilities.isContainTuple(fieldType, tuple)) { + fieldType.getTuple().add(tuple); + } + break; + } + } + } + + AlloyUtilities.writeDocumentRoot(documentRoot); + } + + public static void addRelation2Markers(IMarker selectedMarker, final Object[] checkedMarkers, + final Map relationMap) { + selectedMarker = MarkUtilities.getLeaderOfMarker(selectedMarker); + + for (final Object object : checkedMarkers) { + if (object instanceof IMarker) { + IMarker marker = (IMarker) object; + marker = MarkUtilities.getLeaderOfMarker(marker); + final String relationName = relationMap.get(marker); + AlloyUtilities.addRelation2Markers(selectedMarker, marker, relationName); + } + } + AnnotationFactory.convertAnnotationType(selectedMarker, false, false, + AlloyUtilities.getTotalTargetCount(selectedMarker)); + } + + public static AtomType addStrayedAtom2Sig(final String sigName) { + final DocumentRoot documentRoot = AlloyUtilities.getDocumentRoot(); + + final String id = MarkerFactory.generateId(); + + final ItemType itemType = persistenceFactory.eINSTANCE.createItemType(); + itemType.setId(id); + documentRoot.getAlloy().getRepository().getItem().add(itemType); + + final AtomType atomType = persistenceFactory.eINSTANCE.createAtomType(); + atomType.setLabel(id); + atomType.setReasoned(true); + + for (final SigType sigType : documentRoot.getAlloy().getInstance().getSig()) { + String label = sigType.getLabel(); + label = label.substring(sigType.getLabel().indexOf("/") + 1); + if (label.equals(sigName)) { + sigType.getAtom().add(atomType); + } + } + + AlloyUtilities.writeDocumentRoot(documentRoot); + + return atomType; + } + + public static AtomType addNewAtom2Sig(final String sigName) { + final DocumentRoot documentRoot = AlloyUtilities.getDocumentRoot(); + + final String id = MarkerFactory.generateId(); + + final ItemType itemType = persistenceFactory.eINSTANCE.createItemType(); + itemType.setId(id); + documentRoot.getAlloy().getRepository().getItem().add(itemType); + + final AtomType atomType = persistenceFactory.eINSTANCE.createAtomType(); + atomType.setLabel(id); + atomType.setReasoned(false); + + for (final SigType sigType : documentRoot.getAlloy().getInstance().getSig()) { + String label = sigType.getLabel(); + label = label.substring(sigType.getLabel().indexOf("/") + 1); + if (label.equals(sigName)) { + sigType.getAtom().add(atomType); + } + } + + AlloyUtilities.writeDocumentRoot(documentRoot); + + return atomType; + } + + public static void addTypeToMarker(final IMarker marker) { + final DocumentRoot documentRoot = AlloyUtilities.getDocumentRoot(); + + final AtomType atom = persistenceFactory.eINSTANCE.createAtomType(); + + atom.setLabel(MarkUtilities.getSourceId(marker)); + + final String type = MarkUtilities.getType(marker); + + final EList sigs = documentRoot.getAlloy().getInstance().getSig(); + + final int idOfTypeSigInSigType = AlloyUtilities.isTypeInSig(type); + + for (final SigType sigType : sigs) { + if (type.equals(sigType.getLabel().substring(sigType.getLabel().indexOf("/") + 1))) { + sigType.getAtom().add(atom); + } else if (idOfTypeSigInSigType != -1 && sigType.getID() == idOfTypeSigInSigType) { + final AtomType typeAtom = persistenceFactory.eINSTANCE.createAtomType(); + typeAtom.setLabel(MarkUtilities.getSourceId(marker)); + sigType.getAtom().add(typeAtom); + } + } + + AlloyUtilities.writeDocumentRoot(documentRoot); + } + + public static void assignMarker2Atom(final IMarker marker, final AlloyAtom atom) { + final String atomName = atom.getOriginalName(); + final String sigName = atom.getType().getName(); + final int index = Integer.valueOf(atomName.substring(atomName.indexOf("$") + 1)); + + final SigType sigType = + AlloyUtilities.getSigTypeById(AlloyUtilities.getSigTypeIdByName(sigName)); + final String id = sigType.getAtom().get(index).getLabel(); + + final int itemITypeIndex = AlloyUtilities.findItemTypeInRepository(marker); + + final DocumentRoot documentRoot = AlloyUtilities.getDocumentRoot(); + documentRoot.getAlloy().getRepository().getItem().get(itemITypeIndex).setId(id); + MarkUtilities.setSourceId(marker, id); + AlloyUtilities.writeDocumentRoot(documentRoot); + } + + public static void bindAtomToMarker(final String sigTypeName, final int index, + final IMarker selectedMarker) { + final DocumentRoot documentRoot = AlloyUtilities.getDocumentRoot(); + String atomId = null; + + final EList sigs = documentRoot.getAlloy().getInstance().getSig(); + final EList fields = documentRoot.getAlloy().getInstance().getField(); + final EList items = documentRoot.getAlloy().getRepository().getItem(); + + for (final SigType sigType : sigs) { + String label = sigType.getLabel(); + label = label.substring(label.indexOf("/") + 1); + if (label.equals(sigTypeName)) { + final AtomType atomType = sigType.getAtom().get(index); + atomType.setReasoned(false); + atomId = atomType.getLabel(); + break; + } + } + + for (final FieldType fieldType : fields) { + for (final TupleType tupleType : fieldType.getTuple()) { + for (final AtomType atomType : tupleType.getAtom()) { + if (atomType.getLabel().equals(atomId)) { + atomType.setReasoned(false); + } + } + } + } + + final String id = MarkUtilities.getSourceId(selectedMarker); + + for (final ItemType itemType : items) { + if (itemType.getId().equals(id)) { + itemType.setId(atomId); + break; + } + } + MarkUtilities.setSourceId(selectedMarker, atomId); + MarkUtilities.setType(selectedMarker, sigTypeName); + + AlloyUtilities.writeDocumentRoot(documentRoot); + } + + public static void clearAllReasonedTuplesAndAtoms() { + final DocumentRoot documentRoot = AlloyUtilities.getDocumentRoot(); + + final EList fieldTypes = documentRoot.getAlloy().getInstance().getField(); + + for (final FieldType fieldType : fieldTypes) { + final Iterator tupleIter = fieldType.getTuple().iterator(); + + while (tupleIter.hasNext()) { + final TupleType tupleType = tupleIter.next(); + if (tupleType.isReasoned()) { + tupleIter.remove(); + } + } + } + + final EList sigTypes = documentRoot.getAlloy().getInstance().getSig(); + + for (final SigType sigType : sigTypes) { + final Iterator atomIter = sigType.getAtom().iterator(); + while (atomIter.hasNext()) { + final AtomType atomType = atomIter.next(); + if (atomType.isReasoned()) { + atomIter.remove(); + } + } + } + + AlloyUtilities.writeDocumentRoot(documentRoot); + } + + public static void clearFields() { + final DocumentRoot documentRoot = AlloyUtilities.getDocumentRoot(); + for (final FieldType fieldType : documentRoot.getAlloy().getInstance().getField()) { + fieldType.getTuple().clear(); + } + AlloyUtilities.writeDocumentRoot(documentRoot); + } + + public static void clearRelation() { + final DocumentRoot documentRoot = AlloyUtilities.getDocumentRoot(); + documentRoot.getAlloy().getRelation().getTuple().clear(); + AlloyUtilities.writeDocumentRoot(documentRoot); + } + + public static void clearRepository() { + final DocumentRoot documentRoot = AlloyUtilities.getDocumentRoot(); + documentRoot.getAlloy().getRepository().getItem().clear(); + AlloyUtilities.writeDocumentRoot(documentRoot); + } + + public static void clearSigs() { + final DocumentRoot documentRoot = AlloyUtilities.getDocumentRoot(); + for (final SigType sigType : documentRoot.getAlloy().getInstance().getSig()) { + sigType.getAtom().clear(); + } + AlloyUtilities.writeDocumentRoot(documentRoot); + } + + public static List getAllReasonedAtoms() { + List result = new ArrayList(); + final DocumentRoot documentRoot = AlloyUtilities.getDocumentRoot(); + final EList sigTypes = documentRoot.getAlloy().getInstance().getSig(); + + for (final SigType sigType : sigTypes) { + final Iterator atomIter = sigType.getAtom().iterator(); + while (atomIter.hasNext()) { + final AtomType atomType = atomIter.next(); + if (atomType.isReasoned()) { + result.add(AlloyUtilities.getAtomNameById(atomType.getLabel())); + } + } + } + return result; + } + + public static HashMap getAllReasonedTuples() { + HashMap result = new HashMap(); + final EList fieldList = AlloyUtilities.getFieldTypes(); + for (final FieldType fieldType : fieldList) { + final EList tupleList = fieldType.getTuple(); + + for (final TupleType tupleType : tupleList) { + if (tupleType.isReasoned()) { + result.put(tupleType, fieldType.getLabel()); + } + } + } + return result; + } + + public static int findItemTypeInRepository(final IMarker marker) { + final String markerId = MarkUtilities.getSourceId(marker); + + final EList itemTypes = AlloyUtilities.getItemtypes(); + + int itemTypeIndex = 0; + + for (final ItemType itemType : itemTypes) { + if (markerId.equals(itemType.getId())) { + return itemTypeIndex; + } + itemTypeIndex++; + } + + return -1; + } + + public static IMarker findMarkerByID(final String markerId) { + final ItemType itemType = AlloyUtilities.getItemById(markerId); + + if (itemType == null) { + return null; + } + + final String path = AlloyUtilities.getValueOfEntry(itemType, AlloyUtilities.RESOURCE); + + if (path == null) { + return null; + } + + final IMarker marker = MarkUtilities.getiMarker(markerId, path); + + return marker; + } + + public static IMarker findMarker(final String sigTypeName, final int index) { + final SigType sigType = + AlloyUtilities.getSigTypeById(AlloyUtilities.getSigTypeIdByName(sigTypeName)); + final EList atoms = sigType.getAtom(); + + final String markerId = atoms.get(index).getLabel(); + + final ItemType itemType = AlloyUtilities.getItemById(markerId); + + if (itemType == null) { + return null; + } + + final String path = AlloyUtilities.getValueOfEntry(itemType, AlloyUtilities.RESOURCE); + + if (path == null) { + return null; + } + + final IMarker marker = MarkUtilities.getiMarker(markerId, path); + + return marker; + } + + public static ArrayList getAllChildNames(final int id) { + return (ArrayList) AlloyUtilities.getAllChildIds(id).stream() + .map(p -> AlloyUtilities.getSigNameById(p.intValue())).collect(Collectors.toList()); + } + + public static ArrayList getAllChildIds(final int id) { + final ArrayList ids = new ArrayList<>(); + + final ArrayList sigTypes = AlloyUtilities.getSigTypeListByParentId(id); + + for (final SigType sigType : sigTypes) { + ids.addAll(AlloyUtilities.getAllChildIds(sigType.getID())); + } + + ids.add(id); + + return ids; + } + + public static ArrayList getAllParentNames(final int id) { + return (ArrayList) AlloyUtilities.getAllParentIds(id).stream() + .map(p -> AlloyUtilities.getSigNameById(p.intValue())).collect(Collectors.toList()); + } + + public static ArrayList getAllParentIds(int id) { + final ArrayList ids = new ArrayList<>(); + + do { + ids.add(id); + final SigType sigType = AlloyUtilities.getSigTypeById(id); + if (sigType.getType().size() == 0) { + id = sigType.getParentID(); + } else { + id = sigType.getType().get(0).getID(); + } + } while (id != 0); + + return ids; + } + + public static ArrayList getAllParentIds(int id, final DocumentRoot documentRoot) { + final ArrayList ids = new ArrayList<>(); + + do { + ids.add(id); + final SigType sigType = AlloyUtilities.getSigTypeById(id, documentRoot); + if (sigType.getType().size() == 0) { + id = sigType.getParentID(); + } else { + id = sigType.getType().get(0).getID(); + } + } while (id != 0); + + return ids; + } + + public static SigType getAncestorOfSig(final int id) { + SigType sigType = AlloyUtilities.getSigTypeById(id); + while (sigType.getParentID() != 2) { + sigType = AlloyUtilities.getSigTypeById(sigType.getParentID()); + } + + return sigType; + } + + public static String getAtomNameById(final String id) { + final EList sigList = AlloyUtilities.getSigTypes(AlloyUtilities.getDocumentRoot()); + + for (final SigType sigType : sigList) { + final EList atoms = sigType.getAtom(); + int index = 0; + final String sigLabel = sigType.getLabel().substring(sigType.getLabel().indexOf("/") + 1); + + for (final AtomType atomType : atoms) { + if (atomType.getLabel().equals(id)) { + return sigLabel + "$" + index; + } + index++; + } + } + return null; + } + + public static AtomType getAtomTypeBySourceIdFromSig(final DocumentRoot documentRoot, + final String sourceId) { + final AlloyType alloyType = documentRoot.getAlloy(); + + final EList listOfSigs = alloyType.getInstance().getSig(); + for (final SigType sigType : listOfSigs) { + final EList atoms = sigType.getAtom(); + for (final AtomType atomType : atoms) { + if (atomType.getLabel().equals(sourceId)) { + return atomType; + } + } + } + return null; + } + + public static ArrayList getChangedAtoms() { + final EList sigList = AlloyUtilities.getSigTypes(AlloyUtilities.getDocumentRoot()); + final ArrayList changedAtoms = new ArrayList<>(); + + for (final SigType sigType : sigList) { + final EList atoms = sigType.getAtom(); + int index = 0; + final String sigLabel = sigType.getLabel().substring(sigType.getLabel().indexOf("/") + 1); + + for (final AtomType atomType : atoms) { + if (atomType.getChanged() != null && atomType.getChanged()) { + changedAtoms.add(sigLabel + "$" + index); + } + index++; + } + } + return changedAtoms; + } + + public static DocumentRoot getDocumentRoot() { + @SuppressWarnings("rawtypes") + final ModelIO modelIO = new ModelIO<>(); + @SuppressWarnings("rawtypes") + final List list; + try { + list = modelIO.read(AlloyUtilities.getUri()); + } catch (final IOException e) { + return null; + } + if (list.isEmpty()) { + return null; + } + final DocumentRoot documentRoot = (DocumentRoot) list.get(0); + return documentRoot; + } + + public static DocumentRoot getDocumentRootForMetaModel(final String filename) { + @SuppressWarnings("rawtypes") + final ModelIO modelIO = new ModelIO<>(); + @SuppressWarnings("rawtypes") + List list = null; + try { + list = modelIO.read(URI.createFileURI(AlloyUtilities.getLocationForMetamodel(filename))); + } catch (final IOException e) { + return null; + } + if (list.isEmpty()) { + return null; + } + final DocumentRoot documentRoot = (DocumentRoot) list.get(0); + return documentRoot; + } + + public static FieldType getFieldTypeByName(final String label) { + final DocumentRoot documentRoot = AlloyUtilities.getDocumentRoot(); + + for (final FieldType fieldType : documentRoot.getAlloy().getInstance().getField()) { + if (fieldType.getLabel().equals(label)) { + return fieldType; + } + } + return null; + } + + public static EList getFieldTypes() { + final DocumentRoot documentRoot = AlloyUtilities.getDocumentRoot(); + + return documentRoot.getAlloy().getInstance().getField(); + } + + /** + * @param typeName + * @param side if true, return FieldType List according left side type + * @return + */ + public static ArrayList getFieldTypesList(final String typeName, final boolean side) { + final EList fields = AlloyUtilities.getFieldTypes(); + final ArrayList foundFieldTypes = new ArrayList<>(); + + final int id = AlloyUtilities.getSigTypeIdByName(typeName); + + final ArrayList idList = AlloyUtilities.getAllParentIds(id); + + for (final Integer typeId : idList) { + for (final FieldType fieldType : fields) { + final EList typesTypes = fieldType.getTypes(); + for (final TypesType typesType : typesTypes) { + final EList typeTypes = typesType.getType(); + if (side && typeTypes.get(0).getID() == typeId) { + foundFieldTypes.add(fieldType); + break; + } else if (!side && typeTypes.get(1).getID() == typeId) { + foundFieldTypes.add(fieldType); + break; + } + } + } + } + return foundFieldTypes; + } + + public static ArrayList> getImpactedAtoms() { + final EList fieldList = AlloyUtilities.getFieldTypes(); + final ArrayList> impactedAtoms = new ArrayList<>(); + + for (final FieldType fieldType : fieldList) { + final EList tupleList = fieldType.getTuple(); + + for (final TupleType tupleType : tupleList) { + if (tupleType.getAtom().get(1).getImpact() != null + && tupleType.getAtom().get(1).getImpact()) { + final AtomType atom = AlloyUtilities.getAtomTypeBySourceIdFromSig( + AlloyUtilities.getDocumentRoot(), tupleType.getAtom().get(1).getLabel()); + final AtomType changedAtom = AlloyUtilities.getAtomTypeBySourceIdFromSig( + AlloyUtilities.getDocumentRoot(), tupleType.getAtom().get(0).getLabel()); + final ArrayList impactedRelations = new ArrayList<>(); + impactedRelations.add(AlloyUtilities.getAtomNameById(atom.getLabel())); + impactedRelations.add(AlloyUtilities.getAtomNameById(changedAtom.getLabel())); + impactedAtoms.add(impactedRelations); + } + } + } + return impactedAtoms; + } + + public static ItemType getItemById(final String id) { + final EList itemTypes = AlloyUtilities.getItemtypes(); + + for (final ItemType itemType : itemTypes) { + if (id.equals(itemType.getId())) { + return itemType; + } + } + + return null; + } + + public static EList getItemtypes() { + final DocumentRoot documentRoot = AlloyUtilities.getDocumentRoot(); + + return documentRoot.getAlloy().getRepository().getItem(); + } + + public static String getLocation() { + return ResourcesPlugin.getWorkspace().getRoot().getLocation() + "/" + + AlloyUtilities.xmlFileLocation; + } + + public static String getLocationForMetamodel(final String filename) { + return ResourcesPlugin.getWorkspace().getRoot().getLocation() + + System.getProperty("file.separator") + ".modelwriter" + + System.getProperty("file.separator") + filename + System.getProperty("file.separator") + + ".xml"; + } + + public static String getOriginalModuleName() { + final String filePath = + AlloyUtilities.getDocumentRoot().getAlloy().getSource().get(0).getFilename(); + return filePath.substring(filePath.lastIndexOf(System.getProperty("file.separator")) + 1, + filePath.lastIndexOf(".")); + } + + private static ArrayList getReasonedAtoms() { + final EList sigList = AlloyUtilities.getSigTypes(AlloyUtilities.getDocumentRoot()); + final ArrayList reasonedAtoms = new ArrayList<>(); + + for (final SigType sigType : sigList) { + final EList atoms = sigType.getAtom(); + int index = 0; + final String sigLabel = sigType.getLabel().substring(sigType.getLabel().indexOf("/") + 1); + + for (final AtomType atomType : atoms) { + if (atomType.isReasoned()) { + reasonedAtoms.add(sigLabel + "$" + index); + } + index++; + } + } + return reasonedAtoms; + + } + + public static Map getRelationsOfFirstSideMarker(IMarker selectedMarker) { + selectedMarker = MarkUtilities.getLeaderOfMarker(selectedMarker); + + final Map relationsOfMarker = new HashMap<>(); + if (MarkUtilities.getType(selectedMarker) == null) { + return relationsOfMarker; + } + final ArrayList fieldTypesOfSelectedMarkerType = + AlloyUtilities.getFieldTypesList(MarkUtilities.getType(selectedMarker), true); + final String selectedMarkerId = MarkUtilities.getSourceId(selectedMarker); + + for (final FieldType fieldType : fieldTypesOfSelectedMarkerType) { + final EList tupleTypes = fieldType.getTuple(); + for (final TupleType tupleType : tupleTypes) { + final EList atoms = tupleType.getAtom(); + final AtomType firstAtomType = atoms.get(0); + if (firstAtomType.getLabel().equals(selectedMarkerId)) { + final AtomType secondAtomType = atoms.get(1); + final ItemType itemTypeOfAtom = AlloyUtilities.getItemById(secondAtomType.getLabel()); + if (itemTypeOfAtom == null) + continue; + final IMarker toMarker = MarkUtilities.getiMarker(secondAtomType.getLabel(), + AlloyUtilities.getValueOfEntry(itemTypeOfAtom, AlloyUtilities.RESOURCE)); + relationsOfMarker.put(toMarker, fieldType.getLabel()); + } + } + } + + return relationsOfMarker; + } + + + public static String getAtomId(final String sigTypeName, final int index) { + final SigType sigType = + AlloyUtilities.getSigTypeById(AlloyUtilities.getSigTypeIdByName(sigTypeName)); + final EList atoms = sigType.getAtom(); + return atoms.get(index).getLabel(); + } + + public static Map getReasonedRelationsOfFSAtom(final String sigTypeName, + final int index) { + final String id = AlloyUtilities.getAtomId(sigTypeName, index); + final Map relationsOfMarker = new HashMap<>(); + final ArrayList fieldTypesOfSelectedMarkerType = + AlloyUtilities.getFieldTypesList(sigTypeName, true); + + for (final FieldType fieldType : fieldTypesOfSelectedMarkerType) { + final EList tupleTypes = fieldType.getTuple(); + for (final TupleType tupleType : tupleTypes) { + final EList atoms = tupleType.getAtom(); + final AtomType firstAtomType = atoms.get(0); + if (firstAtomType.getLabel().equals(id) && tupleType.isReasoned()) { + final AtomType secondAtomType = atoms.get(1); + Object key = null; + if (!secondAtomType.isReasoned()) { + final ItemType itemTypeOfAtom = AlloyUtilities.getItemById(secondAtomType.getLabel()); + key = MarkUtilities.getiMarker(secondAtomType.getLabel(), + AlloyUtilities.getValueOfEntry(itemTypeOfAtom, AlloyUtilities.RESOURCE)); + } else + key = AlloyUtilities.getAtomNameById(secondAtomType.getLabel()); + relationsOfMarker.put(key, fieldType.getLabel()); + } + } + } + return relationsOfMarker; + } + + public static Map getReasonedRelationsOfSSAtom(final String sigTypeName, + final int index) { + final String id = AlloyUtilities.getAtomId(sigTypeName, index); + + final Map relationsOfMarker = new HashMap<>(); + final ArrayList fieldTypesOfSelectedMarkerType = + AlloyUtilities.getFieldTypesList(sigTypeName, false); + + for (final FieldType fieldType : fieldTypesOfSelectedMarkerType) { + final EList tupleTypes = fieldType.getTuple(); + for (final TupleType tupleType : tupleTypes) { + final EList atoms = tupleType.getAtom(); + final AtomType secondAtomType = atoms.get(1); + if (secondAtomType.getLabel().equals(id) && tupleType.isReasoned()) { + final AtomType firstAtomType = atoms.get(0); + Object key = null; + if (!firstAtomType.isReasoned()) { + final ItemType itemTypeOfAtom = AlloyUtilities.getItemById(firstAtomType.getLabel()); + key = MarkUtilities.getiMarker(firstAtomType.getLabel(), + AlloyUtilities.getValueOfEntry(itemTypeOfAtom, AlloyUtilities.RESOURCE)); + } else + key = AlloyUtilities.getAtomNameById(firstAtomType.getLabel()); + relationsOfMarker.put(key, fieldType.getLabel()); + } + } + } + return relationsOfMarker; + } + + public static Map getRelationsOfSecondSideMarker(IMarker selectedMarker) { + selectedMarker = MarkUtilities.getLeaderOfMarker(selectedMarker); + + final Map relationsOfMarker = new HashMap<>(); + if (MarkUtilities.getType(selectedMarker) == null) { + return relationsOfMarker; + } + final ArrayList fieldTypesOfSelectedMarkerType = + AlloyUtilities.getFieldTypesList(MarkUtilities.getType(selectedMarker), false); + final String selectedMarkerId = MarkUtilities.getSourceId(selectedMarker); + + for (final FieldType fieldType : fieldTypesOfSelectedMarkerType) { + final EList tupleTypes = fieldType.getTuple(); + for (final TupleType tupleType : tupleTypes) { + final EList atoms = tupleType.getAtom(); + final AtomType firstAtomType = atoms.get(1); + if (firstAtomType.getLabel().equals(selectedMarkerId)) { + final AtomType secondAtomType = atoms.get(0); + final ItemType itemTypeOfAtom = AlloyUtilities.getItemById(secondAtomType.getLabel()); + if (itemTypeOfAtom == null) + continue; + final IMarker toMarker = MarkUtilities.getiMarker(secondAtomType.getLabel(), + AlloyUtilities.getValueOfEntry(itemTypeOfAtom, AlloyUtilities.RESOURCE)); + relationsOfMarker.put(toMarker, fieldType.getLabel()); + } + } + } + + return relationsOfMarker; + } + + /** + * This method is used to get Relations + * + * @return + */ + public static RelationType getRelationType(final DocumentRoot documentRoot) { + return documentRoot.getAlloy().getRelation(); + } + + public static ArrayList getRelationTypesForFirstSide(final String typeName) { + final ArrayList relationTypeNames = new ArrayList<>(); + + final int typeId = AlloyUtilities.getSigTypeIdByName(typeName); + + if (typeId == -1) { + return null; + } + + final ArrayList parentIds = AlloyUtilities.getAllParentIds(typeId); + + final EList fieldTypes = AlloyUtilities.getFieldTypes(); + + for (final FieldType fieldType : fieldTypes) { + for (final Integer parentId : parentIds) { + if (fieldType.getParentID() == parentId) { + relationTypeNames.add(fieldType.getLabel()); + break; + } + } + } + + return relationTypeNames; + } + + public static ArrayList getSecondSideAtomsBySourceIdOfFirstSide( + final DocumentRoot documentRoot, final String sourceId) { + final AlloyType alloyType = documentRoot.getAlloy(); + final ArrayList atoms = new ArrayList<>(); + + final EList listOfField = alloyType.getInstance().getField(); + for (final FieldType fieldType : listOfField) { + final EList tuples = fieldType.getTuple(); + for (final TupleType tupleType : tuples) { + if (tupleType.getAtom().get(0).getLabel().equals(sourceId)) { + atoms.add(tupleType.getAtom().get(1)); + } + } + } + return atoms; + } + + public static ArrayList getSecondSideMarkerIdsByMarkerAndRelation(IMarker marker, + final String relation) { + marker = MarkUtilities.getLeaderOfMarker(marker); + + final EList fieldTypes = AlloyUtilities.getFieldTypes(); + + final String markerId = MarkUtilities.getSourceId(marker); + + final ArrayList suitableMarkers = new ArrayList<>(); + + for (final FieldType fieldType : fieldTypes) { + if (fieldType.getLabel().equals(relation)) { + final EList tuples = fieldType.getTuple(); + for (final TupleType tupleType : tuples) { + final EList atoms = tupleType.getAtom(); + if (atoms.get(0).getLabel().equals(markerId)) { + final ItemType itemType = AlloyUtilities.getItemById(atoms.get(1).getLabel()); + final IMarker suitableMarker = MarkUtilities.getiMarker(itemType.getId(), + AlloyUtilities.getValueOfEntry(itemType, AlloyUtilities.RESOURCE)); + suitableMarkers.add(suitableMarker); + } + } + break; + } + } + + return suitableMarkers; + } + + /** + * This method is used to get Target List of iMarker. Also iMarker doesn't contain any marker + * type. + * + * @param iMarker + * @return + */ + public static ArrayList getSecondSideMarkerIdsByMarkerAndRelationV2(IMarker iMarker) { + iMarker = MarkUtilities.getLeaderOfMarker(iMarker); + + final DocumentRoot documentRoot = AlloyUtilities.getDocumentRoot(); + + final RelationType relationType = AlloyUtilities.getRelationType(documentRoot); + final EList tupleTypes = relationType.getTuple(); + + final String markerId = MarkUtilities.getSourceId(iMarker); + + final ArrayList suitableMarkers = new ArrayList<>(); + + for (final TupleType tupleType : tupleTypes) { + final EList atoms = tupleType.getAtom(); + if (atoms.get(0).getLabel().equals(markerId)) { + final ItemType itemType = AlloyUtilities.getItemById(atoms.get(1).getLabel()); + final IMarker suitableMarker = MarkUtilities.getiMarker(itemType.getId(), + AlloyUtilities.getValueOfEntry(itemType, AlloyUtilities.RESOURCE)); + suitableMarkers.add(suitableMarker); + } + } + + return suitableMarkers; + } + + public static String getSigNameById(final int id) { + final SigType sigType = AlloyUtilities.getSigTypeById(id); + return sigType.getLabel().substring(sigType.getLabel().indexOf("/") + 1); + } + + public static String getSigNameById(final int id, final DocumentRoot documentRoot) { + final SigType sigType = AlloyUtilities.getSigTypeById(id, documentRoot); + return sigType.getLabel().substring(sigType.getLabel().indexOf("/") + 1); + } + + public static SigType getSigTypeById(final int id) { + final DocumentRoot documentRoot = AlloyUtilities.getDocumentRoot(); + final EList sigTypes = AlloyUtilities.getSigTypes(documentRoot); + + for (final SigType sigType : sigTypes) { + if (id == sigType.getID()) { + return sigType; + } + } + return null; + } + + public static SigType getSigTypeById(final int id, final DocumentRoot documentRoot) { + final EList sigTypes = AlloyUtilities.getSigTypes(documentRoot); + + for (final SigType sigType : sigTypes) { + if (id == sigType.getID()) { + return sigType; + } + } + return null; + } + + public static int getSigTypeIdByName(final String typeName) { + int id = -1; + final DocumentRoot documentRoot = AlloyUtilities.getDocumentRoot(); + final EList sigTypes = AlloyUtilities.getSigTypes(documentRoot); + + for (final SigType sigType : sigTypes) { + if (typeName.equals(sigType.getLabel().substring(sigType.getLabel().indexOf("/") + 1))) { + id = sigType.getID(); + break; + } + } + return id; + } + + public static int getSigTypeIdByName(final String typeName, final DocumentRoot documentRoot) { + int id = -1; + final EList sigTypes = AlloyUtilities.getSigTypes(documentRoot); + + for (final SigType sigType : sigTypes) { + if (typeName.equals(sigType.getLabel().substring(sigType.getLabel().indexOf("/") + 1))) { + id = sigType.getID(); + break; + } + } + return id; + } + + public static ArrayList getSigTypeListByParentId(final int id) { + final ArrayList suitableSigTypes = new ArrayList<>(); + + final DocumentRoot documentRoot = AlloyUtilities.getDocumentRoot(); + final EList sigTypes = AlloyUtilities.getSigTypes(documentRoot); + + for (final SigType sigType : sigTypes) { + if (sigType.getParentID() == id) { + suitableSigTypes.add(sigType); + } + } + + return suitableSigTypes; + } + + public static EList getSigTypes(final DocumentRoot documentRoot) { + return documentRoot.getAlloy().getInstance().getSig(); + } + + /** + * This method is used to get source marker list of iMarker. Also iMarker doesn't contain any + * marker type. + * + * @param iMarker + * @return + */ + public static ArrayList getSourcesOfMarkerAtRelations(IMarker iMarker) { + iMarker = MarkUtilities.getLeaderOfMarker(iMarker); + + final ArrayList sources = new ArrayList<>(); + final DocumentRoot documentRoot = AlloyUtilities.getDocumentRoot(); + + final RelationType relationType = AlloyUtilities.getRelationType(documentRoot); + final String selectedMarkerId = MarkUtilities.getSourceId(iMarker); + + final EList tupleTypes = relationType.getTuple(); + for (final TupleType tupleType : tupleTypes) { + final EList atoms = tupleType.getAtom(); + final AtomType firstAtomType = atoms.get(0); + final AtomType secondAtomType = atoms.get(1); + if (secondAtomType.getLabel().equals(selectedMarkerId)) { + final ItemType itemTypeOfAtom = AlloyUtilities.getItemById(firstAtomType.getLabel()); + final IMarker toMarker = MarkUtilities.getiMarker(firstAtomType.getLabel(), + AlloyUtilities.getValueOfEntry(itemTypeOfAtom, AlloyUtilities.RESOURCE)); + sources.add(toMarker); + } + } + return sources; + } + + public static ArrayList getSuitableSecondSideTypesOfRelation(final String relationName, + final String firstSideType) { + final EList fields = AlloyUtilities.getFieldTypes(); + + final ArrayList suitableRelationNames = new ArrayList<>(); + + final int firstSideTypeId = AlloyUtilities.getSigTypeIdByName(firstSideType); + final ArrayList parentIdsOfFirstSideType = + AlloyUtilities.getAllParentIds(firstSideTypeId); + + final ArrayList secondSideTypeIds = new ArrayList<>(); + for (final FieldType fieldType : fields) { + if (fieldType.getLabel().equals(relationName) && parentIdsOfFirstSideType + .contains(fieldType.getTypes().get(0).getType().get(0).getID())) { + secondSideTypeIds.add(fieldType.getTypes().get(0).getType().get(1).getID()); + } + } + + final ArrayList suitableIds = new ArrayList<>(); + for (final Integer secondSideTypeId : secondSideTypeIds) { + suitableIds.addAll(AlloyUtilities.getAllChildIds(secondSideTypeId)); + } + + for (final Integer suitableId : suitableIds) { + suitableRelationNames.add(AlloyUtilities.getSigTypeById(suitableId).getLabel()); + } + + return suitableRelationNames; + } + + /** + * This method is used to when iMarker has marker type and we want to find it's sources both have + * marker type or not. + * + * @param iMarker + * @return + */ + public static ArrayList getSumSources(IMarker iMarker) { + iMarker = MarkUtilities.getLeaderOfMarker(iMarker); + + final Map sourcesMap = AlloyUtilities.getRelationsOfSecondSideMarker(iMarker); + final ArrayList sourcesList = AlloyUtilities.getSourcesOfMarkerAtRelations(iMarker); + + final ArrayList resultList = new ArrayList<>(sourcesList); + + final Set sourceMarkers = sourcesMap.keySet(); + final Iterator iter = sourceMarkers.iterator(); + while (iter.hasNext()) { + final IMarker iMarkerSet = iter.next(); + if (!sourcesList.contains(iMarkerSet)) { + resultList.add(iMarkerSet); + } + } + return resultList; + } + + /** + * This method is used to get target marker list of iMarker. Also iMarker doesn't contain any + * marker type. + * + * @param iMarker + * @return + */ + public static ArrayList getTargetsOfMarkerAtRelations(IMarker iMarker) { + iMarker = MarkUtilities.getLeaderOfMarker(iMarker); + + final ArrayList targets = new ArrayList<>(); + final DocumentRoot documentRoot = AlloyUtilities.getDocumentRoot(); + + final RelationType relationType = AlloyUtilities.getRelationType(documentRoot); + final String selectedMarkerId = MarkUtilities.getSourceId(iMarker); + + final EList tupleTypes = relationType.getTuple(); + for (final TupleType tupleType : tupleTypes) { + final EList atoms = tupleType.getAtom(); + final AtomType firstAtomType = atoms.get(0); + if (firstAtomType.getLabel().equals(selectedMarkerId)) { + final AtomType secondAtomType = atoms.get(1); + final ItemType itemTypeOfAtom = AlloyUtilities.getItemById(secondAtomType.getLabel()); + final IMarker toMarker = MarkUtilities.getiMarker(secondAtomType.getLabel(), + AlloyUtilities.getValueOfEntry(itemTypeOfAtom, AlloyUtilities.RESOURCE)); + targets.add(toMarker); + } + } + + return targets; + } + + public static URI getUri() { + return URI.createFileURI(AlloyUtilities.getLocation()); + } + + public static String getValueOfEntry(final ItemType itemType, final String key) { + String value = null; + final EList entries = itemType.getEntry(); + + for (final EntryType entryType : entries) { + if (key.equals(entryType.getKey())) { + value = entryType.getValue(); + break; + } + } + return value; + } + + public static boolean isAnyReasoned() { + + for (final FieldType fieldType : AlloyUtilities.getFieldTypes()) { + for (final TupleType tupleType : fieldType.getTuple()) { + if (tupleType.isReasoned()) { + return true; + } + } + } + + return false; + } + + public static boolean isContainTuple(final FieldType fieldType, + final TupleType searchedTupleType) { + final EList tuples = fieldType.getTuple(); + final EList searchedAtoms = searchedTupleType.getAtom(); + for (final TupleType tupleType : tuples) { + final EList atoms = tupleType.getAtom(); + if (atoms.get(0).getLabel().equals(searchedAtoms.get(0).getLabel()) + && atoms.get(1).getLabel().equals(searchedAtoms.get(1).getLabel())) { + return true; + } + } + return false; + } + + /** + * @return true if Alloy file parsed and XML file is constructed , false if doesn't. + */ + public static boolean isExists() { + final Path path = new Path(AlloyUtilities.getLocation()); + return path.toFile().exists() ? true : false; + } + + public static int isTypeInSig(final String sigTypeName) { + + final SigType sigType = + AlloyUtilities.getSigTypeById(AlloyUtilities.getSigTypeIdByName(sigTypeName)); + + return sigType.getType().isEmpty() ? -1 : sigType.getType().get(0).getID(); + } + + public static void removeAllRelationsOfMarker(IMarker marker) { + marker = MarkUtilities.getLeaderOfMarker(marker); + + if (marker != null) { + Iterator> iter; + final Map relationsOfFirstSide = + AlloyUtilities.getRelationsOfFirstSideMarker(marker); + iter = relationsOfFirstSide.entrySet().iterator(); + + while (iter.hasNext()) { + @SuppressWarnings("rawtypes") + final Map.Entry pair = iter.next(); + AlloyUtilities.removeFieldOfMarkers(marker, (IMarker) pair.getKey(), + (String) pair.getValue()); + } + + final Map relationsOfSecondSide = + AlloyUtilities.getRelationsOfSecondSideMarker(marker); + iter = relationsOfSecondSide.entrySet().iterator(); + + while (iter.hasNext()) { + @SuppressWarnings("rawtypes") + final Map.Entry pair = iter.next(); + AlloyUtilities.removeFieldOfMarkers((IMarker) pair.getKey(), marker, + (String) pair.getValue()); + } + } + } + + public static void removeFieldOfMarkers(IMarker fromMarker, IMarker toMarker, + final String relationName) { + fromMarker = MarkUtilities.getLeaderOfMarker(fromMarker); + toMarker = MarkUtilities.getLeaderOfMarker(toMarker); + + final DocumentRoot documentRoot = AlloyUtilities.getDocumentRoot(); + final EList fieldTypes = documentRoot.getAlloy().getInstance().getField(); + + final String fromMarkerId = MarkUtilities.getSourceId(fromMarker); + final String toMarkerId = MarkUtilities.getSourceId(toMarker); + + for (final FieldType fieldType : fieldTypes) { + if (fieldType.getLabel().equals(relationName)) { + final Iterator tupleTypesIter = fieldType.getTuple().iterator(); + while (tupleTypesIter.hasNext()) { + final EList atoms = tupleTypesIter.next().getAtom(); + if (atoms.get(0).getLabel().equals(fromMarkerId) + && atoms.get(1).getLabel().equals(toMarkerId)) { + tupleTypesIter.remove(); + AlloyUtilities.writeDocumentRoot(documentRoot); + return; + } + } + } + } + } + + /** + * This method is used to when fromMarker doesn't map toMarker any more. + * + * @param fromMarker + * @param toMarker + */ + public static void removeMappingFromRelationType(IMarker fromMarker, IMarker toMarker) { + fromMarker = MarkUtilities.getLeaderOfMarker(fromMarker); + toMarker = MarkUtilities.getLeaderOfMarker(toMarker); + + final DocumentRoot documentRoot = AlloyUtilities.getDocumentRoot(); + final RelationType relationType = AlloyUtilities.getRelationType(documentRoot); + + final String fromMarkerId = MarkUtilities.getSourceId(fromMarker); + final String toMarkerId = MarkUtilities.getSourceId(toMarker); + + final Iterator iter = relationType.getTuple().iterator(); + while (iter.hasNext()) { + final EList atoms = iter.next().getAtom(); + if (atoms.get(0).getLabel().equals(fromMarkerId) + && atoms.get(1).getLabel().equals(toMarkerId)) { + iter.remove(); + AlloyUtilities.writeDocumentRoot(documentRoot); + return; + } + } + } + + public static void removeMarkerFromRepository(final IMarker marker) { + final DocumentRoot documentRoot = AlloyUtilities.getDocumentRoot(); + + final int itemTypeIndex = AlloyUtilities.findItemTypeInRepository(marker); + if (itemTypeIndex == -1) { + return; + } + documentRoot.getAlloy().getRepository().getItem().remove(itemTypeIndex); + + AlloyUtilities.writeDocumentRoot(documentRoot); + } + + /** + * Removes relation of given marker + * + * @param marker which will be deleted relation of + */ + public static void removeRelationOfMarker(IMarker marker) { + marker = MarkUtilities.getLeaderOfMarker(marker); + + final DocumentRoot documentRoot = AlloyUtilities.getDocumentRoot(); + + final String id = MarkUtilities.getSourceId(marker); + final EList tupleTypes = AlloyUtilities.getRelationType(documentRoot).getTuple(); + final Iterator iter = tupleTypes.iterator(); + while (iter.hasNext()) { + final TupleType tupleType = iter.next(); + final AtomType firstSideAtom = tupleType.getAtom().get(0); + final AtomType secondSideAtom = tupleType.getAtom().get(1); + if (firstSideAtom.getLabel().equals(id) || secondSideAtom.getLabel().equals(id)) { + iter.remove(); + } + } + AlloyUtilities.writeDocumentRoot(documentRoot); + } + + public static void removeTypeFromMarker(final IMarker marker) { + if (MarkUtilities.compare(marker, MarkUtilities.getLeaderOfMarker(marker))) { + AlloyUtilities.removeAllRelationsOfMarker(marker); + } + + if (MarkUtilities.getType(marker) == null || MarkUtilities.getType(marker).isEmpty()) { + return; + } + + final DocumentRoot documentRoot = AlloyUtilities.getDocumentRoot(); + + final String type = MarkUtilities.getType(marker); + + final String markerId = MarkUtilities.getSourceId(marker); + + final int idOfTypeSigInSigType = AlloyUtilities.isTypeInSig(type); + + final EList sigs = documentRoot.getAlloy().getInstance().getSig(); + + for (final SigType sigType : sigs) { + if (type.equals(sigType.getLabel().substring(sigType.getLabel().indexOf("/") + 1)) + || idOfTypeSigInSigType != -1 && sigType.getID() == idOfTypeSigInSigType) { + final Iterator atomsIter = sigType.getAtom().iterator(); + while (atomsIter.hasNext()) { + final AtomType atomType = atomsIter.next(); + if (atomType.getLabel().equals(markerId)) { + atomsIter.remove(); + break; + } + } + } + } + + AlloyUtilities.writeDocumentRoot(documentRoot); + } + + public static void resetReasoned(IMarker fromMarker, IMarker toMarker, + final String relationName) { + fromMarker = MarkUtilities.getLeaderOfMarker(fromMarker); + toMarker = MarkUtilities.getLeaderOfMarker(toMarker); + + final DocumentRoot documentRoot = AlloyUtilities.getDocumentRoot(); + final EList fieldTypes = documentRoot.getAlloy().getInstance().getField(); + + final String fromMarkerId = MarkUtilities.getSourceId(fromMarker); + final String toMarkerId = MarkUtilities.getSourceId(toMarker); + + for (final FieldType fieldType : fieldTypes) { + if (fieldType.getLabel().equals(relationName)) { + final Iterator tupleTypesIter = fieldType.getTuple().iterator(); + while (tupleTypesIter.hasNext()) { + final TupleType tupleType = tupleTypesIter.next(); + final EList atoms = tupleType.getAtom(); + if (atoms.get(0).getLabel().equals(fromMarkerId) + && atoms.get(1).getLabel().equals(toMarkerId)) { + tupleType.setReasoned(false); + AlloyUtilities.writeDocumentRoot(documentRoot); + return; + } + } + } + } + } + + public static void setAllImpactsAndChanges(final AlloyInstance instance) { + final Iterator iter = instance.atom2sets.keySet().iterator(); + + final ArrayList changedAtoms = AlloyUtilities.getChangedAtoms(); + final ArrayList> impactedAtoms = AlloyUtilities.getImpactedAtoms(); + while (iter.hasNext()) { + final AlloyAtom alloyAtom = iter.next(); + final String alloyAtomName = alloyAtom.getOriginalName(); + if (changedAtoms.contains(alloyAtomName)) { + alloyAtom.changed = true; + } + for (final ArrayList impactedAtom : impactedAtoms) { + if (impactedAtom.get(0).equals(alloyAtomName)) { + alloyAtom.impacted.add(impactedAtom.get(1)); + } + } + } + } + + public static void setAllReasonedAtoms(final AlloyInstance instance) { + final Iterator iter = instance.atom2sets.keySet().iterator(); + + final ArrayList reasonedAtoms = AlloyUtilities.getReasonedAtoms(); + while (iter.hasNext()) { + final AlloyAtom alloyAtom = iter.next(); + final String alloyAtomName = alloyAtom.getOriginalName(); + if (reasonedAtoms.contains(alloyAtomName)) { + alloyAtom.isDashed = true; + } + } + } + + public static void setAllReasonedTuples(final AlloyInstance instance) { + final EList fieldList = AlloyUtilities.getFieldTypes(); + final Map reasonedTuplesInFields = new HashMap<>(); + + for (final FieldType fieldType : fieldList) { + final EList tuples = fieldType.getTuple(); + for (final TupleType tupleType : tuples) { + if (tupleType.isReasoned()) { + reasonedTuplesInFields.put(tupleType, fieldType); + } + } + } + + for (final Entry reasonedEntry : reasonedTuplesInFields.entrySet()) { + final TupleType tupleType = reasonedEntry.getKey(); + final FieldType fieldType = reasonedEntry.getValue(); + for (final Entry> entry : instance.rel2tuples.entrySet()) { + if (entry.getKey().getName().equals(fieldType.getLabel())) { + final Iterator tupleSetIter = entry.getValue().iterator(); + while (tupleSetIter.hasNext()) { + final AlloyTuple alloyTuple = tupleSetIter.next(); + boolean tuplesREqual = true; + for (int i = 0; i < tupleType.getAtom().size(); i++) { + if (!AlloyUtilities.getAtomNameById(tupleType.getAtom().get(i).getLabel()) + .equals(alloyTuple.getAtoms().get(i).getOriginalName())) { + tuplesREqual = false; + break; + } + } + if (tuplesREqual) { + alloyTuple.isDashed = true; + } + } + } + } + } + } + + private static void setEntries(final ItemType itemType, final IMarker marker) { + if (MarkUtilities.getPath(marker) != null) { + final EntryType resourceEntry = persistenceFactory.eINSTANCE.createEntryType(); + resourceEntry.setKey(AlloyUtilities.RESOURCE); + resourceEntry.setValue(MarkUtilities.getPath(marker)); + itemType.getEntry().add(resourceEntry); + } + if (MarkUtilities.getStart(marker) != -1) { + final EntryType offsetEntry = persistenceFactory.eINSTANCE.createEntryType(); + offsetEntry.setKey(AlloyUtilities.OFFSET); + offsetEntry.setValue(Integer.toString(MarkUtilities.getStart(marker))); + itemType.getEntry().add(offsetEntry); + } + if (MarkUtilities.getText(marker) != null) { + final EntryType textEntry = persistenceFactory.eINSTANCE.createEntryType(); + textEntry.setKey(AlloyUtilities.TEXT); + textEntry.setValue(MarkUtilities.getText(marker)); + itemType.getEntry().add(textEntry); + } + if (MarkUtilities.getUri(marker) != null) { + final EntryType uriEntry = persistenceFactory.eINSTANCE.createEntryType(); + uriEntry.setKey(AlloyUtilities.MARKER_URI); + uriEntry.setValue(MarkUtilities.getUri(marker)); + itemType.getEntry().add(uriEntry); + } + if (MarkUtilities.getLeaderId(marker) != null) { + final EntryType leaderIdEntry = persistenceFactory.eINSTANCE.createEntryType(); + leaderIdEntry.setKey(AlloyUtilities.LEADER_ID); + leaderIdEntry.setValue(MarkUtilities.getLeaderId(marker)); + itemType.getEntry().add(leaderIdEntry); + } + if (MarkUtilities.getGroupId(marker) != null) { + final EntryType groupIdEntry = persistenceFactory.eINSTANCE.createEntryType(); + groupIdEntry.setKey(AlloyUtilities.GROUP_ID); + groupIdEntry.setValue(MarkUtilities.getGroupId(marker)); + itemType.getEntry().add(groupIdEntry); + } + } + + public static void setImpactAndChanged(final IMarker marker) { + final DocumentRoot documentRoot = AlloyUtilities.getDocumentRoot(); + final AlloyType alloyType = documentRoot.getAlloy(); + + if (MarkUtilities.getType(marker) != null) { + final EList listOfSigs = alloyType.getInstance().getSig(); + for (final SigType sigType : listOfSigs) { + final EList atoms = sigType.getAtom(); + for (final AtomType atomType : atoms) { + if (atomType.getLabel().equals(MarkUtilities.getSourceId(marker))) { + atomType.setChanged(true); + } + } + } + final EList listOfField = alloyType.getInstance().getField(); + for (final FieldType fieldType : listOfField) { + final EList tuples = fieldType.getTuple(); + for (final TupleType tupleType : tuples) { + if (tupleType.getAtom().get(0).getLabel().equals(MarkUtilities.getSourceId(marker))) { + tupleType.getAtom().get(1).setImpact(true); + } + } + } + AlloyUtilities.writeDocumentRoot(documentRoot); + } + } + + public static void setMetamodel(final String filename, final boolean state) { + final DocumentRoot documentRoot = AlloyUtilities.getDocumentRootForMetaModel(filename); + if (state == true) { + documentRoot.getAlloy().getInstance().setMetamodel("yes"); + } else { + documentRoot.getAlloy().getInstance().setMetamodel(null); + } + + AlloyUtilities.writeDocumentRoot(documentRoot); + + } + + public static void unsetChanged(final IMarker fromMarker) { + final DocumentRoot documentRoot = AlloyUtilities.getDocumentRoot(); + final String sourceIdOfFromMarker = MarkUtilities.getSourceId(fromMarker); + final AtomType atom = + AlloyUtilities.getAtomTypeBySourceIdFromSig(documentRoot, sourceIdOfFromMarker); + atom.setChanged(null); + AlloyUtilities.writeDocumentRoot(documentRoot); + } + + public static void unsetChangedAndAllImpacted(final IMarker changedMarker) { + final DocumentRoot documentRoot = AlloyUtilities.getDocumentRoot(); + final String sourceIdOfChangedMarker = MarkUtilities.getSourceId(changedMarker); + + final AtomType atom = + AlloyUtilities.getAtomTypeBySourceIdFromSig(documentRoot, sourceIdOfChangedMarker); + final ArrayList secondSideAtoms = AlloyUtilities + .getSecondSideAtomsBySourceIdOfFirstSide(documentRoot, sourceIdOfChangedMarker); + + for (final AtomType atomType : secondSideAtoms) { + if (atomType.getImpact() != null && atomType.getImpact()) { + atomType.setImpact(null); + } + } + + atom.setChanged(null); + + AlloyUtilities.writeDocumentRoot(documentRoot); + } + + public static void unsetImpactAndChanged(final IMarker fromMarker, final IMarker toMarker) { + final DocumentRoot documentRoot = AlloyUtilities.getDocumentRoot(); + final String sourceIdOfFromMarker = MarkUtilities.getSourceId(fromMarker); + final String sourceIdOfToMarker = MarkUtilities.getSourceId(toMarker); + + final AtomType atom = + AlloyUtilities.getAtomTypeBySourceIdFromSig(documentRoot, sourceIdOfFromMarker); + final ArrayList secondSideAtoms = + AlloyUtilities.getSecondSideAtomsBySourceIdOfFirstSide(documentRoot, sourceIdOfFromMarker); + + int impactCount = 0; + for (final AtomType atomType : secondSideAtoms) { + if (atomType.getImpact() != null) { + impactCount++; + if (atomType.getLabel().equals(sourceIdOfToMarker)) { + atomType.setImpact(null); + } + } + } + if (impactCount == 1) { + atom.setChanged(null); + } + + AlloyUtilities.writeDocumentRoot(documentRoot); + } + + /** + * This method doesn't change the sig and relation definitions, also doesn't change actual atoms + * status. + * + * @param file + * @param content + */ + public static void updateSpec(final String file, final String content) { + final DocumentRoot documentRoot = AlloyUtilities.getDocumentRoot(); + final EList sources = documentRoot.getAlloy().getSource(); + for (final SourceType sourceType : sources) { + if (sourceType.getFilename().equals(file)) { + sourceType.setContent(content); + AlloyUtilities.writeDocumentRoot(documentRoot); + return; + } + } + } + + @SuppressWarnings("unchecked") + public static void writeDocumentRoot(final DocumentRoot documentRoot) { + @SuppressWarnings("rawtypes") + final ModelIO modelIO = new ModelIO<>(); + modelIO.write(AlloyUtilities.getUri(), documentRoot); + } + + @SuppressWarnings("unchecked") + public static void writeDocumentRootForMetamodel(final DocumentRoot documentRoot, + final String filename) { + @SuppressWarnings("rawtypes") + final ModelIO modelIO = new ModelIO<>(); + modelIO.write(URI.createFileURI(AlloyUtilities.getLocationForMetamodel(filename)), + documentRoot); + } + + public static AtomType cloneAtomType(final AtomType atomType) { + final AtomType clone = persistenceFactory.eINSTANCE.createAtomType(); + clone.setBound(atomType.getBound()); + clone.setChanged(atomType.getChanged()); + clone.setImpact(atomType.getImpact()); + clone.setLabel(atomType.getLabel()); + clone.setReasoned(atomType.isReasoned()); + clone.setValue(atomType.getValue()); + return clone; + } + + public static String getSigNameByAtomId(final String atomId, final DocumentRoot documentRoot) { + for (final SigType sigType : documentRoot.getAlloy().getInstance().getSig()) { + for (final AtomType atomType : sigType.getAtom()) { + if (atomType.getLabel().equals(atomId)) { + return sigType.getLabel().substring(sigType.getLabel().indexOf("/") + 1); + } + } + } + return null; + } +} diff --git a/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/internal/CreateMarkerWithType.java b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/internal/CreateMarkerWithType.java new file mode 100644 index 00000000..256d2b4b --- /dev/null +++ b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/internal/CreateMarkerWithType.java @@ -0,0 +1,39 @@ +/******************************************************************************* + * Copyright (c) 2015 UNIT Information Technologies R&D Ltd All rights reserved. This program and + * the accompanying materials are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: Ferhat Erata - initial API and implementation H. Emre Kirmizi - initial API and + * implementation Serhat Celik - initial API and implementation U. Anil Ozturk - initial API and + * implementation + *******************************************************************************/ +package eu.modelwriter.configuration.internal; + +import org.eclipse.core.resources.IMarker; +import org.eclipse.core.resources.IResource; +import org.eclipse.jface.text.ITextSelection; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.ITreeSelection; + +import eu.modelwriter.marker.internal.MarkUtilities; +import eu.modelwriter.marker.internal.MarkerFactory; + +public class CreateMarkerWithType { + + public static IMarker createMarker(IResource resource, ISelection selection, String type) { + IMarker marker = null; + if (selection instanceof ITextSelection) { + marker = MarkerFactory.createMarker(resource, (ITextSelection) selection); + } else if (selection instanceof ITreeSelection) { + marker = MarkerFactory.createMarker(resource, (ITreeSelection) selection); + } + + MarkUtilities.setType(marker, type); + + AlloyUtilities.addTypeToMarker(marker); + AlloyUtilities.addMarkerToRepository(marker); + + return marker; + } +} diff --git a/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/internal/EcoreUtilities.java b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/internal/EcoreUtilities.java new file mode 100644 index 00000000..2dffd419 --- /dev/null +++ b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/internal/EcoreUtilities.java @@ -0,0 +1,267 @@ +package eu.modelwriter.configuration.internal; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.eclipse.emf.common.util.URI; +import org.eclipse.emf.ecore.EAttribute; +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EClassifier; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EPackage; +import org.eclipse.emf.ecore.EReference; +import org.eclipse.emf.ecore.EStructuralFeature; +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.emf.ecore.resource.ResourceSet; +import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl; +import org.eclipse.emf.ecore.xmi.XMLResource; +import org.eclipse.emf.ecore.xmi.XMLSave; +import org.eclipse.emf.ecore.xmi.impl.XMIResourceFactoryImpl; +import org.eclipse.emf.ecore.xmi.impl.XMLResourceFactoryImpl; + +public class EcoreUtilities { + + public static final String EOBJECT_URI = "relative_eobject_uri"; + public static final String ROOT_URI = "relative_ecore_root_uri"; + + public static XMLSave.XMLTypeInfo noTypeInfo; + + static { + noTypeInfo = new XMLSave.XMLTypeInfo() { + + @Override + public boolean shouldSaveType(EClass objectType, EClass featureType, + EStructuralFeature feature) { + return false; + } + + @Override + public boolean shouldSaveType(EClass objectType, EClassifier featureType, + EStructuralFeature feature) { + return false; + } + }; + } + + /** + * Gets root EObject of given xmi file path + * + * @param xmiFileFullPath file path of xmi file + * @return root @EObject + * @throws IOException + */ + public static EObject getRootObject(final String xmiFileFullPath) throws IOException { + return getRootObject(URI.createPlatformResourceURI(xmiFileFullPath, true)); + } + + /** + * Gets root EObject of given xmi file path + * + * @param xmiFileFullPath file path of xmi file + * @return root @EObject + * @throws IOException + */ + public static EObject getRootObject(URI uri) throws IOException { + @SuppressWarnings("rawtypes") + final ModelIO modelIO = new ModelIO<>(); + @SuppressWarnings("rawtypes") + final List list; + + list = modelIO.read(uri); + + if (list.isEmpty()) { + return null; + } + final EObject rootObject = (EObject) list.get(0); + return rootObject; + } + + /** + * Gets root EObject of given xmi file path + * + * @param xmiFileFullPath file path of xmi file + * @return root @EObject + * @throws IOException + */ + @SuppressWarnings({"rawtypes", "unchecked"}) + public static Resource loadInstanceRoot(final String xmiFileFullPath) throws IOException { + Map options = new HashMap(); + options.put(XMLResource.OPTION_SCHEMA_LOCATION, Boolean.TRUE); + + ResourceSetImpl resourceSet = new ResourceSetImpl(); + resourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap() + .put(Resource.Factory.Registry.DEFAULT_EXTENSION, new XMIResourceFactoryImpl()); + Resource resource = + resourceSet.getResource(URI.createPlatformResourceURI(xmiFileFullPath, true), true); + resource.load(options); + if (resource.isLoaded()) { + return resource; + } + return null; + } + + /** + * + * @param eObject possibly the package to find all @EClass's under it + * @return @List of @EClass names + */ + public static List getAllEClassNames(EObject eObject) { + List classes = new ArrayList<>(); + recursiveGetEClasses(eObject, classes); + List classNames = new ArrayList<>(); + for (EClass eClass : classes) { + classNames.add(eClass.getName()); + } + return classNames; + } + + /** + * + * @param eObject possibly the package to find all @EClass under it + * @return @List of all @EClass + */ + public static List getAllEClass(EObject eObject) { + List classes = new ArrayList<>(); + recursiveGetEClasses(eObject, classes); + return classes; + } + + private static void recursiveGetEClasses(EObject object, List classes) { + for (EObject eObject : object.eContents()) { + if (eObject instanceof EClass) + classes.add((EClass) eObject); + else if (eObject instanceof EPackage) + recursiveGetEClasses(eObject, classes); + } + } + + /** + * + * @param @EObject object to be set + * @param name reference name + * @param newVal new value + */ + public static void eSetAttributeByName(EObject eObject, String name, Object newVal) { + for (EAttribute eAttribute : eObject.eClass().getEAllAttributes()) { + if (eAttribute.getName().equals(name)) { + eObject.eSet(eAttribute, newVal); + break; + } + } + } + + /** + * + * @param @EObject object to be set + * @param name reference name + * @param newVal new value + */ + @SuppressWarnings({"unchecked", "rawtypes"}) + public static void eSetReferenceByName(EObject eObject, String name, Object newVal) { + for (EReference eReference : eObject.eClass().getEAllReferences()) { + if (eReference.getName().equals(name)) { + if (eReference.isMany()) { + ((List) eObject.eGet(eReference)).add(newVal); + } else { + eObject.eSet(eReference, newVal); + } + break; + } + } + } + + /** + * + * @param root @EObject + * @param className class name to be find + * @return + */ + public static EClass findEClass(EObject root, String className) { + List allEClass = getAllEClass(root); + return allEClass.stream().filter(c -> c.getName().equals(className)).findFirst().orElse(null); + } + + /** + * + * @param container + * @param eObject + * @return + */ + public static EReference getContainmentEReference(EObject container, EObject eObject) { + for (EReference eReference : container.eClass().getEAllReferences()) { + if ((eObject.eClass().getName().equals(eReference.getEReferenceType().getName()) + || eObject.eClass().getEAllSuperTypes().stream() + .anyMatch(s -> s.getName().equals(eReference.getEReferenceType().getName()))) + && eReference.isContainment()) + return eReference; + } + return null; + } + + /** + * Puts given dynamic EObject to container + * + * @param container + * @param eObject + */ + @SuppressWarnings({"unchecked", "rawtypes"}) + public static void putIntoContainer(EObject container, EObject eObject) { + EReference eReference = getContainmentEReference(container, eObject); + if (eReference != null) { + if (eReference.isMany()) + ((List) container.eGet(eReference)).add(eObject); + else + container.eSet(eReference, eObject); + } + } + + /** + * Saves given @EObject to given file path + * + * @param root @EObject to be saved + * @param savePath file location + */ + @SuppressWarnings({"unchecked", "rawtypes"}) + public static void saveResource(EObject root, String savePath) { + ResourceSet resourceSet = new ResourceSetImpl(); + resourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap().put("*", + new XMLResourceFactoryImpl()); + Resource resource = resourceSet.createResource(URI.createFileURI(savePath)); + resource.getContents().add(root); + + Map options = new HashMap(); + options.put(XMLResource.OPTION_SCHEMA_LOCATION, Boolean.TRUE); + // options.put(XMLResource.OPTION_SAVE_TYPE_INFORMATION, noTypeInfo); + try { + resource.save(options); + } catch (IOException e) { + e.printStackTrace(); + } + } + + + /** + * Saves given @EObject to its resource. + * + * @param root @EObject + */ + @SuppressWarnings({"rawtypes", "unchecked"}) + public static void saveResource(EObject root) { + Map options = new HashMap(); + options.put(XMLResource.OPTION_SCHEMA_LOCATION, Boolean.TRUE); + // options.put(XMLResource.OPTION_SAVE_TYPE_INFORMATION, noTypeInfo); + + try { + root.eResource().save(options); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public static EClass findEClass(List allEClasses, String className) { + return allEClasses.stream().filter(c -> c.getName().equals(className)).findFirst().orElse(null); + } +} diff --git a/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/internal/ModelIO.java b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/internal/ModelIO.java new file mode 100644 index 00000000..d55d70db --- /dev/null +++ b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/internal/ModelIO.java @@ -0,0 +1,82 @@ +package eu.modelwriter.configuration.internal; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import org.eclipse.emf.common.util.EList; +import org.eclipse.emf.common.util.URI; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EPackage; +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.emf.ecore.resource.ResourceSet; +import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl; +import org.eclipse.emf.ecore.xmi.impl.EcoreResourceFactoryImpl; + +public class ModelIO { + private ResourceSet resourceSet; + + public ResourceSet getResourceSet() { + if (this.resourceSet == null) { + this.resourceSet = new ResourceSetImpl(); + this.resourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap() + .put(Resource.Factory.Registry.DEFAULT_EXTENSION, new EcoreResourceFactoryImpl()); + this.registerPackages(this.resourceSet.getPackageRegistry()); + } + + return this.resourceSet; + } + + @SuppressWarnings("unchecked") + public List read(final URI uri) throws IOException { + final Resource res = this.getResourceSet().createResource(uri); + + try { + res.load(null); + } catch (final IOException e) { + throw new IOException(); + } + final EList contents = res.getContents(); + + final List list = new ArrayList(); + for (final EObject content : contents) { + + try { + list.add((T) content); + } catch (final Exception e) { + throw new RuntimeException("Unexpected resource type."); + } + } + + return list; + } + + /** + * ResourceSet'e paket kayit eder. + * + *
+ *
+ * Ornegin;
+ * packageRegistry.put(ContentPackage.eNS_URI, ContentPackage.eINSTANCE);
+ *
+ * Kendi modellerinizi kullanacaksaniz bu methodu gerceklestiriniz ve kendi paket arayuzunuzu + * kayit ediniz. EMF metamodelleri kullanacaksaniz varsayilan gerceklestirim yeterlidir. + * + * @param packageRegistry ResourceSet'a ait paket kayitcisi. + */ + protected void registerPackages(final EPackage.Registry packageRegistry) {} + + public void write(final URI uri, final T obj) { + final Resource resource = this.getResourceSet().createResource(uri); + + resource.getContents().add(obj); + + final HashMap options = new HashMap<>(); + try { + resource.save(options); + } catch (final IOException e) { + e.printStackTrace(); + } + } +} diff --git a/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/internal/Utilities.java b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/internal/Utilities.java new file mode 100644 index 00000000..72f56fe1 --- /dev/null +++ b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/internal/Utilities.java @@ -0,0 +1,128 @@ +package eu.modelwriter.configuration.internal; + +import java.io.BufferedWriter; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.channels.FileChannel; +import java.util.Scanner; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.Path; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.swt.widgets.Display; + +import eu.modelwriter.configuration.Activator; + +public class Utilities { + + public static final String FILE_SEPERATOR = System.getProperty("file.separator"); + + public static boolean appendToFile(String filePath, String textToAppend) { + try (FileWriter fw = new FileWriter(filePath, true)) { + fw.write(textToAppend); + fw.close(); + return true; + } catch (Exception e) { + return false; + } + } + + public static boolean copyFileContent(String sourcePath, String targetPath) { + FileInputStream fr = null; + FileOutputStream fw = null; + try { + fr = new FileInputStream(sourcePath); + fw = new FileOutputStream(targetPath); + FileChannel src = fr.getChannel(); + FileChannel dest = fw.getChannel(); + dest.transferFrom(src, 0, src.size()); + return true; + } catch (Exception e) { + return false; + } finally { + try { + fr.close(); + fw.close(); + } catch (Exception e) { + return false; + } + } + } + + public static void writeToFile(String fileName, StringBuilder builder) { + try (BufferedWriter bw = new BufferedWriter(new FileWriter(fileName))) { + final int aLength = builder.length(); + final int aChunk = 1024;// 1 kb buffer to read data from + final char[] aChars = new char[aChunk]; + + for (int aPosStart = 0; aPosStart < aLength; aPosStart += aChunk) { + final int aPosEnd = Math.min(aPosStart + aChunk, aLength); + builder.getChars(aPosStart, aPosEnd, aChars, 0); // Create no new buffer + bw.write(aChars, 0, aPosEnd - aPosStart);// This is faster than just copying one byte at the + // time + } + bw.flush(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public static IFile getIFileFromPath(String path) { + final Path filePath = new Path(path); + // for absolute paths + IFile iFile = ResourcesPlugin.getWorkspace().getRoot().getFileForLocation(filePath); + // might be relative path? + if (iFile == null) + iFile = ResourcesPlugin.getWorkspace().getRoot().getFile(filePath); + return iFile; + } + + /** + * Skips comment lines + * + * @param scanner + * @return comment free line + */ + public static String getNextLine(Scanner scanner) { + while (scanner.hasNextLine()) { + String line = scanner.nextLine().trim(); + if (line.startsWith("//")) + continue; // Its single comment line, skip + + if (line.startsWith("/*")) { // multi line comment + String skipLine = line; + while (scanner.hasNextLine() && !skipLine.contains("*/")) { // skip until close tag + skipLine = scanner.nextLine(); + } + // in case there is data after comment close tag + line = skipLine.substring(skipLine.indexOf("*/") + 2); + if (line.isEmpty()) + continue; + } + return line; + } + return ""; + } + + /** + * Displays a message dialog on ui thread. (async) + * + * @param title dialog's title + * @param message dialog's message + * @param type dialog's type image constant, see @MessageDialog + */ + public static void showOKDialog(int type, String title, String message) { + Display.getDefault().asyncExec(new Runnable() { + + @Override + public void run() { + final MessageDialog warningdialog = new MessageDialog(Activator.getShell(), title, null, + message, type, new String[] {"OK"}, 0); + warningdialog.open(); + } + }); + } +} diff --git a/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/synthesis/AutomatedTraceCreator.java b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/synthesis/AutomatedTraceCreator.java new file mode 100644 index 00000000..5798bb0f --- /dev/null +++ b/Source/eu.modelwriter.configuration/src/eu/modelwriter/configuration/synthesis/AutomatedTraceCreator.java @@ -0,0 +1,148 @@ +package eu.modelwriter.configuration.synthesis; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IMarker; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EReference; +import org.eclipse.jface.dialogs.MessageDialog; + +import eu.modelwriter.configuration.alloy.trace.LoadItem; +import eu.modelwriter.configuration.alloy.trace.RelationTrace; +import eu.modelwriter.configuration.alloy.trace.SigTrace; +import eu.modelwriter.configuration.alloy.trace.TraceException; +import eu.modelwriter.configuration.alloy.trace.TraceManager; +import eu.modelwriter.configuration.internal.AlloyUtilities; +import eu.modelwriter.configuration.internal.Utilities; +import eu.modelwriter.marker.internal.AnnotationFactory; +import eu.modelwriter.marker.internal.MarkerFactory; + +public class AutomatedTraceCreator extends Job { + + private final static String TITLE = "Automated Trace Creation"; + private final HashMap eObject2Marker = new HashMap<>(); + + + public AutomatedTraceCreator() { + super(TITLE); + } + + @Override + protected IStatus run(IProgressMonitor monitor) { + try { + monitor.beginTask(getName(), 5); + automate(monitor); + } catch (final TraceException e) { + e.printStackTrace(); + Utilities.showOKDialog(MessageDialog.WARNING, TITLE, e.getMessage()); + return Status.CANCEL_STATUS; + } catch (NullPointerException e) { + Utilities.showOKDialog(MessageDialog.WARNING, TITLE, "Load the specification first."); + } finally { + monitor.done(); + } + return Status.OK_STATUS; + } + + private void automate(IProgressMonitor monitor) throws TraceException { + final List allEObjects = new ArrayList<>(); + for (LoadItem load : TraceManager.get().getLoads()) { + if (load.getInstanceRoot() == null) { + throw new TraceException("Check alias: " + load.getAlias() + + "\nYou must define the ecore instance location to create the traces.\n" + + "\n e.g. -- loadInstance@/path/to/your.xmi"); + } + findAllEObjects(allEObjects, load.getInstanceRoot()); + allEObjects.remove(load.getInstanceRoot()); + } + monitor.worked(1); + createMarkers(allEObjects); + monitor.worked(2); + createRelations(); + monitor.worked(2); + } + + private void findAllEObjects(List allEObjects, EObject root) { + allEObjects.add(root); + for (EObject object : root.eContents()) { + findAllEObjects(allEObjects, object); + } + } + + private void createMarkers(List allEObjects) throws TraceException { + for (EObject eObject : allEObjects) { + final String className = eObject.eClass().getName(); + final SigTrace sigTrace = TraceManager.get().getSigTraceByClassName(className); + if (sigTrace != null) { + LoadItem load = TraceManager.get().getLoadByAlias(sigTrace.getAlias()); + IMarker marker = createMarker(eObject, load.getInstanceFile(), sigTrace.getSigType()); + if (marker != null) { + eObject2Marker.put(eObject, marker); + } + } else { + throw new TraceException("There is no sig trace for EClass " + className); + } + } + } + + @SuppressWarnings({"unchecked"}) + private void createRelations() { + for (EObject sourceObject : eObject2Marker.keySet()) { + final IMarker sourceMarker = eObject2Marker.get(sourceObject); + for (EReference eRef : sourceObject.eClass().getEAllReferences()) { + if (eRef.isMany()) { + List refs = (List) sourceObject.eGet(eRef); + for (EObject ref : refs) { + IMarker targetMarker = eObject2Marker.get(ref); + RelationTrace relTrace; + EClass _super = sourceObject.eClass(); + + do { + relTrace = TraceManager.get().getRelationTrace(_super.getName(), eRef.getName()); + if (relTrace == null) + _super = _super.getESuperTypes().get(0); + } while (_super != null && relTrace == null); + + if (sourceMarker != null && targetMarker != null && !eRef.isVolatile() + && relTrace != null) { + AlloyUtilities.addRelation2Markers(sourceMarker, targetMarker, + relTrace.getRelationName()); + } + } + } else { + EObject ref = (EObject) sourceObject.eGet(eRef); + IMarker targetMarker = eObject2Marker.get(ref); + if (sourceMarker != null && targetMarker != null && !eRef.isVolatile()) + AlloyUtilities.addRelation2Markers(sourceMarker, targetMarker, TraceManager.get() + .getRelationTraceByReferenceName(eRef.getName()).getRelationName()); + } + } + if (sourceMarker != null) { + IMarker newSourceMarker = AnnotationFactory.convertAnnotationType(sourceMarker, false, + false, AlloyUtilities.getTotalTargetCount(sourceMarker)); + eObject2Marker.put(sourceObject, newSourceMarker); + } + + } + } + + public static IMarker createMarker(EObject eObject, IFile iFile, String sigType) { + final IMarker marker = MarkerFactory.createInstanceMarker(eObject, iFile, sigType); + + if (marker == null) + return null; + + AlloyUtilities.addTypeToMarker(marker); + AlloyUtilities.addMarkerToRepository(marker); + return marker; + } +} + diff --git a/Source/eu.modelwriter.configuration/traffic lights.als b/Source/eu.modelwriter.configuration/traffic lights.als new file mode 100644 index 00000000..72749e06 --- /dev/null +++ b/Source/eu.modelwriter.configuration/traffic lights.als @@ -0,0 +1,103 @@ +/* +All clafers: 21 | Abstract: 5 | Concrete: 14 | References: 2 +Constraints: 3 +Goals: 0 +Global scope: 1..* +Can skip resolver: False +*/ + +pred show {} +run show for 1 but 2 c0_Library, 2 c0_List, 6 c0_NamedElement, 2 c0_Type, 2 c0_Web, 2 c0_contentTypes, 6 c0_name, 2 c0_ownedList, 2 c1_List + +abstract sig c0_NamedElement +{ r_c0_name : one c0_name } + +sig c0_name +{ ref : one Int } +{ one @r_c0_name.this } + +abstract sig c0_Web extends c0_NamedElement +{ r_c0_ownedList : set c0_ownedList +, r_c0_ownedWeb : set c0_ownedWeb } + +sig c0_ownedList extends c0_List +{} +{ one @r_c0_ownedList.this } + +sig c0_ownedWeb extends c0_Web +{} +{ one @r_c0_ownedWeb.this } + +abstract sig c0_List extends c0_NamedElement +{ r_c0_contentTypes : set c0_contentTypes +, r_c0_Type : one c0_Type } +{ all disj x, y : this.@r_c0_contentTypes | (x.@ref) != (y.@ref) } + +sig c0_contentTypes +{ ref : one c0_ContentType } +{ one @r_c0_contentTypes.this } + +sig c0_Type +{ r_c0_Library : lone c0_Library +, r_c1_List : lone c1_List } +{ one @r_c0_Type.this + let children = (r_c0_Library + r_c1_List) | one children } + +sig c0_Library +{} +{ one @r_c0_Library.this } + +sig c1_List +{} +{ one @r_c1_List.this } + +fact { #c0_ContentType = 0 } +abstract sig c0_ContentType extends c0_NamedElement +{ r_c0_isAbstract : lone c0_isAbstract +, r_c0_ownedField : set c0_ownedField } + +sig c0_isAbstract +{ ref : one Int } +{ one @r_c0_isAbstract.this } + +sig c0_ownedField extends c0_Field +{} +{ one @r_c0_ownedField.this } + +fact { #c0_Field = 0 } +abstract sig c0_Field extends c0_NamedElement +{ r_c1_Type : one c1_Type } + +sig c1_Type +{ r_c0_Number : lone c0_Number +, r_c0_Boolean : lone c0_Boolean +, r_c0_Text : lone c0_Text +, r_c0_Reference : lone c0_Reference } +{ one @r_c1_Type.this + let children = (r_c0_Number + r_c0_Boolean + r_c0_Text + r_c0_Reference) | one children } + +sig c0_Number +{} +{ one @r_c0_Number.this } + +sig c0_Boolean +{} +{ one @r_c0_Boolean.this } + +sig c0_Text +{} +{ one @r_c0_Text.this } + +sig c0_Reference +{ r_c0_contentType : one c0_contentType } +{ one @r_c0_Reference.this + all disj x, y : this.@r_c0_contentType | (x.@ref) != (y.@ref) } + +sig c0_contentType +{ ref : one c0_ContentType } +{ one @r_c0_contentType.this } + +one sig c0_ConferenceLibrary extends c0_Web +{} +{ (this.(@r_c0_name.@ref)) = 0 } + diff --git a/Source/eu.modelwriter.core.alloyinecore.ui/.classpath b/Source/eu.modelwriter.core.alloyinecore.ui/.classpath new file mode 100644 index 00000000..6d9a1b4c --- /dev/null +++ b/Source/eu.modelwriter.core.alloyinecore.ui/.classpath @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/Source/eu.modelwriter.core.alloyinecore.ui/.gitignore b/Source/eu.modelwriter.core.alloyinecore.ui/.gitignore new file mode 100644 index 00000000..ae3c1726 --- /dev/null +++ b/Source/eu.modelwriter.core.alloyinecore.ui/.gitignore @@ -0,0 +1 @@ +/bin/ diff --git a/Source/eu.modelwriter.core.alloyinecore.ui/.project b/Source/eu.modelwriter.core.alloyinecore.ui/.project new file mode 100644 index 00000000..90eaaf4a --- /dev/null +++ b/Source/eu.modelwriter.core.alloyinecore.ui/.project @@ -0,0 +1,34 @@ + + + eu.modelwriter.core.alloyinecore.ui + + + + + + net.certiv.stdt.core.builder + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + net.certiv.stdt.core.nature + + diff --git a/Source/eu.modelwriter.core.alloyinecore.ui/.settings/org.eclipse.jdt.core.prefs b/Source/eu.modelwriter.core.alloyinecore.ui/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 00000000..0c68a61d --- /dev/null +++ b/Source/eu.modelwriter.core.alloyinecore.ui/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,7 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 +org.eclipse.jdt.core.compiler.compliance=1.8 +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.8 diff --git a/Source/eu.modelwriter.core.alloyinecore.ui/META-INF/MANIFEST.MF b/Source/eu.modelwriter.core.alloyinecore.ui/META-INF/MANIFEST.MF new file mode 100644 index 00000000..65a3cc40 --- /dev/null +++ b/Source/eu.modelwriter.core.alloyinecore.ui/META-INF/MANIFEST.MF @@ -0,0 +1,19 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: Ui +Bundle-SymbolicName: eu.modelwriter.core.alloyinecore.ui;singleton:=true +Bundle-Version: 1.0.0.qualifier +Require-Bundle: org.eclipse.core.runtime, + org.eclipse.ui, + org.eclipse.jface.text, + org.eclipse.ui.editors, + org.junit;bundle-version="4.12.0", + org.eclipse.emf.ecore;bundle-version="2.12.0", + eu.modelwriter.configuration;bundle-version="1.0.0", + org.eclipse.emf.ecore.xmi;bundle-version="2.12.0", + org.eclipse.ui.workbench, + org.eclipse.ui.ide;bundle-version="3.12.1", + eu.modelwriter.core.alloyinecore;bundle-version="1.0.0" +Bundle-RequiredExecutionEnvironment: JavaSE-1.8 +Import-Package: eu.modelwriter.core.alloyinecore.recognizer, + org.eclipse.core.resources diff --git a/Source/eu.modelwriter.core.alloyinecore.ui/build.properties b/Source/eu.modelwriter.core.alloyinecore.ui/build.properties new file mode 100644 index 00000000..0d3d3a74 --- /dev/null +++ b/Source/eu.modelwriter.core.alloyinecore.ui/build.properties @@ -0,0 +1,6 @@ +source.. = src/ +output.. = bin/ +bin.includes = plugin.xml,\ + META-INF/,\ + .,\ + icons/ diff --git a/Source/eu.modelwriter.core.alloyinecore.ui/icons/sample.gif b/Source/eu.modelwriter.core.alloyinecore.ui/icons/sample.gif new file mode 100644 index 00000000..34fb3c9d Binary files /dev/null and b/Source/eu.modelwriter.core.alloyinecore.ui/icons/sample.gif differ diff --git a/Source/eu.modelwriter.core.alloyinecore.ui/lib/ST-4.0.8.jar b/Source/eu.modelwriter.core.alloyinecore.ui/lib/ST-4.0.8.jar new file mode 100644 index 00000000..ef879198 Binary files /dev/null and b/Source/eu.modelwriter.core.alloyinecore.ui/lib/ST-4.0.8.jar differ diff --git a/Source/eu.modelwriter.core.alloyinecore.ui/mappingTest/tutorial.ecore b/Source/eu.modelwriter.core.alloyinecore.ui/mappingTest/tutorial.ecore new file mode 100644 index 00000000..efc491a7 --- /dev/null +++ b/Source/eu.modelwriter.core.alloyinecore.ui/mappingTest/tutorial.ecore @@ -0,0 +1,86 @@ + + + +
+
+
+ + + + + + + + + + + +
+
+
+ + +
+ + + + +
+ + + +
+
+ + +
+ + + + + + + + + + + + + + + +
+
+
+ + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Source/eu.modelwriter.core.alloyinecore.ui/mappingTest/tutorial.recore b/Source/eu.modelwriter.core.alloyinecore.ui/mappingTest/tutorial.recore new file mode 100644 index 00000000..86d7bd86 --- /dev/null +++ b/Source/eu.modelwriter.core.alloyinecore.ui/mappingTest/tutorial.recore @@ -0,0 +1,73 @@ +import ecore : 'http://www.eclipse.org/emf/2002/Ecore' ; import extlib : '/eu.modelwriter.core.alloyinecore/docs/extlibrary.ecore' ; + +package tutorial : tut = 'http://www.eclipse.org/mdt/ocl/oclinecore/tutorial' +{ + invariant x: expr; + package Organization : org = 'http://www.eclipse.org/mdt/ocl/oclinecore/tutorial/organization' + { + abstract class Librarian extends Employee + { + property workOn : tutorial::Library[1]; + } + abstract class Employee extends tutorial::Library + { + property RefOutsideEcore : extlib::Item[+]; + } + datatype JavaPackage : 'java.lang.Package' { serializable }; + enum Type + { + literal type1; + literal type2; + } + } + abstract class Library { interface } + { + attribute name : String[1] { id }; + property books#library : Book[*] { composes }; + property loans : Loan[*] { composes }; + property members#library : Member[*] { composes }; + } + class Book + { + operation isAvailable() : Boolean[?] + { + precondition:; + precondition test:; + precondition; + body test: expr; + body: expr; + body; + } + attribute name : String[1]; + attribute copies : Integer[1]; + property library#books : Library[?]; + property loans : Loan[*] { derived volatile } + { + initial: expr; + } + invariant SufficientCopies: expr; + invariant; + } + class Member + { + attribute name : String[1]; + property library#members : Library[?]; + property loans : Loan[*] { derived volatile } + { + initial: expr; + derivation: expr; + } + property books : Book[*] { !unique derived volatile } + { + initial: expr; + } + invariant AtMostTwoLoans: expr; + invariant UniqueLoans: expr; + } + class Loan + { + property book : Book[1]; + property member : Member[1]; + attribute date : ecore::EDate[?]; + } +} \ No newline at end of file diff --git a/Source/eu.modelwriter.core.alloyinecore.ui/mappingTest/tutorialExtended.ecore b/Source/eu.modelwriter.core.alloyinecore.ui/mappingTest/tutorialExtended.ecore new file mode 100644 index 00000000..9758ce8e --- /dev/null +++ b/Source/eu.modelwriter.core.alloyinecore.ui/mappingTest/tutorialExtended.ecore @@ -0,0 +1,76 @@ + + + + + + + + + + + + +
+
+
+ + + + +
+ + +
+
+ + +
+ + + + + + + + + + + + + + +
+
+
+ + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Source/eu.modelwriter.core.alloyinecore.ui/mappingTest/tutorialExtended.recore b/Source/eu.modelwriter.core.alloyinecore.ui/mappingTest/tutorialExtended.recore new file mode 100644 index 00000000..5ca5a9b9 --- /dev/null +++ b/Source/eu.modelwriter.core.alloyinecore.ui/mappingTest/tutorialExtended.recore @@ -0,0 +1,69 @@ +import ecore : 'http://www.eclipse.org/emf/2002/Ecore' ; import extlib : '/eu.modelwriter.core.alloyinecore/docs/extlibrary.ecore' ; + +package tutorial : tut = 'http://www.eclipse.org/mdt/ocl/oclinecore/tutorial' +{ + package Organization : org = 'http://www.eclipse.org/mdt/ocl/oclinecore/tutorial/organization' + { + abstract class Librarian extends Employee + { + property workOn : tutorial::Library[1]; + } + abstract class Employee extends tutorial::Library + { + property RefOutsideEcore : extlib::Item[+]; + } + datatype JavaPackage : 'java.lang.Package' { serializable }; + enum Type + { + literal type1; + literal type2; + } + } + abstract class Library { interface } + { + attribute name : String[1] { id }; + property books#library : Book[*] { composes }; + property loans : Loan[*] { composes }; + property members#library : Member[*] { composes }; + } + class Book + { + operation isAvailable() : Boolean[?] + { + precondition:; + precondition test:; + body test: expr; + body: expr; + } + attribute name : String[1]; + attribute copies : Integer[1]; + property library#books : Library[?]; + property loans : Loan[*] { derived volatile } + { + initial: expr; + } + invariant SufficientCopies: expr; + } + class Member + { + attribute name : String[1]; + property library#members : Library[?]; + property loans : Loan[*] { derived volatile } + { + initial: expr; + derivation: expr; + } + property books : Book[*] { !unique derived volatile } + { + initial: expr; + } + invariant AtMostTwoLoans: expr; + invariant UniqueLoans: expr; + } + class Loan + { + property book : Book[1]; + property member : Member[1]; + attribute date : ecore::EDate[?]; + } +} \ No newline at end of file diff --git a/Source/eu.modelwriter.core.alloyinecore.ui/plugin.xml b/Source/eu.modelwriter.core.alloyinecore.ui/plugin.xml new file mode 100644 index 00000000..e24cbcdd --- /dev/null +++ b/Source/eu.modelwriter.core.alloyinecore.ui/plugin.xml @@ -0,0 +1,17 @@ + + + + + + + + + + diff --git a/Source/eu.modelwriter.core.alloyinecore.ui/src/eu/modelwriter/core/alloyinecore/ui/cs2as/PackageImport.java b/Source/eu.modelwriter.core.alloyinecore.ui/src/eu/modelwriter/core/alloyinecore/ui/cs2as/PackageImport.java new file mode 100644 index 00000000..f33af7e4 --- /dev/null +++ b/Source/eu.modelwriter.core.alloyinecore.ui/src/eu/modelwriter/core/alloyinecore/ui/cs2as/PackageImport.java @@ -0,0 +1,55 @@ +package eu.modelwriter.core.alloyinecore.ui.cs2as; + +import java.util.Arrays; +import java.util.stream.Collectors; + +import org.eclipse.emf.common.util.URI; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.util.EcoreUtil; + +public class PackageImport { + private String name; + private String path; + private EObject root; + + public static PackageImport newInstance() { + return new PackageImport(); + } + + public String getName() { + return name; + } + + public PackageImport setName(final String name) { + this.name = name; + return this; + } + + public String getPath() { + return path; + } + + public PackageImport setPath(final String path) { + this.path = path; + return this; + } + + public EObject getRoot() { + return root; + } + + public PackageImport setRoot(final EObject root) { + this.root = root; + return this; + } + + public URI getURI() { + return EcoreUtil.getURI(root); + } + + public EObject getElement(final String[] relativePathFragments) { + final String relativeFragmentPath = + Arrays.asList(relativePathFragments).stream().collect(Collectors.joining("/")); + return EcoreUtil.getEObject(root, relativeFragmentPath); + } +} diff --git a/Source/eu.modelwriter.core.alloyinecore.ui/src/eu/modelwriter/core/alloyinecore/ui/cs2as/Qualification.java b/Source/eu.modelwriter.core.alloyinecore.ui/src/eu/modelwriter/core/alloyinecore/ui/cs2as/Qualification.java new file mode 100644 index 00000000..9da3f457 --- /dev/null +++ b/Source/eu.modelwriter.core.alloyinecore.ui/src/eu/modelwriter/core/alloyinecore/ui/cs2as/Qualification.java @@ -0,0 +1,54 @@ +package eu.modelwriter.core.alloyinecore.ui.cs2as; + +import eu.modelwriter.core.alloyinecore.ui.model.AnnotationSources; + +public enum Qualification { + VISIBILITY, + STATIC, + PRIMITIVE, + SERIALIZABLE, + CALLABLE, + MESSAGE, + FORMULA, + EXPRESSION, + NAME, + DERIVED, + NOT_DERIVED, + ORDERED, + NOT_ORDERED, + UNIQUE, + NOT_UNIQUE, + ID, + NOT_ID, + READONLY, + NOT_READONLY, + TRANSIENT, + NOT_TRANSIENT, + UNSETTABLE, + NOT_UNSETTABLE, + VOLATILE, + NOT_VOLATILE, + COMPOSES, + NOT_COMPOSES, + RESOLVE, + NOT_RESOLVE; + @Override + public String toString() { + return super.toString().toLowerCase().replaceAll("not_", "!"); + } + + public String getAnnotationSource() { + switch (this) { + case VISIBILITY: + return AnnotationSources.VISIBILTY; + case STATIC: + return AnnotationSources.STATIC; + case PRIMITIVE: + return AnnotationSources.DATATYPE_PRIMITIVE; + case CALLABLE: + return AnnotationSources.INVARIANT; + default: + return AnnotationSources.QUALIFIER; + } + } +} diff --git a/Source/eu.modelwriter.core.alloyinecore.ui/src/eu/modelwriter/core/alloyinecore/ui/cs2as/mapping/CS2ASInitializer.java b/Source/eu.modelwriter.core.alloyinecore.ui/src/eu/modelwriter/core/alloyinecore/ui/cs2as/mapping/CS2ASInitializer.java new file mode 100644 index 00000000..61b93953 --- /dev/null +++ b/Source/eu.modelwriter.core.alloyinecore.ui/src/eu/modelwriter/core/alloyinecore/ui/cs2as/mapping/CS2ASInitializer.java @@ -0,0 +1,152 @@ +package eu.modelwriter.core.alloyinecore.ui.cs2as.mapping; + +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.emf.common.util.URI; +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EDataType; +import org.eclipse.emf.ecore.EEnum; +import org.eclipse.emf.ecore.EModelElement; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EPackage; +import org.eclipse.emf.ecore.EcoreFactory; +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.emf.ecore.resource.ResourceSet; +import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl; +import org.eclipse.emf.ecore.xmi.impl.XMLResourceFactoryImpl; + +import eu.modelwriter.core.alloyinecore.recognizer.AlloyInEcoreBaseVisitor; +import eu.modelwriter.core.alloyinecore.recognizer.AlloyInEcoreParser.EClassContext; +import eu.modelwriter.core.alloyinecore.recognizer.AlloyInEcoreParser.EDataTypeContext; +import eu.modelwriter.core.alloyinecore.recognizer.AlloyInEcoreParser.EEnumContext; +import eu.modelwriter.core.alloyinecore.recognizer.AlloyInEcoreParser.EPackageContext; +import eu.modelwriter.core.alloyinecore.recognizer.AlloyInEcoreParser.PackageImportContext; +import eu.modelwriter.core.alloyinecore.ui.cs2as.PackageImport; + +public class CS2ASInitializer extends AlloyInEcoreBaseVisitor { + public static final CS2ASInitializer instance = new CS2ASInitializer(); + + private final EcoreFactory factory = EcoreFactory.eINSTANCE; + + private final Map name2packageImport = new HashMap<>(); + private final Map name2ePackage = new HashMap<>(); + private final Map name2eClass = new HashMap<>(); + private final Map name2eDataType = new HashMap<>(); + private final Map name2eEnum = new HashMap<>(); + private EModelElement root; + + @Override + public Object visitPackageImport(final PackageImportContext ctx) { + final String name = ctx.name.getText(); + if (name.equals("ecore")) { + return null; + } + final String path = ctx.ownedPathName.getText().replace("'", ""); + final EObject root = loadResource(path); + + final PackageImport packageImport = + PackageImport.newInstance().setName(name).setPath(path).setRoot(root); + getName2packageImport().put(name, packageImport); + return null; + } + + @Override + public Object visitEPackage(final EPackageContext ctx) { + final EPackage ePackage = factory.createEPackage(); + + final String name = ctx.name.getText(); + ePackage.setName(name); + + final boolean isRoot = ctx.parent.getChild(0).equals(ctx); + root = isRoot ? ePackage : null; + + name2ePackage.put(name, ePackage); + return super.visitEPackage(ctx); + } + + @Override + public Object visitEClass(final EClassContext ctx) { + final EClass eClass = factory.createEClass(); + + final String name = ctx.name.getText(); + eClass.setName(name); + + name2eClass.put(name, eClass); + return super.visitEClass(ctx); + } + + @Override + public Object visitEDataType(final EDataTypeContext ctx) { + final EDataType eDataType = factory.createEDataType(); + + final String name = ctx.name.getText(); + eDataType.setName(name); + + name2eDataType.put(name, eDataType); + return super.visitEDataType(ctx); + } + + @Override + public Object visitEEnum(final EEnumContext ctx) { + final EEnum eEnum = factory.createEEnum(); + + final String name = ctx.name.getText(); + eEnum.setName(name); + + name2eEnum.put(name, eEnum); + return super.visitEEnum(ctx); + } + + public Map getName2packageImport() { + return name2packageImport; + } + + public Map getName2epackage() { + return name2ePackage; + } + + public Map getName2eclass() { + return name2eClass; + } + + public Map getName2edatatype() { + return name2eDataType; + } + + public Map getName2eenum() { + return name2eEnum; + } + + /** + * @param path + * @return + * + */ + private EObject loadResource(final String path) { + final ResourceSet metaResourceSet = new ResourceSetImpl(); + + /* + * Register XML Factory implementation to handle .ecore files + */ + metaResourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap().put("ecore", + new XMLResourceFactoryImpl()); + + /* + * Create empty resource with the given URI + */ + final Resource metaResource = + metaResourceSet.getResource(URI.createPlatformResourceURI(path, true), true); + + /* + * Get root element of resource + */ + final EObject root = metaResource.getContents().get(0); + + return root; + } + + public EModelElement getRoot() { + return root; + } +} diff --git a/Source/eu.modelwriter.core.alloyinecore.ui/src/eu/modelwriter/core/alloyinecore/ui/cs2as/mapping/CS2ASMapping.java b/Source/eu.modelwriter.core.alloyinecore.ui/src/eu/modelwriter/core/alloyinecore/ui/cs2as/mapping/CS2ASMapping.java new file mode 100644 index 00000000..9ae5fd63 --- /dev/null +++ b/Source/eu.modelwriter.core.alloyinecore.ui/src/eu/modelwriter/core/alloyinecore/ui/cs2as/mapping/CS2ASMapping.java @@ -0,0 +1,1034 @@ +package eu.modelwriter.core.alloyinecore.ui.cs2as.mapping; + +import java.io.File; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import org.antlr.v4.runtime.ANTLRFileStream; +import org.antlr.v4.runtime.ANTLRInputStream; +import org.antlr.v4.runtime.CommonTokenStream; +import org.antlr.v4.runtime.tree.ParseTree; +import org.eclipse.emf.common.util.URI; +import org.eclipse.emf.ecore.EAnnotation; +import org.eclipse.emf.ecore.EAttribute; +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EClassifier; +import org.eclipse.emf.ecore.EDataType; +import org.eclipse.emf.ecore.EEnum; +import org.eclipse.emf.ecore.EEnumLiteral; +import org.eclipse.emf.ecore.EModelElement; +import org.eclipse.emf.ecore.ENamedElement; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EOperation; +import org.eclipse.emf.ecore.EPackage; +import org.eclipse.emf.ecore.EParameter; +import org.eclipse.emf.ecore.EReference; +import org.eclipse.emf.ecore.EStructuralFeature; +import org.eclipse.emf.ecore.ETypedElement; +import org.eclipse.emf.ecore.EcoreFactory; +import org.eclipse.emf.ecore.EcorePackage; +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.emf.ecore.resource.ResourceSet; +import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl; +import org.eclipse.emf.ecore.xmi.impl.XMLResourceFactoryImpl; + +import eu.modelwriter.core.alloyinecore.recognizer.AlloyInEcoreBaseVisitor; +import eu.modelwriter.core.alloyinecore.recognizer.AlloyInEcoreLexer; +import eu.modelwriter.core.alloyinecore.recognizer.AlloyInEcoreParser; +import eu.modelwriter.core.alloyinecore.recognizer.AlloyInEcoreParser.BodyContext; +import eu.modelwriter.core.alloyinecore.recognizer.AlloyInEcoreParser.EAnnotationContext; +import eu.modelwriter.core.alloyinecore.recognizer.AlloyInEcoreParser.EAttributeContext; +import eu.modelwriter.core.alloyinecore.recognizer.AlloyInEcoreParser.EClassContext; +import eu.modelwriter.core.alloyinecore.recognizer.AlloyInEcoreParser.EClassifierContext; +import eu.modelwriter.core.alloyinecore.recognizer.AlloyInEcoreParser.EDataTypeContext; +import eu.modelwriter.core.alloyinecore.recognizer.AlloyInEcoreParser.EEnumContext; +import eu.modelwriter.core.alloyinecore.recognizer.AlloyInEcoreParser.EEnumLiteralContext; +import eu.modelwriter.core.alloyinecore.recognizer.AlloyInEcoreParser.EModelElementContext; +import eu.modelwriter.core.alloyinecore.recognizer.AlloyInEcoreParser.EModelElementRefContext; +import eu.modelwriter.core.alloyinecore.recognizer.AlloyInEcoreParser.EMultiplicityContext; +import eu.modelwriter.core.alloyinecore.recognizer.AlloyInEcoreParser.ENamedElementContext; +import eu.modelwriter.core.alloyinecore.recognizer.AlloyInEcoreParser.EOperationContext; +import eu.modelwriter.core.alloyinecore.recognizer.AlloyInEcoreParser.EPackageContext; +import eu.modelwriter.core.alloyinecore.recognizer.AlloyInEcoreParser.EParameterContext; +import eu.modelwriter.core.alloyinecore.recognizer.AlloyInEcoreParser.EPrimitiveTypeContext; +import eu.modelwriter.core.alloyinecore.recognizer.AlloyInEcoreParser.EReferenceContext; +import eu.modelwriter.core.alloyinecore.recognizer.AlloyInEcoreParser.EStructuralFeatureContext; +import eu.modelwriter.core.alloyinecore.recognizer.AlloyInEcoreParser.ETypeContext; +import eu.modelwriter.core.alloyinecore.recognizer.AlloyInEcoreParser.ETypedElementContext; +import eu.modelwriter.core.alloyinecore.recognizer.AlloyInEcoreParser.ExpressionContext; +import eu.modelwriter.core.alloyinecore.recognizer.AlloyInEcoreParser.FormulaContext; +import eu.modelwriter.core.alloyinecore.recognizer.AlloyInEcoreParser.InvariantContext; +import eu.modelwriter.core.alloyinecore.recognizer.AlloyInEcoreParser.ModuleContext; +import eu.modelwriter.core.alloyinecore.recognizer.AlloyInEcoreParser.PackageImportContext; +import eu.modelwriter.core.alloyinecore.recognizer.AlloyInEcoreParser.PostconditionContext; +import eu.modelwriter.core.alloyinecore.recognizer.AlloyInEcoreParser.PreconditionContext; +import eu.modelwriter.core.alloyinecore.recognizer.AlloyInEcoreParser.QualifiedNameContext; +import eu.modelwriter.core.alloyinecore.ui.cs2as.PackageImport; +import eu.modelwriter.core.alloyinecore.ui.cs2as.Qualification; +import eu.modelwriter.core.alloyinecore.ui.model.AnnotationSources; + +public class CS2ASMapping extends AlloyInEcoreBaseVisitor { + private static final String rootLocation = "mappingTest" + System.getProperty("file.separator"); + private static final String fileName = "tutorial"; + private static final String codeFile = + CS2ASMapping.rootLocation + CS2ASMapping.fileName + ".recore"; + private static final String ecoreFile = + CS2ASMapping.rootLocation + CS2ASMapping.fileName + ".ecore"; + + public static void main(final String[] args) { + ANTLRInputStream input = null; + final File file = new File(CS2ASMapping.codeFile); + try { + input = new ANTLRFileStream(file.getAbsolutePath()); + } catch (final IOException e) { + e.printStackTrace(); + } + final AlloyInEcoreLexer lexer = new AlloyInEcoreLexer(input); + final CommonTokenStream tokens = new CommonTokenStream(lexer); + final AlloyInEcoreParser parser = new AlloyInEcoreParser(tokens); + final ParseTree tree = parser.module(); + + /** ----------Loader for Data Structure------------------------------- **/ + final CS2ASMapping code2Ecore = CS2ASMapping.getInstance(); + code2Ecore.visit(tree); + } + + private static final CS2ASMapping instance = new CS2ASMapping(); + + private static final EcoreFactory factory = EcoreFactory.eINSTANCE; + + private static Map name2packageImport = new HashMap<>(); + private static Map name2ePackage = new HashMap<>(); + private static Map name2eClass = new HashMap<>(); + private static Map name2eDataType = new HashMap<>(); + private static Map name2eEnum = new HashMap<>(); + private static EModelElement root; + + private CS2ASMapping() {} + + public static CS2ASMapping getInstance() { + CS2ASMapping.name2packageImport.clear(); + CS2ASMapping.name2ePackage.clear(); + CS2ASMapping.name2eClass.clear(); + CS2ASMapping.name2eDataType.clear(); + CS2ASMapping.name2eEnum.clear(); + return CS2ASMapping.instance; + } + + @Override + public Object visitModule(final ModuleContext ctx) { + CS2ASInitializer.instance.visit(ctx); + CS2ASMapping.name2packageImport = CS2ASInitializer.instance.getName2packageImport(); + CS2ASMapping.name2ePackage = CS2ASInitializer.instance.getName2epackage(); + CS2ASMapping.name2eClass = CS2ASInitializer.instance.getName2eclass(); + CS2ASMapping.name2eDataType = CS2ASInitializer.instance.getName2edatatype(); + CS2ASMapping.name2eEnum = CS2ASInitializer.instance.getName2eenum(); + CS2ASMapping.root = CS2ASInitializer.instance.getRoot(); + + ctx.ownedPackageImport.forEach(opi -> { + visitPackageImport(opi); + }); + + ctx.ownedPackage.forEach(op -> { + visitEPackage(op); + }); + + CS2ASMapping.saveResource(CS2ASMapping.name2ePackage.get("tutorial"), CS2ASMapping.ecoreFile); + + return null; + } + + @Override + public Object visitPackageImport(final PackageImportContext ctx) { + final String name = ctx.name.getText(); + final String path = ctx.ownedPathName.getText(); + + final EAnnotation importAnnotation = createEAnnotation(AnnotationSources.IMPORT); + importAnnotation.getDetails().put(name, path); + + CS2ASMapping.root.getEAnnotations().add(importAnnotation); + return null; + } + + @Override + public EPackage visitEPackage(final EPackageContext ctx) { + return createEPackage(ctx); + } + + /** + * @param ctx + * @return + */ + private EPackage createEPackage(final EPackageContext ctx) { + final EPackage ePackage = CS2ASMapping.name2ePackage.get(ctx.name.getText()); + + if (ctx.visibility != null) { + final EAnnotation visibilityAnnotation = createVisibilityAnnotation(ctx.visibility.getText()); + ePackage.getEAnnotations().add(visibilityAnnotation); + } // DEFAULT NULL + + final String name = ctx.name.getText(); + ePackage.setName(name); + + final String nsPrefix = ctx.nsPrefix.getText(); + ePackage.setNsPrefix(nsPrefix); + + final String nsURI = ctx.nsURI.getText(); + ePackage.setNsURI(nsURI); + + ctx.ownedAnnotations.forEach(oa -> { + final EAnnotation eAnnotation = visitEAnnotation(oa); + ePackage.getEAnnotations().add(eAnnotation); + }); + + ctx.eSubPackages.forEach(esp -> { + final EPackage subPackage = visitEPackage(esp); + ePackage.getESubpackages().add(subPackage); + }); + + ctx.eClassifiers.forEach(ec -> { + final EClassifier eClassifier = visitEClassifier(ec); + ePackage.getEClassifiers().add(eClassifier); + }); + + ctx.eConstraints.forEach(ec -> { + final EAnnotation invariantAnnotation = visitInvariant(ec); + ePackage.getEAnnotations().add(invariantAnnotation); + }); + + return ePackage; + } + + @Override + public EClassifier visitEClassifier(final EClassifierContext ctx) { + return (EClassifier) super.visitEClassifier(ctx); + } + + + @Override + public EClass visitEClass(final EClassContext ctx) { + return createEClass(ctx); + } + + /** + * @param ctx + * @return + */ + private EClass createEClass(final EClassContext ctx) { + final EClass eClass = CS2ASMapping.name2eClass.get(ctx.name.getText()); + + if (ctx.visibility != null) { + final EAnnotation visibilityAnnotation = createVisibilityAnnotation(ctx.visibility.getText()); + eClass.getEAnnotations().add(visibilityAnnotation); + } // DEFAULT NULL + + final boolean isAbstract = ctx.isAbstract != null ? true : false; + // DEFAULT FALSE + eClass.setAbstract(isAbstract); + + final String name = ctx.name.getText(); + eClass.setName(name); + + ctx.eSuperTypes.forEach(est -> { + final EClass superType = (EClass) visitQualifiedName(est); + if (superType != null) { + eClass.getESuperTypes().add(superType); + } + }); + + if (ctx.instanceClassName != null) { + final String instanceClassName = ctx.instanceClassName.getText(); + eClass.setInstanceClassName(instanceClassName); + } + + final boolean isInterface = ctx.isInterface != null ? true : false; + // DEFAULT FALSE + eClass.setInterface(isInterface); + + ctx.ownedAnnotations.forEach(oa -> { + final EAnnotation eAnnotation = visitEAnnotation(oa); + eClass.getEAnnotations().add(eAnnotation); + }); + + ctx.eOperations.forEach(eo -> { + final EOperation eOperation = visitEOperation(eo); + eClass.getEOperations().add(eOperation); + }); + + ctx.eStructuralFeatures.forEach(esf -> { + final EStructuralFeature eStructuralFeature = visitEStructuralFeature(esf); + eClass.getEStructuralFeatures().add(eStructuralFeature); + }); + + ctx.eConstraints.forEach(ec -> { + final EAnnotation invariantAnnotation = visitInvariant(ec); + eClass.getEAnnotations().add(invariantAnnotation); + }); + + return eClass; + } + + @Override + public EStructuralFeature visitEStructuralFeature(final EStructuralFeatureContext ctx) { + return (EStructuralFeature) super.visitEStructuralFeature(ctx); + } + + @Override + public EAttribute visitEAttribute(final EAttributeContext ctx) { + return createEAttribute(ctx); + } + + /** + * @param ctx + * @return + */ + private EAttribute createEAttribute(final EAttributeContext ctx) { + final EAttribute eAttribute = CS2ASMapping.factory.createEAttribute(); + + if (ctx.visibility != null) { + final EAnnotation visibilityAnnotation = createVisibilityAnnotation(ctx.visibility.getText()); + eAttribute.getEAnnotations().add(visibilityAnnotation); + } // DEFAULT NULL + + final boolean isStatic = + ctx.qualifier.stream().anyMatch(q -> q.getText().equals(Qualification.STATIC.toString())); + if (isStatic) { + final EAnnotation staticAnnotation = + createEAnnotation(Qualification.STATIC.getAnnotationSource()); + // DEFAULT NULL + eAttribute.getEAnnotations().add(staticAnnotation); + } + + final String name = ctx.name.getText(); + eAttribute.setName(name); + + if (ctx.defaultValue != null) { + final String defaultValue = ctx.defaultValue.getText(); + eAttribute.setDefaultValue(defaultValue); + } // DEFAULT NULL + + if (ctx.eAttributeType != null) { + final EClassifier eType = visitEType(ctx.eAttributeType); + eAttribute.setEType(eType); + } // DEFAULT NULL + + if (ctx.multiplicity != null) { + final int[] multiplicity = visitEMultiplicity(ctx.multiplicity); + eAttribute.setLowerBound(multiplicity[0]); + eAttribute.setUpperBound(multiplicity[1]); + } else { // DEFAULT 1 + eAttribute.setLowerBound(1); + eAttribute.setUpperBound(1); + } + + final boolean isDerived = + ctx.qualifier.stream().anyMatch(p -> p.getText().equals(Qualification.DERIVED.toString())); + // DEFAULT FALSE + eAttribute.setDerived(isDerived); + + final boolean isId = + ctx.qualifier.stream().anyMatch(p -> p.getText().equals(Qualification.ID.toString())); + // DEFAULT FALSE + eAttribute.setID(isId); + + final boolean isOrdered = + ctx.qualifier.stream().anyMatch(p -> p.getText().equals(Qualification.ORDERED.toString())); + // DEFAULT FALSE + eAttribute.setOrdered(isOrdered); + + final boolean isReadonly = + ctx.qualifier.stream().anyMatch(p -> p.getText().equals(Qualification.READONLY.toString())); + // DEFAULT FALSE + eAttribute.setChangeable(isReadonly); + + final boolean isTransient = ctx.qualifier.stream() + .anyMatch(p -> p.getText().equals(Qualification.TRANSIENT.toString())); + // DEFAULT FALSE + eAttribute.setTransient(isTransient); + + final boolean isUnique = !ctx.qualifier.stream() + .anyMatch(p -> p.getText().equals(Qualification.NOT_UNIQUE.toString())); + // DEFAULT TRUE + eAttribute.setUnique(isUnique); + + final boolean isUnsettable = ctx.qualifier.stream() + .anyMatch(p -> p.getText().equals(Qualification.UNSETTABLE.toString())); + // DEFAULT FALSE + eAttribute.setUnsettable(isUnsettable); + + final boolean isVolatile = + ctx.qualifier.stream().anyMatch(p -> p.getText().equals(Qualification.VOLATILE.toString())); + // DEFAULT FALSE + eAttribute.setVolatile(isVolatile); + + ctx.ownedAnnotations.forEach(oa -> { + final EAnnotation eAnnotation = visitEAnnotation(oa); + eAttribute.getEAnnotations().add(eAnnotation); + }); + + // TODO INITIAL EXPRESSION + + // TODO DERIVATION EXPRESSION + + return eAttribute; + } + + @Override + public EReference visitEReference(final EReferenceContext ctx) { + return createEReference(ctx); + } + + /** + * @param ctx + * @return + */ + private EReference createEReference(final EReferenceContext ctx) { + final EReference eReference = CS2ASMapping.factory.createEReference(); + + if (ctx.visibility != null) { + final EAnnotation visibilityAnnotation = createVisibilityAnnotation(ctx.visibility.getText()); + eReference.getEAnnotations().add(visibilityAnnotation); + } // DEFAULT NULL + + final boolean isStatic = + ctx.qualifier.stream().anyMatch(q -> q.getText().equals(Qualification.STATIC.toString())); + if (isStatic) { + final EAnnotation staticAnnotation = + createEAnnotation(Qualification.STATIC.getAnnotationSource()); + // DEFAULT NULL + eReference.getEAnnotations().add(staticAnnotation); + } + + final String name = ctx.name.getText(); + eReference.setName(name); + + final EClassifier eType = visitEType(ctx.eReferenceType); + eReference.setEType(eType); + + if (ctx.opposite != null) { + final String oppositeName = ctx.opposite.getText(); + final EClass oppositeType = (EClass) eType; + final EReference eOpposite = oppositeType.getEReferences().stream() + .filter(er -> er.getName().equals(oppositeName)).findFirst().orElse(null); + eReference.setEOpposite(eOpposite); + } // DEFAULT NULL + + if (ctx.multiplicity != null) { + final int[] multiplicity = visitEMultiplicity(ctx.multiplicity); + eReference.setLowerBound(multiplicity[0]); + eReference.setUpperBound(multiplicity[1]); + } else { // DEFAULT 1 + eReference.setLowerBound(1); + eReference.setUpperBound(1); + } + + if (ctx.defaultValue != null) { + final String defaultValue = ctx.defaultValue.getText(); + eReference.setDefaultValue(defaultValue); + } // DEFAULT NULL + + final boolean isComposes = + ctx.qualifier.stream().anyMatch(p -> p.getText().equals(Qualification.COMPOSES.toString())); + // DEFAULT FALSE + eReference.setContainment(isComposes); + + final boolean isDerived = + ctx.qualifier.stream().anyMatch(p -> p.getText().equals(Qualification.DERIVED.toString())); + // DEFAULT FALSE + eReference.setDerived(isDerived); + + final boolean isOrdered = + ctx.qualifier.stream().anyMatch(p -> p.getText().equals(Qualification.ORDERED.toString())); + // DEFAULT FALSE + eReference.setOrdered(isOrdered); + + final boolean isReadonly = + ctx.qualifier.stream().anyMatch(p -> p.getText().equals(Qualification.READONLY.toString())); + // DEFAULT FALSE + eReference.setChangeable(isReadonly); + + final boolean isResolve = + ctx.qualifier.stream().anyMatch(p -> p.getText().equals(Qualification.RESOLVE.toString())); + // DEFAULT FALSE + eReference.setResolveProxies(isResolve); + + final boolean isTransient = ctx.qualifier.stream() + .anyMatch(p -> p.getText().equals(Qualification.TRANSIENT.toString())); + // DEFAULT FALSE + eReference.setTransient(isTransient); + + final boolean isUnique = !ctx.qualifier.stream() + .anyMatch(p -> p.getText().equals(Qualification.NOT_UNIQUE.toString())); + // DEFAULT TRUE + eReference.setUnique(isUnique); + + final boolean isUnsettable = ctx.qualifier.stream() + .anyMatch(p -> p.getText().equals(Qualification.UNSETTABLE.toString())); + // DEFAULT FALSE + eReference.setUnsettable(isUnsettable); + + final boolean isVolatile = + ctx.qualifier.stream().anyMatch(p -> p.getText().equals(Qualification.VOLATILE.toString())); + // DEFAULT FALSE + eReference.setVolatile(isVolatile); + + ctx.ownedAnnotations.forEach(oa -> { + final EAnnotation eAnnotation = visitEAnnotation(oa); + eReference.getEAnnotations().add(eAnnotation); + }); + + // TODO REFERRED KEYS ? + + // TODO OWNED INITIAL EXPRESSIONS + + // TODO OWNED DERIVATION EXPRESSIONS + + return eReference; + } + + @Override + public EOperation visitEOperation(final EOperationContext ctx) { + return createEOperation(ctx); + } + + /** + * @param ctx + * @return + */ + private EOperation createEOperation(final EOperationContext ctx) { + final EOperation eOperation = CS2ASMapping.factory.createEOperation(); + + if (ctx.visibility != null) { + final EAnnotation visibilityAnnotation = createVisibilityAnnotation(ctx.visibility.getText()); + eOperation.getEAnnotations().add(visibilityAnnotation); + } // DEFAULT NULL + + final boolean isStatic = + ctx.qualifier.stream().anyMatch(q -> q.getText().equals(Qualification.STATIC.toString())); + if (isStatic) { + final EAnnotation staticAnnotation = + createEAnnotation(Qualification.STATIC.getAnnotationSource()); + // DEFAULT NULL + eOperation.getEAnnotations().add(staticAnnotation); + } + + final String name = ctx.name.getText(); + eOperation.setName(name); + + ctx.eParameters.forEach(ep -> { + final EParameter eParameter = visitEParameter(ep); + eOperation.getEParameters().add(eParameter); + }); + + final EClassifier returnType = visitEType(ctx.returnType); + eOperation.setEType(returnType); + + final int[] multiplicity = visitEMultiplicity(ctx.multiplicity); + eOperation.setLowerBound(multiplicity[0]); + eOperation.setUpperBound(multiplicity[1]); + + // TODO OWNED EXCEPTION (NOT IMPLEMENTED ON BNF) + + // TODO IS IT WRONG? + // final Boolean isDerived = + // ctx.qualifier.stream().anyMatch(q -> q.getText().equals(Qualification.DERIVED.toString())); + // final EAnnotation derivedAnnotation = createDerivedAnnotation(isDerived); + // // DEFAULT FALSE + // eOperation.getEAnnotations().add(derivedAnnotation); + + final boolean isOrdered = + ctx.qualifier.stream().anyMatch(q -> q.getText().equals(Qualification.ORDERED.toString())); + // DEFAULT FALSE + eOperation.setOrdered(isOrdered); + + final boolean isUnique = !ctx.qualifier.stream() + .anyMatch(q -> q.getText().equals(Qualification.NOT_UNIQUE.toString())); + // DEFAULT TRUE + eOperation.setUnique(isUnique); + + ctx.ownedAnnotations.forEach(oa -> { + final EAnnotation eAnnotation = visitEAnnotation(oa); + eOperation.getEAnnotations().add(eAnnotation); + }); + + ctx.ownedPreconditions.forEach(opc -> { + final EAnnotation preconditionAnnotation = visitPrecondition(opc); + eOperation.getEAnnotations().add(preconditionAnnotation); + }); + + ctx.ownedBodyExpression.forEach(obe -> { + final EAnnotation bodyAnnotation = visitBody(obe); + eOperation.getEAnnotations().add(bodyAnnotation); + }); + + ctx.ownedPostconditions.forEach(opc -> { + final EAnnotation postconditionAnnotation = visitPostcondition(opc); + eOperation.getEAnnotations().add(postconditionAnnotation); + }); + + return eOperation; + } + + @Override + public EParameter visitEParameter(final EParameterContext ctx) { + return createEParameter(ctx); + } + + /** + * @param ctx + * @return + */ + private EParameter createEParameter(final EParameterContext ctx) { + final EParameter eParameter = CS2ASMapping.factory.createEParameter(); + + final String name = ctx.name.getText(); + eParameter.setName(name); + + if (ctx.ownedType != null) { + final EClassifier eType = visitEType(ctx.ownedType); + eParameter.setEType(eType); + } // DEFAULT NULL + + if (ctx.ownedMultiplicity != null) { + final int[] multiplicity = visitEMultiplicity(ctx.ownedMultiplicity); + eParameter.setLowerBound(multiplicity[0]); + eParameter.setUpperBound(multiplicity[1]); + } else { // DEFAULT 1 + eParameter.setLowerBound(1); + eParameter.setUpperBound(1); + } + + final boolean isOrdered = + ctx.qualifier.stream().anyMatch(q -> q.getText().equals(Qualification.ORDERED.toString())); + // DEFAULT FALSE + eParameter.setOrdered(isOrdered); + + final boolean isUnique = !ctx.qualifier.stream() + .anyMatch(q -> q.getText().equals(Qualification.NOT_UNIQUE.toString())); + // DEFAULT TRUE + eParameter.setUnique(isUnique); + + ctx.ownedAnnotations.forEach(oa -> { + final EAnnotation eAnnotation = visitEAnnotation(oa); + eParameter.getEAnnotations().add(eAnnotation); + }); + + return eParameter; + } + + @Override + public EClassifier visitEType(final ETypeContext ctx) { + return (EClassifier) super.visitEType(ctx); + } + + @Override + public int[] visitEMultiplicity(final EMultiplicityContext ctx) { + int lower = 0; + int upper = 1; + if (ctx.stringBounds != null) { + final String stringBound = ctx.stringBounds.getText(); + switch (stringBound) { + case "*": + lower = 0; + upper = -1; + break; + case "+": + lower = 1; + upper = -1; + case "?": + lower = 0; + upper = 1; + default: + break; + } + } else { + lower = Integer.valueOf(ctx.lowerBound.getText()); + if (ctx.upperBound != null) { + upper = Integer.valueOf(ctx.upperBound.getText()); + } else { + upper = lower; + } + } // TODO ('|?' | isNullFree= '|1')? + return new int[] {lower, upper}; + } + + @Override + public EDataType visitEDataType(final EDataTypeContext ctx) { + return createEDataType(ctx); + } + + /** + * @param ctx + * @return + */ + private EDataType createEDataType(final EDataTypeContext ctx) { + final EDataType eDataType = CS2ASMapping.name2eDataType.get(ctx.name.getText()); + + if (ctx.isPrimitive != null) { + final EAnnotation primitiveAnnotation = + createEAnnotation(Qualification.PRIMITIVE.getAnnotationSource()); + // DEFAULT NULL + eDataType.getEAnnotations().add(primitiveAnnotation); + } + + final String name = ctx.name.getText(); + eDataType.setName(name); + + // TODO OWNED SIGNATURE = TEMPLATE SIGNATURE + + if (ctx.instanceClassName != null) { + final String instanceClassName = ctx.instanceClassName.getText(); + eDataType.setInstanceClassName(instanceClassName); + } + + final boolean isSerializable = ctx.isSerializable != null + && ctx.isSerializable.equals(Qualification.SERIALIZABLE.toString()) ? true : false; + // TODO DEFAULT FALSE, ANCAK ECORE DA TRUE, TARTIS!. + eDataType.setSerializable(isSerializable); + + ctx.ownedAnnotations.forEach(oa -> { + final EAnnotation eAnnotation = visitEAnnotation(oa); + eDataType.getEAnnotations().add(eAnnotation); + }); + + ctx.ownedConstraints.forEach(oc -> { + final EAnnotation invariantAnnotation = visitInvariant(oc); + eDataType.getEAnnotations().add(invariantAnnotation); + }); + + return eDataType; + } + + @Override + public EDataType visitEPrimitiveType(final EPrimitiveTypeContext ctx) { + switch (ctx.getText()) { + case "Boolean": // EBoolean + return EcorePackage.eINSTANCE.getEBoolean(); + case "Integer": // EBigInteger + return EcorePackage.eINSTANCE.getEInt(); + case "String": // EString + return EcorePackage.eINSTANCE.getEString(); + case "Real": // EBigDecimal + return EcorePackage.eINSTANCE.getEBigDecimal(); + case "UnlimitedNatural": // EBigInteger + return EcorePackage.eINSTANCE.getEBigInteger(); + default: + return null; + } + } + + @Override + public EEnum visitEEnum(final EEnumContext ctx) { + return createEEnum(ctx); + } + + /** + * @param ctx + * @return + */ + private EEnum createEEnum(final EEnumContext ctx) { + final EEnum eEnum = CS2ASMapping.name2eEnum.get(ctx.name.getText()); + + if (ctx.visibility != null) { + final EAnnotation visibilityAnnotation = createVisibilityAnnotation(ctx.visibility.getText()); + eEnum.getEAnnotations().add(visibilityAnnotation); + } // DEFAULT NULL + + final String name = ctx.name.getText(); + eEnum.setName(name); + + // TODO OWNED SIGNATURE = TEMPLATE SIGNATURE + + if (ctx.instanceClassName != null) { + final String instanceClassName = ctx.instanceClassName.getText(); + eEnum.setInstanceClassName(instanceClassName); + } + + final boolean isSerializable = ctx.isSerializable != null + && ctx.isSerializable.equals(Qualification.SERIALIZABLE.toString()) ? true : false; + // TODO DEFAULT FALSE, ANCAK ECORE DA TRUE, TARTIS!. + eEnum.setSerializable(isSerializable); + + ctx.ownedAnnotations.forEach(oa -> { + final EAnnotation eAnnotation = visitEAnnotation(oa); + eEnum.getEAnnotations().add(eAnnotation); + }); + + ctx.ownedLiteral.forEach(l -> { + final EEnumLiteral eEnumLiteral = visitEEnumLiteral(l); + eEnum.getELiterals().add(eEnumLiteral); + }); + + ctx.ownedConstraint.forEach(oc -> { + final EAnnotation invariantAnnotation = visitInvariant(oc); + eEnum.getEAnnotations().add(invariantAnnotation); + }); + + return eEnum; + } + + @Override + public EEnumLiteral visitEEnumLiteral(final EEnumLiteralContext ctx) { + return createEEnumLiteral(ctx); + } + + /** + * @param ctx + * @return + */ + private EEnumLiteral createEEnumLiteral(final EEnumLiteralContext ctx) { + final EEnumLiteral eEnumLiteral = CS2ASMapping.factory.createEEnumLiteral(); + + final String name = ctx.name.getText(); + eEnumLiteral.setName(name); + + if (ctx.value != null) { + final int value = Integer.parseInt(ctx.value.getText()); + eEnumLiteral.setValue(value); + } + + ctx.ownedAnnotations.forEach(oa -> { + final EAnnotation eAnnotation = visitEAnnotation(oa); + eEnumLiteral.getEAnnotations().add(eAnnotation); + }); + + return eEnumLiteral; + } + + @Override + public EAnnotation visitEAnnotation(final EAnnotationContext ctx) { + final String source = ctx.name.getText(); + final EAnnotation eAnnotation = createEAnnotation(source); + + ctx.ownedDetails.forEach(od -> { + final String key = od.name.getText(); + final String value = od.value.getText(); + eAnnotation.getDetails().put(key, value); + }); + + ctx.ownedAnnotations.forEach(oa -> { + final EAnnotation ownedAnnoation = visitEAnnotation(oa); + eAnnotation.getEAnnotations().add(ownedAnnoation); + }); + + ctx.ownedContents.forEach(oc -> { + final EModelElement eModelElement = visitEModelElement(oc); + eAnnotation.getContents().add(eModelElement); + }); + + ctx.ownedReferences.forEach(or -> { + final EObject eModelElementRef = visitEModelElementRef(or); + eAnnotation.getReferences().add(eModelElementRef); + }); + + return eAnnotation; + } + + private EAnnotation createEAnnotation(final String source) { + final EAnnotation eAnnotation = CS2ASMapping.factory.createEAnnotation(); + + eAnnotation.setSource(source); + + return eAnnotation; + } + + @Override + public EModelElement visitEModelElement(final EModelElementContext ctx) { + return (EModelElement) super.visitEModelElement(ctx); + } + + @Override + public ENamedElement visitENamedElement(final ENamedElementContext ctx) { + return (ENamedElement) super.visitENamedElement(ctx); + } + + @Override + public ETypedElement visitETypedElement(final ETypedElementContext ctx) { + return (ETypedElement) super.visitETypedElement(ctx); + } + + @Override + public EObject visitEModelElementRef(final EModelElementRefContext ctx) { + return visitQualifiedName(ctx.ownedPathName); + } + + @Override + public EAnnotation visitBody(final BodyContext ctx) { + final String source = AnnotationSources.BODY; + final EAnnotation eAnnotation = createEAnnotation(source); + + if (ctx.name != null) { + final String name = ctx.name.getText(); + eAnnotation.getDetails().put(Qualification.NAME.toString(), name); + } + + if (ctx.ownedExpression != null) { + final String expression = visitExpression(ctx.ownedExpression); + eAnnotation.getDetails().put(Qualification.EXPRESSION.toString(), expression); + } + + return eAnnotation; + } + + @Override + public EAnnotation visitInvariant(final InvariantContext ctx) { + final String source = AnnotationSources.INVARIANT; + final EAnnotation eAnnotation = createEAnnotation(source); + + if (ctx.name != null) { + final String name = ctx.name.getText(); + eAnnotation.getDetails().put(Qualification.NAME.toString(), name); + } + + final Boolean isCallable = ctx.isCallable != null ? true : false; + eAnnotation.getDetails().put(Qualification.CALLABLE.toString(), isCallable.toString()); + + if (ctx.message != null) { + final String message = ctx.message.getText(); + eAnnotation.getDetails().put(Qualification.MESSAGE.toString(), message); + } + + if (ctx.ownedSpecification != null) { + final String formula = visitFormula(ctx.ownedSpecification); + eAnnotation.getDetails().put(Qualification.FORMULA.toString(), formula); + } + + return eAnnotation; + } + + @Override + public EAnnotation visitPrecondition(final PreconditionContext ctx) { + final String source = AnnotationSources.PRECONDITION; + final EAnnotation eAnnotation = createEAnnotation(source); + + if (ctx.name != null) { + final String name = ctx.name.getText(); + eAnnotation.getDetails().put(Qualification.NAME.toString(), name); + } + + if (ctx.message != null) { + final String message = ctx.message.getText(); + eAnnotation.getDetails().put(Qualification.MESSAGE.toString(), message); + } + + if (ctx.ownedSpecification != null) { + final String formula = visitFormula(ctx.ownedSpecification); + eAnnotation.getDetails().put(Qualification.FORMULA.toString(), formula); + } + + return eAnnotation; + } + + @Override + public EAnnotation visitPostcondition(final PostconditionContext ctx) { + final String source = AnnotationSources.POSTCONDITION; + final EAnnotation eAnnotation = createEAnnotation(source); + + if (ctx.name != null) { + final String name = ctx.name.getText(); + eAnnotation.getDetails().put(Qualification.NAME.toString(), name); + } + + if (ctx.message != null) { + final String message = ctx.message.getText(); + eAnnotation.getDetails().put(Qualification.MESSAGE.toString(), message); + } + + if (ctx.ownedSpecification != null) { + final String formula = visitFormula(ctx.ownedSpecification); + eAnnotation.getDetails().put(Qualification.FORMULA.toString(), formula); + } + + return eAnnotation; + } + + /** + * @param visibilityText + * @param ctx + * @return + */ + private EAnnotation createVisibilityAnnotation(final String visibilityText) { + final EAnnotation eAnnoation = + createEAnnotation(Qualification.VISIBILITY.getAnnotationSource()); + + eAnnoation.getDetails().put(Qualification.VISIBILITY.toString(), visibilityText); + + return eAnnoation; + } + + @Override + public String visitExpression(final ExpressionContext ctx) { + // TODO will be implemented when expressions are defined + return ctx.getText(); + } + + @Override + public String visitFormula(final FormulaContext ctx) { + // TODO will be implemented when formulas are defined + return ctx.getText(); + } + + @Override + public EObject visitQualifiedName(final QualifiedNameContext ctx) { + if (ctx.lastPart != null) { + String[] relativePathFragments; + final String importName = ctx.firstPart.getText(); + final String objectName = ctx.lastPart.getText(); + if (ctx.midParts != null) { + relativePathFragments = new String[ctx.midParts.size() + 1]; + for (int i = 0; i < ctx.midParts.size(); i++) { + relativePathFragments[i] = ctx.midParts.get(i).getText(); + } + relativePathFragments[relativePathFragments.length - 1] = objectName; + } else { // : importName::ObjectName ... ; + relativePathFragments = new String[1]; + relativePathFragments[0] = objectName; + } + if (CS2ASMapping.name2packageImport.containsKey(importName)) { + final PackageImport packageImport = CS2ASMapping.name2packageImport.get(importName); + return packageImport.getElement(relativePathFragments); + } + } else { // : ObjectName ... ; + final String objectName = ctx.firstPart.getText(); + if (CS2ASMapping.name2eClass.containsKey(objectName)) { + return CS2ASMapping.name2eClass.get(objectName); + } else if (CS2ASMapping.name2eDataType.containsKey(objectName)) { + return CS2ASMapping.name2eDataType.get(objectName); + } else if (CS2ASMapping.name2eEnum.containsKey(objectName)) { + return CS2ASMapping.name2eEnum.get(objectName); + } + } + return null; + } + + public static void saveResource(final EObject root, final String savePath) { + final ResourceSet metaResourceSet = new ResourceSetImpl(); + + /* + * Register XML Factory implementation to handle .ecore files + */ + metaResourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap().put("ecore", + new XMLResourceFactoryImpl()); + + /* + * Create empty resource with the given URI + */ + final Resource metaResource = + metaResourceSet.createResource(URI.createPlatformResourceURI(savePath, true)); + + /* + * Add bookStoreEPackage to contents list of the resource + */ + metaResource.getContents().add(root); + + try { + /* + * Save the resource + */ + metaResource.save(null); + } catch (final IOException e) { + e.printStackTrace(); + } + } +} diff --git a/Source/eu.modelwriter.core.alloyinecore.ui/src/eu/modelwriter/core/alloyinecore/ui/editor/AlloyInEcoreEditor.java b/Source/eu.modelwriter.core.alloyinecore.ui/src/eu/modelwriter/core/alloyinecore/ui/editor/AlloyInEcoreEditor.java new file mode 100644 index 00000000..7bd48eaa --- /dev/null +++ b/Source/eu.modelwriter.core.alloyinecore.ui/src/eu/modelwriter/core/alloyinecore/ui/editor/AlloyInEcoreEditor.java @@ -0,0 +1,46 @@ +package eu.modelwriter.core.alloyinecore.ui.editor; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.ui.IEditorInput; +import org.eclipse.ui.editors.text.TextEditor; + +import eu.modelwriter.core.alloyinecore.ui.model.AlloyInEcoreDocument; + +public class AlloyInEcoreEditor extends TextEditor { + + private ColorManager colorManager; + + public AlloyInEcoreEditor() { + super(); + colorManager = new ColorManager(); + setSourceViewerConfiguration(new ViewerConfiguration(colorManager)); + setDocumentProvider(new DocumentProvider()); + } + + @Override + public IEditorInput getEditorInput() { + return super.getEditorInput(); + } + + @Override + public void dispose() { + colorManager.dispose(); + super.dispose(); + } + + @Override + protected void initializeEditor() { + super.initializeEditor(); + } + + @Override + public void doSave(IProgressMonitor progressMonitor) { + DocumentProvider documentProvider = (DocumentProvider) getDocumentProvider(); + AlloyInEcoreDocument document = (AlloyInEcoreDocument) documentProvider.getDocument(); + boolean success = document.saveInEcore(); + if (success) { + progressMonitor.done(); + } + } + +} diff --git a/Source/eu.modelwriter.core.alloyinecore.ui/src/eu/modelwriter/core/alloyinecore/ui/editor/ColorConstants.java b/Source/eu.modelwriter.core.alloyinecore.ui/src/eu/modelwriter/core/alloyinecore/ui/editor/ColorConstants.java new file mode 100644 index 00000000..4262ab5b --- /dev/null +++ b/Source/eu.modelwriter.core.alloyinecore.ui/src/eu/modelwriter/core/alloyinecore/ui/editor/ColorConstants.java @@ -0,0 +1,10 @@ +package eu.modelwriter.core.alloyinecore.ui.editor; + +import org.eclipse.swt.graphics.RGB; + +public interface ColorConstants { + RGB COMMENT = new RGB(0, 128, 0); + RGB STRING = new RGB(0, 0, 128); + RGB KEYWORD = new RGB(128, 0, 0); + RGB DEFAULT = new RGB(0, 0, 0); +} diff --git a/Source/eu.modelwriter.core.alloyinecore.ui/src/eu/modelwriter/core/alloyinecore/ui/editor/ColorManager.java b/Source/eu.modelwriter.core.alloyinecore.ui/src/eu/modelwriter/core/alloyinecore/ui/editor/ColorManager.java new file mode 100644 index 00000000..a49a7526 --- /dev/null +++ b/Source/eu.modelwriter.core.alloyinecore.ui/src/eu/modelwriter/core/alloyinecore/ui/editor/ColorManager.java @@ -0,0 +1,28 @@ +package eu.modelwriter.core.alloyinecore.ui.editor; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.RGB; +import org.eclipse.swt.widgets.Display; + +public class ColorManager { + + protected Map fColorTable = new HashMap(10); + + public void dispose() { + Iterator e = fColorTable.values().iterator(); + while (e.hasNext()) + ((Color) e.next()).dispose(); + } + public Color getColor(RGB rgb) { + Color color = (Color) fColorTable.get(rgb); + if (color == null) { + color = new Color(Display.getCurrent(), rgb); + fColorTable.put(rgb, color); + } + return color; + } +} diff --git a/Source/eu.modelwriter.core.alloyinecore.ui/src/eu/modelwriter/core/alloyinecore/ui/editor/DocumentProvider.java b/Source/eu.modelwriter.core.alloyinecore.ui/src/eu/modelwriter/core/alloyinecore/ui/editor/DocumentProvider.java new file mode 100644 index 00000000..534c8433 --- /dev/null +++ b/Source/eu.modelwriter.core.alloyinecore.ui/src/eu/modelwriter/core/alloyinecore/ui/editor/DocumentProvider.java @@ -0,0 +1,30 @@ +package eu.modelwriter.core.alloyinecore.ui.editor; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.IDocumentPartitioner; +import org.eclipse.jface.text.rules.FastPartitioner; +import org.eclipse.ui.editors.text.FileDocumentProvider; + +import eu.modelwriter.core.alloyinecore.ui.model.AlloyInEcoreDocument; + +public class DocumentProvider extends FileDocumentProvider { + + private IDocument document; + + @Override + protected IDocument createDocument(Object element) throws CoreException { + document = new AlloyInEcoreDocument(element); + if (document != null) { + IDocumentPartitioner partitioner = + new FastPartitioner(new PartitionScanner(), PartitionScanner.ALL_PARTITIONS); + partitioner.connect(document); + document.setDocumentPartitioner(partitioner); + } + return document; + } + + public IDocument getDocument() { + return document; + } +} diff --git a/Source/eu.modelwriter.core.alloyinecore.ui/src/eu/modelwriter/core/alloyinecore/ui/editor/PartitionScanner.java b/Source/eu.modelwriter.core.alloyinecore.ui/src/eu/modelwriter/core/alloyinecore/ui/editor/PartitionScanner.java new file mode 100644 index 00000000..3190dbe5 --- /dev/null +++ b/Source/eu.modelwriter.core.alloyinecore.ui/src/eu/modelwriter/core/alloyinecore/ui/editor/PartitionScanner.java @@ -0,0 +1,35 @@ +package eu.modelwriter.core.alloyinecore.ui.editor; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.rules.EndOfLineRule; +import org.eclipse.jface.text.rules.IPredicateRule; +import org.eclipse.jface.text.rules.IToken; +import org.eclipse.jface.text.rules.MultiLineRule; +import org.eclipse.jface.text.rules.RuleBasedPartitionScanner; +import org.eclipse.jface.text.rules.Token; + +public class PartitionScanner extends RuleBasedPartitionScanner { + + public final static String PACKAGE = "__alloyinecore_package"; + public final static String CLASS = "__alloyinecore_class"; + private static final String COMMENT = "__alloyinecore_class";; + public final static String[] ALL_PARTITIONS = + new String[] {IDocument.DEFAULT_CONTENT_TYPE, PACKAGE, CLASS}; + + public PartitionScanner() { + + IToken packageToken = new Token(PACKAGE); + IToken classToken = new Token(CLASS); + IToken commentToken = new Token(COMMENT); + List rulez = new ArrayList<>(); + rulez.add(new EndOfLineRule("--", commentToken)); + rulez.add(new MultiLineRule("package", "}", packageToken)); + rulez.add(new MultiLineRule("class", "}", classToken)); + final IPredicateRule[] result = new IPredicateRule[rulez.size()]; + rulez.toArray(result); + setPredicateRules(result); + } +} diff --git a/Source/eu.modelwriter.core.alloyinecore.ui/src/eu/modelwriter/core/alloyinecore/ui/editor/Scanner.java b/Source/eu.modelwriter.core.alloyinecore.ui/src/eu/modelwriter/core/alloyinecore/ui/editor/Scanner.java new file mode 100644 index 00000000..055e2e2c --- /dev/null +++ b/Source/eu.modelwriter.core.alloyinecore.ui/src/eu/modelwriter/core/alloyinecore/ui/editor/Scanner.java @@ -0,0 +1,63 @@ +package eu.modelwriter.core.alloyinecore.ui.editor; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.jface.text.TextAttribute; +import org.eclipse.jface.text.rules.IRule; +import org.eclipse.jface.text.rules.IToken; +import org.eclipse.jface.text.rules.IWhitespaceDetector; +import org.eclipse.jface.text.rules.IWordDetector; +import org.eclipse.jface.text.rules.MultiLineRule; +import org.eclipse.jface.text.rules.RuleBasedScanner; +import org.eclipse.jface.text.rules.Token; +import org.eclipse.jface.text.rules.WhitespaceRule; +import org.eclipse.jface.text.rules.WordRule; +import org.eclipse.swt.SWT; + + +public class Scanner extends RuleBasedScanner { + + public static final String[] keywords = new String[] {"package", "class", "extends", "import", + "public", "final", "private", "static", "invariant", "attribute", "operation", "enum", + "datatype", "body", "precondition", "postcondition", "property"}; + + public Scanner(ColorManager manager) { + IToken keyword = + new Token(new TextAttribute(manager.getColor(ColorConstants.KEYWORD), null, SWT.BOLD)); + IToken defaultToken = new Token(new TextAttribute(manager.getColor(ColorConstants.DEFAULT))); + IToken stringToken = new Token(new TextAttribute(manager.getColor(ColorConstants.STRING))); + final List rules = new ArrayList(); + // whitespace rule for skipping whitespaces. + rules.add(new WhitespaceRule(new IWhitespaceDetector() { + + @Override + public boolean isWhitespace(final char c) { + return Character.isWhitespace(c); + } + })); + + final WordRule keywordRule = new WordRule(new IWordDetector() { + + @Override + public boolean isWordPart(final char c) { + return Character.isLetter(c); + } + + @Override + public boolean isWordStart(final char c) { + return Character.isLetter(c); + } + }, defaultToken); + + // add all keywords to keyword rule for keyword matching. + for (int i = 0; i < keywords.length; i++) { + keywordRule.addWord(keywords[i], keyword); + } + rules.add(new MultiLineRule("\'", "\'", stringToken)); + rules.add(keywordRule); + final IRule[] result = new IRule[rules.size()]; + rules.toArray(result); + setRules(result); + } +} diff --git a/Source/eu.modelwriter.core.alloyinecore.ui/src/eu/modelwriter/core/alloyinecore/ui/editor/ViewerConfiguration.java b/Source/eu.modelwriter.core.alloyinecore.ui/src/eu/modelwriter/core/alloyinecore/ui/editor/ViewerConfiguration.java new file mode 100644 index 00000000..22e28931 --- /dev/null +++ b/Source/eu.modelwriter.core.alloyinecore.ui/src/eu/modelwriter/core/alloyinecore/ui/editor/ViewerConfiguration.java @@ -0,0 +1,54 @@ +package eu.modelwriter.core.alloyinecore.ui.editor; + +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.TextAttribute; +import org.eclipse.jface.text.presentation.IPresentationReconciler; +import org.eclipse.jface.text.presentation.PresentationReconciler; +import org.eclipse.jface.text.rules.DefaultDamagerRepairer; +import org.eclipse.jface.text.rules.Token; +import org.eclipse.jface.text.source.ISourceViewer; +import org.eclipse.jface.text.source.SourceViewerConfiguration; + +public class ViewerConfiguration extends SourceViewerConfiguration { + private Scanner scanner; + private ColorManager colorManager; + + public ViewerConfiguration(ColorManager colorManager) { + this.colorManager = colorManager; + } + + @Override + public String[] getConfiguredContentTypes(ISourceViewer sourceViewer) { + return PartitionScanner.ALL_PARTITIONS; + } + + protected Scanner getScanner() { + if (scanner == null) { + scanner = new Scanner(colorManager); + scanner.setDefaultReturnToken( + new Token(new TextAttribute(colorManager.getColor(ColorConstants.DEFAULT)))); + } + return scanner; + } + + @Override + public IPresentationReconciler getPresentationReconciler(ISourceViewer sourceViewer) { + PresentationReconciler reconciler = new PresentationReconciler(); + + DefaultDamagerRepairer dr = new DefaultDamagerRepairer(getScanner()); + + dr = new DefaultDamagerRepairer(getScanner()); + reconciler.setDamager(dr, IDocument.DEFAULT_CONTENT_TYPE); + reconciler.setRepairer(dr, IDocument.DEFAULT_CONTENT_TYPE); + + dr = new DefaultDamagerRepairer(getScanner()); + reconciler.setDamager(dr, PartitionScanner.PACKAGE); + reconciler.setRepairer(dr, PartitionScanner.PACKAGE); + + dr = new DefaultDamagerRepairer(getScanner()); + reconciler.setDamager(dr, PartitionScanner.CLASS); + reconciler.setRepairer(dr, PartitionScanner.CLASS); + return reconciler; + } + +} diff --git a/Source/eu.modelwriter.core.alloyinecore.ui/src/eu/modelwriter/core/alloyinecore/ui/editor/WhitespaceDetector.java b/Source/eu.modelwriter.core.alloyinecore.ui/src/eu/modelwriter/core/alloyinecore/ui/editor/WhitespaceDetector.java new file mode 100644 index 00000000..1c2a7863 --- /dev/null +++ b/Source/eu.modelwriter.core.alloyinecore.ui/src/eu/modelwriter/core/alloyinecore/ui/editor/WhitespaceDetector.java @@ -0,0 +1,11 @@ +package eu.modelwriter.core.alloyinecore.ui.editor; + +import org.eclipse.jface.text.rules.IWhitespaceDetector; + +public class WhitespaceDetector implements IWhitespaceDetector { + + @Override + public boolean isWhitespace(char c) { + return (c == ' ' || c == '\t' || c == '\n' || c == '\r'); + } +} diff --git a/Source/eu.modelwriter.core.alloyinecore.ui/src/eu/modelwriter/core/alloyinecore/ui/model/AlloyInEcoreDocument.java b/Source/eu.modelwriter.core.alloyinecore.ui/src/eu/modelwriter/core/alloyinecore/ui/model/AlloyInEcoreDocument.java new file mode 100644 index 00000000..dc305a13 --- /dev/null +++ b/Source/eu.modelwriter.core.alloyinecore.ui/src/eu/modelwriter/core/alloyinecore/ui/model/AlloyInEcoreDocument.java @@ -0,0 +1,78 @@ +package eu.modelwriter.core.alloyinecore.ui.model; + +import java.io.IOException; + +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EPackage; +import org.eclipse.jface.text.Document; +import org.eclipse.ui.IFileEditorInput; + +import eu.modelwriter.configuration.internal.EcoreUtilities; + +public class AlloyInEcoreDocument extends Document { + + public static final String EDITOR_ID = "eu.modelwriter.core.alloyinecore.ui.editor"; + private EObject ecoreRoot; + + public AlloyInEcoreDocument() { + super(); + } + + public AlloyInEcoreDocument(Object element) { + if (element instanceof IFileEditorInput) { + IFileEditorInput input = (IFileEditorInput) element; + try { + ecoreRoot = EcoreUtilities.getRootObject(input.getFile().getFullPath().toString()); + if (!refreshEditor()) { + set(""); + } + } catch (IOException e) { + set(""); + e.printStackTrace(); + } + } + } + + + /** + * + * @return current ecore object + */ + public EObject getEcoreRoot() { + return ecoreRoot; + } + + + /** + * Saves editor input to current ecore file. + * + * @return true if succeed. + */ + public boolean saveInEcore() { + try { + ecoreRoot.eResource().save(null); + return true; + } catch (IOException e) { + e.printStackTrace(); + return false; + } + } + + /** + * Refreshs the editor with current ecore file. + * + * @return true if succeed. + */ + public boolean refreshEditor() { + if (ecoreRoot != null) { + set(EcoreTranslator.translate((EPackage) ecoreRoot).toString()); + return true; + } else + return false; + } + + @Override + public void set(String text) { + super.set(text); + } +} diff --git a/Source/eu.modelwriter.core.alloyinecore.ui/src/eu/modelwriter/core/alloyinecore/ui/model/AnnotationSources.java b/Source/eu.modelwriter.core.alloyinecore.ui/src/eu/modelwriter/core/alloyinecore/ui/model/AnnotationSources.java new file mode 100644 index 00000000..7fb4e9d5 --- /dev/null +++ b/Source/eu.modelwriter.core.alloyinecore.ui/src/eu/modelwriter/core/alloyinecore/ui/model/AnnotationSources.java @@ -0,0 +1,53 @@ +package eu.modelwriter.core.alloyinecore.ui.model; + +import java.util.List; +import java.util.stream.Collectors; + +import org.eclipse.emf.ecore.EAnnotation; +import org.eclipse.emf.ecore.EDataType; +import org.eclipse.emf.ecore.EModelElement; + +public interface AnnotationSources { + + public final static String BASE = "http://www.modelwriter.eu/AlloyInEcore/"; + public final static String IMPORT = BASE + "Import"; + + public final static String VISIBILTY = BASE + "Visibility"; + public final static String STATIC = BASE + "Static"; + public final static String QUALIFIER = BASE + "Qualifier"; + public final static String INVARIANT = BASE + "Invariant"; + + public final static String ATTR_EXPRESSIONS = BASE + "Attribute/Expressions"; + public final static String REF_EXPRESSIONS = BASE + "Reference/Expressions"; + + public final static String PRECONDITION = BASE + "Operation/Precondition"; + public final static String POSTCONDITION = BASE + "Operation/Postcondition"; + public final static String BODY = BASE + "Operation/Body"; + + public final static String DATATYPE_PRIMITIVE = BASE + "DataType/Primitive"; + + // UNUSED + public final static String REFERENCE = BASE + "Reference"; + public final static String ATTRIBUTE = BASE + "Attribute"; + public final static String OPERATION = BASE + "Operation"; + + public static boolean isStatic(EModelElement element) { + return element.getEAnnotation(STATIC) != null; + } + + public static boolean isPrimitive(EDataType dataType) { + return dataType.getEAnnotation(DATATYPE_PRIMITIVE) != null; + } + + public static List getInvariants(EModelElement element) { + return element.getEAnnotations().stream().filter(anno -> INVARIANT.equals(anno.getSource())) + .collect(Collectors.toList()); + } + + public static List getAnnotations(EModelElement element) { + return element.getEAnnotations().stream() + .filter(anno -> anno.getSource() == null || !anno.getSource().startsWith(BASE)) + .collect(Collectors.toList()); + } + +} diff --git a/Source/eu.modelwriter.core.alloyinecore.ui/src/eu/modelwriter/core/alloyinecore/ui/model/EcoreTranslator.java b/Source/eu.modelwriter.core.alloyinecore.ui/src/eu/modelwriter/core/alloyinecore/ui/model/EcoreTranslator.java new file mode 100644 index 00000000..2e33f81c --- /dev/null +++ b/Source/eu.modelwriter.core.alloyinecore.ui/src/eu/modelwriter/core/alloyinecore/ui/model/EcoreTranslator.java @@ -0,0 +1,373 @@ +package eu.modelwriter.core.alloyinecore.ui.model; + +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.emf.common.util.EMap; +import org.eclipse.emf.common.util.URI; +import org.eclipse.emf.ecore.EAnnotation; +import org.eclipse.emf.ecore.EAttribute; +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EClassifier; +import org.eclipse.emf.ecore.EDataType; +import org.eclipse.emf.ecore.EEnum; +import org.eclipse.emf.ecore.EEnumLiteral; +import org.eclipse.emf.ecore.EModelElement; +import org.eclipse.emf.ecore.ENamedElement; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EOperation; +import org.eclipse.emf.ecore.EPackage; +import org.eclipse.emf.ecore.EReference; +import org.eclipse.emf.ecore.EStructuralFeature; +import org.eclipse.emf.ecore.ETypedElement; +import org.eclipse.emf.ecore.util.EcoreUtil; +import org.stringtemplate.v4.ST; +import org.stringtemplate.v4.STGroup; +import org.stringtemplate.v4.STGroupFile; + +import eu.modelwriter.core.alloyinecore.ui.cs2as.Qualification; + +public class EcoreTranslator implements AnnotationSources { + + private static final Map imports = new HashMap<>(); + + // FIXME fix stringtemplate path + // platform:/plugin/eu.modelwriter.core.alloyinecore.ui/stringtemplate/AlloyInEcore.stg + private static STGroup TEMPLATE_GROUP = new STGroupFile("stringtemplate/AlloyInEcore.stg"); + + public static String translate(EPackage ePackage) { + imports.clear(); + String text = packageToString(ePackage); + return getImports() + text; + } + + private static void addAnnotations(ST template, EModelElement element) { + AnnotationSources.getAnnotations(element).forEach(anno -> { + template.add("subElement", annoToString(anno).trim()); + }); + } + + private static Object invariantToString(EAnnotation invAnno) { + ST template = TEMPLATE_GROUP.getInstanceOf("inv"); + EMap details = invAnno.getDetails(); + template.add("isCallable", Boolean.parseBoolean(Qualification.CALLABLE.toString())); + template.add("name", details.get(Qualification.NAME.toString())); + template.add("message", details.get(Qualification.MESSAGE.toString())); + template.add("formula", details.get(Qualification.FORMULA.toString())); + return template.render().trim(); + } + + private static String packageToString(EPackage ePackage) { + ST template = TEMPLATE_GROUP.getInstanceOf("package"); + template.add("visibility", getVisibility(ePackage)); + template.add("name", ePackage.getName()); + template.add("prefix", ePackage.getNsPrefix()); + template.add("namespace", ePackage.getNsURI()); + for (EClassifier eClassifier : ePackage.getEClassifiers()) { + template.add("subElement", classifierToString(eClassifier)); + } + for (EPackage subPackage : ePackage.getESubpackages()) { + template.add("subElement", packageToString(subPackage)); + } + AnnotationSources.getInvariants(ePackage).forEach(invAnno -> { + template.add("subElement", invariantToString(invAnno)); + }); + addAnnotations(template, ePackage); + return template.render().trim(); + } + + private static String classifierToString(EClassifier eClassifier) { + if (eClassifier instanceof EEnum) + return enumToString((EEnum) eClassifier); + if (eClassifier instanceof EClass) + return classToString((EClass) eClassifier); + if (eClassifier instanceof EDataType) + return datatypeToString((EDataType) eClassifier); + return ""; + } + + private static String datatypeToString(EDataType eDataType) { + ST template = TEMPLATE_GROUP.getInstanceOf("datatype"); + template.add("isPrimitive", AnnotationSources.isPrimitive(eDataType)); + template.add("name", eDataType.getName()); + template.add("instanceName", eDataType.getInstanceClassName()); + if (eDataType.isSerializable()) + template.add("isSerializable", "serializable"); + AnnotationSources.getInvariants(eDataType).forEach(invAnno -> { + template.add("subElement", invariantToString(invAnno)); + }); + addAnnotations(template, eDataType); + return template.render().trim(); + } + + private static String enumToString(EEnum eEnum) { + ST template = TEMPLATE_GROUP.getInstanceOf("enum"); + template.add("visibility", getVisibility(eEnum)); + template.add("name", eEnum.getName()); + template.add("instanceName", eEnum.getInstanceClassName()); + if (eEnum.isSerializable()) + template.add("isSerializable", "serializable"); + AnnotationSources.getInvariants(eEnum).forEach(invAnno -> { + template.add("subElement", invariantToString(invAnno)); + }); + for (EEnumLiteral eEnumLiteral : eEnum.getELiterals()) { + template.add("subElement", enumLiteralToString(eEnumLiteral)); + } + addAnnotations(template, eEnum); + return template.render().trim(); + } + + private static String enumLiteralToString(EEnumLiteral literal) { + ST template = TEMPLATE_GROUP.getInstanceOf("enumLiteral"); + template.add("name", literal.getName()); + template.add("enumValue", literal.getValue()); + addAnnotations(template, literal); + return template.render().trim(); + } + + private static String classToString(EClass eClass) { + ST template = TEMPLATE_GROUP.getInstanceOf("class"); + template.add("visibility", getVisibility(eClass)); + template.add("isAbstract", eClass.isAbstract()); + template.add("name", eClass.getName()); + template.add("instanceName", eClass.getInstanceClassName()); + for (EClass superClass : eClass.getESuperTypes()) { + template.add("superClass", getName(superClass)); + } + template.add("isInterface", eClass.isInterface()); + + eClass.getEAttributes().forEach(attr -> { + template.add("subElement", attrToString(attr)); + }); + eClass.getEOperations().forEach(op -> { + template.add("subElement", operationToString(op)); + }); + eClass.getEReferences().forEach(eRef -> { + template.add("subElement", referenceToString(eRef)); + }); + AnnotationSources.getInvariants(eClass).forEach(invAnno -> { + template.add("subElement", invariantToString(invAnno)); + }); + addAnnotations(template, eClass); + return template.render().trim(); + } + + private static String operationToString(EOperation op) { + ST template = TEMPLATE_GROUP.getInstanceOf("op"); + template.add("visibility", getVisibility(op)); + template.add("isStatic", AnnotationSources.isStatic(op)); + template.add("name", op.getName()); + template.add("type", getName(op.getEType())); + template.add("multiplicity", getMultiplicity(op)); + template.add("qualifier", getQualifiers(op)); + op.getEExceptions().forEach(e -> { + template.add("throws", e.getName()); + }); + op.getEParameters().forEach(param -> { + template.add("params", param.getName()); + }); + if (op.getEAnnotation(PRECONDITION) != null) + template.add("subElement", preconditionToString(op.getEAnnotation(PRECONDITION))); + if (op.getEAnnotation(BODY) != null) + template.add("subElement", bodyToString(op.getEAnnotation(BODY))); + if (op.getEAnnotation(POSTCONDITION) != null) + template.add("subElement", postconditionToString(op.getEAnnotation(POSTCONDITION))); + addAnnotations(template, op); + return template.render().trim(); + } + + private static Object postconditionToString(EAnnotation eAnnotation) { + ST template = TEMPLATE_GROUP.getInstanceOf("postcondition"); + EMap details = eAnnotation.getDetails(); + template.add("name", details.get(Qualification.NAME.toString())); + template.add("message", details.get(Qualification.MESSAGE.toString())); + template.add("formula", details.get(Qualification.FORMULA.toString())); + return template.render().trim(); + } + + private static Object bodyToString(EAnnotation eAnnotation) { + ST template = TEMPLATE_GROUP.getInstanceOf("body"); + EMap details = eAnnotation.getDetails(); + template.add("name", details.get(Qualification.NAME.toString())); + template.add("formula", details.get(Qualification.EXPRESSION.toString())); + return template.render().replace(" ", ""); + } + + private static String preconditionToString(EAnnotation invAnno) { + ST template = TEMPLATE_GROUP.getInstanceOf("precondition"); + EMap details = invAnno.getDetails(); + template.add("name", details.get(Qualification.NAME.toString())); + template.add("message", details.get(Qualification.MESSAGE.toString())); + template.add("formula", details.get(Qualification.FORMULA.toString())); + return template.render().trim(); + } + + private static String referenceToString(EReference eRef) { + ST template = TEMPLATE_GROUP.getInstanceOf("ref"); + template.add("visibility", getVisibility(eRef)); + template.add("isStatic", AnnotationSources.isStatic(eRef)); + template.add("name", eRef.getName()); + if (eRef.getEOpposite() != null) + template.add("opposite", getName(eRef.getEOpposite())); + template.add("defaultValue", eRef.getDefaultValue()); + template.add("type", getName(eRef.getEType())); + template.add("multiplicity", getMultiplicity(eRef)); + template.add("qualifier", getQualifiers(eRef)); + addAnnotations(template, eRef); + return template.render().trim(); + } + + private static String attrToString(EAttribute eAttr) { + ST template = TEMPLATE_GROUP.getInstanceOf("ref"); + template.add("visibility", getVisibility(eAttr)); + template.add("isStatic", AnnotationSources.isStatic(eAttr)); + template.add("name", eAttr.getName()); + template.add("defaultValue", eAttr.getDefaultValue()); + template.add("type", getName(eAttr.getEType())); + template.add("multiplicity", getMultiplicity(eAttr)); + template.add("qualifier", getQualifiers(eAttr)); + addAnnotations(template, eAttr); + return template.render().trim(); + } + + private static String annoToString(EAnnotation eAnnotation) { + ST template = TEMPLATE_GROUP.getInstanceOf("anno"); + template.add("name", eAnnotation.getSource()); + eAnnotation.getDetails().forEach(entry -> { + template.add("detail", edetailToString(entry.getKey(), entry.getValue())); + }); + eAnnotation.getReferences().forEach(ref -> { + template.add("subElement", "reference " + getName(ref) + ";"); + }); + eAnnotation.getContents().forEach(eObject -> { + String subElement = ""; + if (eObject instanceof EPackage) + subElement = packageToString((EPackage) eObject); + if (eObject instanceof EClass) + subElement = classToString((EClass) eObject); + if (eObject instanceof EEnum) + subElement = enumToString((EEnum) eObject); + if (eObject instanceof EDataType) + subElement = datatypeToString((EDataType) eObject); + if (eObject instanceof EOperation) + subElement = operationToString((EOperation) eObject); + if (eObject instanceof EAttribute) + subElement = attrToString((EAttribute) eObject); + if (eObject instanceof EReference) + subElement = referenceToString((EReference) eObject); + template.add("subElement", subElement); + }); + addAnnotations(template, eAnnotation); + return template.render().trim(); + } + + private static String edetailToString(String name, String value) { + ST template = TEMPLATE_GROUP.getInstanceOf("edetail"); + template.add("name", name); + template.add("value", value); + return template.render(); + } + + private static String getImports() { + StringBuilder importsBuilder = new StringBuilder(); + imports.entrySet().forEach(entry -> { + ST template = TEMPLATE_GROUP.getInstanceOf("load"); + template.add("name", entry.getKey()); + template.add("namespace", entry.getValue()); + importsBuilder.append(template.render().trim()); + importsBuilder.append("\n"); + }); + importsBuilder.append("\n"); + return importsBuilder.toString(); + } + + private static String getName(EObject eObject) { + String name = "null"; + if (eObject instanceof ENamedElement) { + ENamedElement nElement = (ENamedElement) eObject; + if (nElement.getName() == null) // its from another ecore + { + URI uri = EcoreUtil.getURI(eObject); + imports.put(uri.trimFileExtension().lastSegment(), uri.toFileString()); + name = uri.toString().replaceAll("#//", "::").replace(".ecore", ""); + } else { + name = nElement.getName(); + } + } + return name; + } + + private static String getMultiplicity(ETypedElement eTypedElement) { + int l = eTypedElement.getLowerBound(); + int u = eTypedElement.getUpperBound(); + if (l == 0 && u == 1) + return "?"; + if (l == 0 && u == -1) + return "*"; + if (l == 1 && u == 1) + return "1"; + if (l == 1 && u == -1) + return "+"; + + return l + ".." + u; + } + + private static String getVisibility(ENamedElement element) { + String visibility = ""; + EAnnotation visibilityAnno = element.getEAnnotation(VISIBILTY); + if (visibilityAnno != null && !visibilityAnno.getDetails().isEmpty()) + visibility = visibilityAnno.getDetails().get(Qualification.VISIBILITY.toString()); + return visibility; + } + + private static String getQualifiers(ETypedElement element) { + StringBuilder builder = new StringBuilder(); + builder.append(" { "); + if (!element.isUnique()) + builder.append("!unique "); + if (element.isOrdered()) + builder.append("ordered "); + + // if there is no qualifier, delete curly bracket + if (builder.charAt(builder.length() - 2) == '{') + builder.delete(builder.length() - 3, builder.length()); + else + builder.append("}"); + // TODO check other qualifiers, maybe? + return builder.toString(); + } + + private static String getQualifiers(EStructuralFeature eStructuralFeature) { + StringBuilder builder = new StringBuilder(); + builder.append(" { "); + if (!eStructuralFeature.isUnique()) + builder.append("!unique "); + if (eStructuralFeature.isDerived()) + builder.append("derived "); + if (eStructuralFeature.isVolatile()) + builder.append("volatile "); + if (eStructuralFeature.isUnsettable()) + builder.append("unsettable "); + if (eStructuralFeature.isTransient()) + builder.append("transient "); + if (eStructuralFeature.isOrdered()) + builder.append("ordered "); + if (!eStructuralFeature.isChangeable()) + builder.append("readonly "); + + if (eStructuralFeature instanceof EAttribute && ((EAttribute) eStructuralFeature).isID()) + builder.append("id "); + if (eStructuralFeature instanceof EReference + && ((EReference) eStructuralFeature).isContainment()) + builder.append("composes "); + + // if there is no qualifier, delete curly bracket + if (builder.charAt(builder.length() - 2) == '{') + builder.delete(builder.length() - 3, builder.length()); + else + builder.append("}"); + // TODO check other qualifiers, maybe? + return builder.toString(); + } + +} diff --git a/Source/eu.modelwriter.core.alloyinecore.ui/src/eu/modelwriter/core/alloyinecore/ui/test/EcoreTranslatorTest.java b/Source/eu.modelwriter.core.alloyinecore.ui/src/eu/modelwriter/core/alloyinecore/ui/test/EcoreTranslatorTest.java new file mode 100644 index 00000000..278284c7 --- /dev/null +++ b/Source/eu.modelwriter.core.alloyinecore.ui/src/eu/modelwriter/core/alloyinecore/ui/test/EcoreTranslatorTest.java @@ -0,0 +1,93 @@ +package eu.modelwriter.core.alloyinecore.ui.test; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.fail; + +import java.io.IOException; + +import org.eclipse.emf.common.util.URI; +import org.eclipse.emf.ecore.EPackage; +import org.junit.Before; +import org.junit.Test; +import org.stringtemplate.v4.ST; +import org.stringtemplate.v4.STGroup; +import org.stringtemplate.v4.STGroupFile; + +import eu.modelwriter.configuration.internal.EcoreUtilities; +import eu.modelwriter.core.alloyinecore.ui.model.EcoreTranslator; + +public class EcoreTranslatorTest { + + private EPackage testRoot = null; + private STGroup group; + private EPackage testRoot2; + + @Before + public void before() { + try { + group = new STGroupFile("stringtemplate/AlloyInEcore.stg"); + URI fileURI2 = URI.createFileURI("mappingTest/tutorialExtended.ecore"); + URI fileURI = URI.createFileURI("src/eu/modelwriter/core/alloyinecore/ui/test/Test.ecore"); + testRoot2 = (EPackage) EcoreUtilities.getRootObject(fileURI2); + testRoot = (EPackage) EcoreUtilities.getRootObject(fileURI); + assertNotNull(testRoot); + } catch (IOException e) { + e.printStackTrace(); + fail(); + } + } + + @Test + public void translatorTest2() { + long millis = System.currentTimeMillis(); + String string = EcoreTranslator.translate(testRoot2); + System.out.println("2-Time ellapsed: " + (System.currentTimeMillis() - millis)); + System.out.println(string); + } + + @Test + public void translatorTest() { + long millis = System.currentTimeMillis(); + String string = EcoreTranslator.translate(testRoot); + System.out.println("Time ellapsed: " + (System.currentTimeMillis() - millis)); + System.out.println(string); + } + + @Test + public void templateClassTest() { + String result = classTemplate(); + System.out.println("---- templateClassTest"); + System.out.println(result); + } + + private String classTemplate() { + ST st = group.getInstanceOf("class"); + st.add("visibility", "public"); + st.add("name", "Library"); + st.add("isAbstract", "abstract"); + st.add("instanceName", "Lib"); + st.add("superClass", "SuperLib"); + st.add("superClass", "SuperLib2"); + st.add("isInterface", false); + String result = st.render().replaceAll(" ", " "); + return result; + } + + @Test + public void templatePackageTest() { + String result = packageTemplate(); + System.out.println("---- templatePackageTest"); + System.out.println(result); + } + + private String packageTemplate() { + ST st = group.getInstanceOf("package"); + st.add("visibility", "public"); + st.add("name", "Package1"); + st.add("prefix", "p1"); + st.add("namespace", "modelwriter"); + st.add("subElement", classTemplate()); + String result = st.render().replaceAll(" ", " "); + return result; + } +} diff --git a/Source/eu.modelwriter.core.alloyinecore.ui/src/eu/modelwriter/core/alloyinecore/ui/test/Test.ecore b/Source/eu.modelwriter.core.alloyinecore.ui/src/eu/modelwriter/core/alloyinecore/ui/test/Test.ecore new file mode 100644 index 00000000..376769d2 --- /dev/null +++ b/Source/eu.modelwriter.core.alloyinecore.ui/src/eu/modelwriter/core/alloyinecore/ui/test/Test.ecore @@ -0,0 +1,107 @@ + + + +
+
+
+ + + +
+ + + + + + + + + + + + + + + +
+
+
+
+ + + +
+
+
+ + +
+
+ + + + + + + +
+ + + + + +
+
+ + + + + +
+ + + + +
+ + + + + + + + + + + +
+ + + + + + + + + + + + + diff --git a/Source/eu.modelwriter.core.alloyinecore.ui/src/eu/modelwriter/core/alloyinecore/ui/test/extlibrary.ecore b/Source/eu.modelwriter.core.alloyinecore.ui/src/eu/modelwriter/core/alloyinecore/ui/test/extlibrary.ecore new file mode 100644 index 00000000..4d637438 --- /dev/null +++ b/Source/eu.modelwriter.core.alloyinecore.ui/src/eu/modelwriter/core/alloyinecore/ui/test/extlibrary.ecore @@ -0,0 +1,107 @@ + + + + + + + + + + + + +
+ + + + +
+ + + + +
+ + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Source/eu.modelwriter.core.alloyinecore.ui/stringtemplate/AlloyInEcore.stg b/Source/eu.modelwriter.core.alloyinecore.ui/stringtemplate/AlloyInEcore.stg new file mode 100644 index 00000000..82c3a5f3 --- /dev/null +++ b/Source/eu.modelwriter.core.alloyinecore.ui/stringtemplate/AlloyInEcore.stg @@ -0,0 +1,108 @@ +load(name, namespace) ::= +<< +import : ; +>> + +package(visibility, name, prefix, namespace, subElement) ::= +<< + package : = <\n><\u007B>; + +<\u007D><\n> +>> + +enum(visibility, name, instanceName, isSerializable, subElement)::= +<< + enum <\n><\u007B>; + +<\u007D><\n> +>> + +enumLiteral(name, enumValue, subElement) ::= +<< +literal = <\n><\u007B>; + +<\u007D><\n> +>> + +datatype(isPrimitive, name, instanceName, isSerializable, subElement)::= +<< +primitive datatype <(instanceName:singleQuote()):semicolon()> <\n><\u007B>; + +<\u007D><\n> +>> + +class(visibility, isAbstract, name, superClass, instanceName, isInterface, subElement) ::= +<< + abstract class extends <\n><\u007B>; + +<\u007D><\n> +>> + +attr(visibility, isStatic, name, defaultValue, type, multiplicity, qualifier, subElement)::= +<< + static attribute <\n><\u007B>; + +<\u007D><\n> +>> + +ref(visibility, isStatic, name, opposite, defaultValue, type, multiplicity, qualifier, subElement)::= +<< + static property <\n><\u007B>; + +<\u007D><\n> +>> + +op(visibility, isStatic, name, params, throws, type, multiplicity, qualifier, subElement)::= +<< + static operation () <\n><\u007B>; + +<\u007D><\n> +>> + +anno(name, detail, subElement)::= +<< +annotation ; +( + +); +<\n><\u007B> + +<\u007D><\n> +>> + +edetail(name, value)::= +<< + = +>> + +inv(isCallable, name, message, formula)::= +<< +callable invariant ; +>> + +precondition(name, message, formula)::= +<< +precondition ; +>> + +postcondition(name, message, formula)::= +<< +postcondition ; +>> + +body(name, formula)::= +<< +body ; +>> + +throw(x)::=" throws " +parenthesis(x) ::= "()" +sharp(x) ::= "#" +equals(x) ::= "= " +bracket(x) ::= "[]" +semicolon(x) ::=": " +curlyBracket(x) ::=" <\u007B> <\u007D> " +indentCurlyBracket(x) ::= "<\u007B><\n><\t><\n><\u007D>" +singleQuote(x)::="''" +doubleQuote(x)::="\"\"" + diff --git a/Source/eu.modelwriter.core.alloyinecore/.classpath b/Source/eu.modelwriter.core.alloyinecore/.classpath new file mode 100644 index 00000000..6a116c22 --- /dev/null +++ b/Source/eu.modelwriter.core.alloyinecore/.classpath @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Source/eu.modelwriter.core.alloyinecore/.gitignore b/Source/eu.modelwriter.core.alloyinecore/.gitignore new file mode 100644 index 00000000..c261ba13 --- /dev/null +++ b/Source/eu.modelwriter.core.alloyinecore/.gitignore @@ -0,0 +1,4 @@ +/bin/ +/.idea/ +/target +*.iml \ No newline at end of file diff --git a/Source/eu.modelwriter.core.alloyinecore/.project b/Source/eu.modelwriter.core.alloyinecore/.project new file mode 100644 index 00000000..77b30573 --- /dev/null +++ b/Source/eu.modelwriter.core.alloyinecore/.project @@ -0,0 +1,34 @@ + + + eu.modelwriter.core.alloyinecore + + + + + + org.eclipse.xtext.ui.shared.xtextBuilder + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.pde.PluginNature + org.eclipse.xtext.ui.shared.xtextNature + + diff --git a/Source/eu.modelwriter.core.alloyinecore/.settings/org.eclipse.jdt.core.prefs b/Source/eu.modelwriter.core.alloyinecore/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 00000000..3a215370 --- /dev/null +++ b/Source/eu.modelwriter.core.alloyinecore/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,11 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=1.8 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.8 diff --git a/Source/eu.modelwriter.core.alloyinecore/META-INF/MANIFEST.MF b/Source/eu.modelwriter.core.alloyinecore/META-INF/MANIFEST.MF new file mode 100644 index 00000000..e21d6a1a --- /dev/null +++ b/Source/eu.modelwriter.core.alloyinecore/META-INF/MANIFEST.MF @@ -0,0 +1,82 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: Alloyinecore +Bundle-SymbolicName: eu.modelwriter.core.alloyinecore;singleton:=true +Bundle-Version: 1.0.0.qualifier +Bundle-ClassPath: alloyinecore.jar, + lib/antlr-4.5.1-complete.jar, + lib/org.eclipse.emf_2.6.0.v20150806-0404.jar, + lib/org.eclipse.emf.ecore_2.11.1.v20150805-0538.jar, + lib/org.eclipse.emf.ecore.xmi_2.11.1.v20150805-0538.jar, + lib/org.eclipse.emf.common_2.11.0.v20150805-0538.jar +Export-Package: eu.modelwriter.core.alloyinecore.example, + eu.modelwriter.core.alloyinecore.recognizer, + model, + org.abego.treelayout, + org.abego.treelayout.internal.util, + org.abego.treelayout.internal.util.java.lang, + org.abego.treelayout.internal.util.java.lang.string, + org.abego.treelayout.internal.util.java.util, + org.abego.treelayout.util, + org.antlr.runtime, + org.antlr.runtime.debug, + org.antlr.runtime.misc, + org.antlr.runtime.tree, + org.antlr.v4, + org.antlr.v4.analysis, + org.antlr.v4.automata, + org.antlr.v4.codegen, + org.antlr.v4.codegen.model, + org.antlr.v4.codegen.model.chunk, + org.antlr.v4.codegen.model.decl, + org.antlr.v4.codegen.target, + org.antlr.v4.gui, + org.antlr.v4.misc, + org.antlr.v4.parse, + org.antlr.v4.runtime, + org.antlr.v4.runtime.atn, + org.antlr.v4.runtime.dfa, + org.antlr.v4.runtime.misc, + org.antlr.v4.runtime.tree, + org.antlr.v4.runtime.tree.pattern, + org.antlr.v4.runtime.tree.xpath, + org.antlr.v4.semantics, + org.antlr.v4.tool, + org.antlr.v4.tool.ast, + org.antlr.v4.tool.templates, + org.antlr.v4.tool.templates.codegen.CSharp, + org.antlr.v4.tool.templates.codegen.Java, + org.antlr.v4.tool.templates.codegen.JavaScript, + org.antlr.v4.tool.templates.codegen.Python2, + org.antlr.v4.tool.templates.codegen.Python3, + org.antlr.v4.tool.templates.dot, + org.antlr.v4.tool.templates.messages.formats, + org.eclipse.emf.common, + org.eclipse.emf.common.archive, + org.eclipse.emf.common.command, + org.eclipse.emf.common.notify, + org.eclipse.emf.common.notify.impl, + org.eclipse.emf.common.util, + org.eclipse.emf.ecore, + org.eclipse.emf.ecore.impl, + org.eclipse.emf.ecore.plugin, + org.eclipse.emf.ecore.resource, + org.eclipse.emf.ecore.resource.impl, + org.eclipse.emf.ecore.util, + org.eclipse.emf.ecore.xmi, + org.eclipse.emf.ecore.xmi.impl, + org.eclipse.emf.ecore.xmi.util, + org.eclipse.emf.ecore.xml.namespace, + org.eclipse.emf.ecore.xml.namespace.impl, + org.eclipse.emf.ecore.xml.namespace.util, + org.eclipse.emf.ecore.xml.type, + org.eclipse.emf.ecore.xml.type.impl, + org.eclipse.emf.ecore.xml.type.internal, + org.eclipse.emf.ecore.xml.type.util, + org.stringtemplate.v4, + org.stringtemplate.v4.compiler, + org.stringtemplate.v4.debug, + org.stringtemplate.v4.gui, + org.stringtemplate.v4.misc, + programs, + schema diff --git a/Source/eu.modelwriter.core.alloyinecore/bookStore.ecore b/Source/eu.modelwriter.core.alloyinecore/bookStore.ecore new file mode 100644 index 00000000..4d5119a8 --- /dev/null +++ b/Source/eu.modelwriter.core.alloyinecore/bookStore.ecore @@ -0,0 +1,18 @@ + + + +
+ + + + + + + + + + + diff --git a/Source/eu.modelwriter.core.alloyinecore/bookStore.xml b/Source/eu.modelwriter.core.alloyinecore/bookStore.xml new file mode 100644 index 00000000..912d1077 --- /dev/null +++ b/Source/eu.modelwriter.core.alloyinecore/bookStore.xml @@ -0,0 +1,4 @@ + + + + diff --git a/Source/eu.modelwriter.core.alloyinecore/build.properties b/Source/eu.modelwriter.core.alloyinecore/build.properties new file mode 100644 index 00000000..17df9586 --- /dev/null +++ b/Source/eu.modelwriter.core.alloyinecore/build.properties @@ -0,0 +1,9 @@ +source.alloyinecore.jar = src/ +bin.includes = META-INF/,\ + lib/antlr-4.5.1-complete.jar,\ + lib/org.eclipse.emf_2.6.0.v20150806-0404.jar,\ + lib/org.eclipse.emf.ecore_2.11.1.v20150805-0538.jar,\ + lib/org.eclipse.emf.ecore.xmi_2.11.1.v20150805-0538.jar,\ + lib/org.eclipse.emf.common_2.11.0.v20150805-0538.jar,\ + alloyinecore.jar,\ + plugin.xml diff --git a/Source/eu.modelwriter.core.alloyinecore/docs/Eclipse OCL Documentation.pdf b/Source/eu.modelwriter.core.alloyinecore/docs/Eclipse OCL Documentation.pdf new file mode 100644 index 00000000..ff9dc221 Binary files /dev/null and b/Source/eu.modelwriter.core.alloyinecore/docs/Eclipse OCL Documentation.pdf differ diff --git a/Source/eu.modelwriter.core.alloyinecore/docs/Ecore-Simplified.als b/Source/eu.modelwriter.core.alloyinecore/docs/Ecore-Simplified.als new file mode 100644 index 00000000..f296fd2d --- /dev/null +++ b/Source/eu.modelwriter.core.alloyinecore/docs/Ecore-Simplified.als @@ -0,0 +1,68 @@ +module eu_modelwriter_transformation_models/ECoreSimplified + +/* Source: ECore Simplified Metamodel*/ + +one sig ECoreModel extends EModelElement { classes: set EClass } + +abstract sig EModelElement {} + +sig string extends EModelElement {} +abstract sig boolean extends EModelElement {} +sig integer extends EModelElement {} +one sig false,true extends boolean {} + +abstract sig EDataType extends EModelElement {} +one sig EString, EBoolean, EInt extends EDataType {} + +abstract sig ENamedElement extends EModelElement {name: one string } + +abstract sig EClass extends ENamedElement { + attrs: set EAttribute, + refs: set EReference, + cbelongs: one ECoreModel, + parents: set EClass, + isAbstract: one boolean +} + +abstract sig EAttribute extends ENamedElement { + abelongs: one EClass, + type: one EDataType +} + +abstract sig EReference extends ENamedElement { + rbelongs: one EClass, + type: one EClass +} + +/* Well-formedness rules of ECore Metamodel*/ + +fact { + + all m: ECoreModel, c: m.classes | c.cbelongs = m + classes = ~cbelongs + + all c: EClass, a: c.attrs | a.abelongs = c + attrs = ~abelongs + + all c: EClass, r: c.refs | r.rbelongs = c + refs = ~rbelongs + + all c1: EClass, c2: EClass | c1.name = c2.name => c1 = c2 + all e1: EAttribute, e2: EAttribute | e1.name = e2.name => e1 = e2 + all r1: EReference, r2: EReference | r1.name = r2.name => r1 = r2 + + all a: EAttribute, c: EClass, r: EReference | (c -> a in attrs && c -> r in refs) => a.name != r.name + + --no self parent class + no p: EClass | p in p.parents + --no parent one another + no c1: EClass, c2: EClass | c1.parents = c2 and c2.parents = c1 +} + +/* test */ +pred example {#EClass > 2 and #EAttribute > 2 and #EReference >2 and #parents >2} +run example for exactly 18 EModelElement +//run example for exactly 15 ECoreElement + + + diff --git a/Source/eu.modelwriter.core.alloyinecore/docs/Ecore.als b/Source/eu.modelwriter.core.alloyinecore/docs/Ecore.als new file mode 100644 index 00000000..98dadf2a --- /dev/null +++ b/Source/eu.modelwriter.core.alloyinecore/docs/Ecore.als @@ -0,0 +1,102 @@ +module eu_modelwriter_transformation_models/Ecore + +open eu_modelwriter_transformation/Type +open eu_modelwriter_transformation/Helper + +/* Source: ECore Metamodel*/ +abstract sig EModelElement extends Element {} + +abstract sig EDataType extends EModelElement {} +lone sig EString, EBoolean, EInt extends EDataType {} + +abstract sig ENamedElement extends EModelElement {name: one string } + +one sig ECoreModel extends EModelElement { ePackages: some EPackage} + +abstract sig EPackage extends ENamedElement { + eClassifiers: set EClassifier, + eSubPackages: set EPackage, +-- eSuperPackage: lone EPackage, +} +--{ all p: EPackage | lone p.~eSubPackages} + +abstract sig EClassifier extends ENamedElement { ePackage: one EPackage } + +abstract sig EClass extends EClassifier { + eAttributes: set EAttribute, + eReferences: set EReference, + eSuperTypes: set EClass, + isAbstract: one boolean +} + +abstract sig EAttribute extends ENamedElement { + eContainingClass: one EClass, + eType: one (EDataType + EEnum) +} + +abstract sig EReference extends ENamedElement { +-- eContainingClass: one EClass, + eReferenceType: one EClass, + containment: one boolean +} + +abstract sig EEnum extends EClassifier { + eLiterals: set EEnumLiteral, +} + +abstract sig EEnumLiteral extends ENamedElement{ + eEnum: one EEnum, +} + +/* Well-formedness rules of ECore Metamodel*/ + +fact containmentRelationshipsOfECoreElements{ + all m: ECoreModel, e1: EPackage, e2: EPackage | e1 in m.ePackages => e1 !in e2.eSubPackages +-- eSubPackages = ~eSuperPackage + -- + all e: EPackage, c: e.eClassifiers | c.ePackage = e + eClassifiers = ~ePackage + -- + all c: EClass, a: c.eAttributes | a.eContainingClass = c + eAttributes = ~eContainingClass + -- + all c: EClass, r: c.eReferences | r.eContainingClass= c + eReferences = ~eContainingClass + -- + all e: EEnum, l: e.eLiterals | l.eEnum = e + eLiterals = ~eEnum +} + +fact uniqueNameConstraintsOfECoreElements{ + --names of EClasses are unique + all c1: EClass, c2: EClass | c1.name = c2.name => c1 = c2 + --names of EAttributes are unique + all e1: EAttribute, e2: EAttribute | e1.name = e2.name => e1 = e2 + --names of EReferences are unique + all r1: EReference, r2: EReference | r1.name = r2.name => r1 = r2 + --names of EEnums are unique + all e1: EEnum, e2: EEnum | e1.name = e2.name => e1 = e2 + --name + all c: EClass, a: EAttribute, r: EReference | (c -> a in eAttributes && c -> r in eReferences) => a.name != r.name +} + +fact realismConstraint {some EClassifier} + +fact inheritance { acyclic [EClass, eSuperTypes] } +fact package { acyclic [EPackage, eSubPackages] } + +/* Counterexample Finding */ +assert noContainment {some r: EReference, f: false | r.containment in f} +check noContainment for 10 Element + +assert noSelfParent{one p: EClass | p in p.eSuperTypes } +check noSelfParent + +/* Instance Finding / Instance Completion*/ +pred example {#EPackage >1 and #EClass > 2 and #EAttribute > 2 and #EReference >2 and #eSuperTypes >2 + and #isAbstract > 2 and #EEnum = 2 and #EEnumLiteral > 1 and #eSubPackages >=2} +run example for exactly 25 Element +//run example for exactly 15 ECoreElement + + + diff --git a/Source/eu.modelwriter.core.alloyinecore/docs/Ecore.ecore b/Source/eu.modelwriter.core.alloyinecore/docs/Ecore.ecore new file mode 100644 index 00000000..15a3569f --- /dev/null +++ b/Source/eu.modelwriter.core.alloyinecore/docs/Ecore.ecore @@ -0,0 +1,526 @@ + + + + +
+ + + + + + +
+ + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + +
+
+ + + + + + +
+ + + + + + + +
+
+ + + + + + + + + + +
+
+ + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+
+ + + + +
+
+ + + + + +
+ + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + +
+ + + + + + + + + +
+
+ + + + +
+
+ + + + + +
+ + + + +
+ + + + +
+ + + + +
+
+ + + + +
+ + + + +
+ + + + +
+
+ + + + + +
+
+ + + + + + +
+ + + + +
+
+ + + + + + + + + + +
+ + + + +
+
+ + + + +
+ + + + +
+
+ + + + + + + + +
+ + + + +
+
+ + + + + + + + + + +
+ + + + +
+
+ + + + +
+ + + + + + + + + + + +
+ + + + + + + + + + + + + + + + diff --git a/Source/eu.modelwriter.core.alloyinecore/docs/EcoreRelations.gif b/Source/eu.modelwriter.core.alloyinecore/docs/EcoreRelations.gif new file mode 100644 index 00000000..a5bc2bb0 Binary files /dev/null and b/Source/eu.modelwriter.core.alloyinecore/docs/EcoreRelations.gif differ diff --git a/Source/eu.modelwriter.core.alloyinecore/docs/Java.ecore b/Source/eu.modelwriter.core.alloyinecore/docs/Java.ecore new file mode 100644 index 00000000..90f1fffd --- /dev/null +++ b/Source/eu.modelwriter.core.alloyinecore/docs/Java.ecore @@ -0,0 +1,131 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Source/eu.modelwriter.core.alloyinecore/docs/ReadMe.md b/Source/eu.modelwriter.core.alloyinecore/docs/ReadMe.md new file mode 100644 index 00000000..f87112c9 --- /dev/null +++ b/Source/eu.modelwriter.core.alloyinecore/docs/ReadMe.md @@ -0,0 +1,29 @@ + * Safe Navigations + * Declaring Null-free Collections in Ecore + * [http://help.eclipse.org/neon/index.jsp?topic=%2Forg.eclipse.ocl.doc%2Fhelp%2FSafeNavigationTutorial.html] + * Java Method Calls for string and integers + * KodKod TotalOrder Relation can be a remedy for ordered sets in ECore + + + * (Set{1, 4, 7, 10}->symmetricDifference(Set{4, 5, 7})) = Set{1, 5, 10} + * ((Set{1, 4, 7, 10}) - Set{4, 7}) = Set{1, 10} + + * ((OrderedSet{12, 9, 6, 3}) - Set{1, 3, 2}) = OrderedSet{12, 9, 6} + * (OrderedSet{'a', 'b', 'c', 'd'}->insertAt(3, 'X')) = OrderedSet{'a', 'b', 'X', 'c', 'd'} + * (OrderedSet{'a', 'b', 'c', 'd'}->subOrderedSet(2, 3)) = OrderedSet{'b', 'c'} + + * (Sequence{'a', 'b', 'c', 'c', 'd', 'e'}->prepend('X')) = Sequence{'X', 'a', 'b', 'c', 'c', 'd', 'e'} + * (Sequence{'a', 'b', 'c', 'c', 'd', 'e'}->append('X')) = Sequence{'a', 'b', 'c', 'c', 'd', 'e', 'X'} + * (Sequence{'a', 'b', 'c', 'c', 'd', 'e'}->subSequence(3, 5)) = Sequence{'c', 'c', 'd'} + * (Sequence{'a', 'b', 'c', 'c', 'd', 'e'}->indexOf('c')) = 3 + + * nullable quantifier for eReferences, inspired from this http://www.eecs.ucf.edu/~leavens/JML//refman/jmlrefman.pdf + * default is non-null + * modifiers such as ghost, model, instance + + For a field whose type is an array of reference types, such as a field of type Object[], + both the field that refers to the array and the elements of the array are non_null by default. + If a field whose type is an array of reference types is declared as nullable, then both the + reference to the array and all of its elements may potentially be null. To specify that the + field is not null but the elements may be null, use an invariant to state that the field cannot + contain null, as follows. [Page 18] \ No newline at end of file diff --git a/Source/eu.modelwriter.core.alloyinecore/docs/Tutorial.ecore b/Source/eu.modelwriter.core.alloyinecore/docs/Tutorial.ecore new file mode 100644 index 00000000..7f0972cd --- /dev/null +++ b/Source/eu.modelwriter.core.alloyinecore/docs/Tutorial.ecore @@ -0,0 +1,126 @@ + + + +
+
+ + +
+
+
+ + + + + + + + + + +
+
+ + + + + + + +
+ + +
+ + + + +
+
+ + + +
+ + +
+ + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + +
+ + +
+
+ + + + + +
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + diff --git a/Source/eu.modelwriter.core.alloyinecore/docs/Tutorial.xmi b/Source/eu.modelwriter.core.alloyinecore/docs/Tutorial.xmi new file mode 100644 index 00000000..b70e420d --- /dev/null +++ b/Source/eu.modelwriter.core.alloyinecore/docs/Tutorial.xmi @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/Source/eu.modelwriter.core.alloyinecore/docs/extlibrary.ecore b/Source/eu.modelwriter.core.alloyinecore/docs/extlibrary.ecore new file mode 100644 index 00000000..fdfb8f6a --- /dev/null +++ b/Source/eu.modelwriter.core.alloyinecore/docs/extlibrary.ecore @@ -0,0 +1,110 @@ + + + + + + + + + + + + +
+ + + + +
+ + + + +
+ + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Source/eu.modelwriter.core.alloyinecore/docs/tutorial.png b/Source/eu.modelwriter.core.alloyinecore/docs/tutorial.png new file mode 100644 index 00000000..7a609fe7 Binary files /dev/null and b/Source/eu.modelwriter.core.alloyinecore/docs/tutorial.png differ diff --git a/Source/eu.modelwriter.core.alloyinecore/lib/antlr-4.5.1-complete.jar b/Source/eu.modelwriter.core.alloyinecore/lib/antlr-4.5.1-complete.jar new file mode 100644 index 00000000..2e24ebe8 Binary files /dev/null and b/Source/eu.modelwriter.core.alloyinecore/lib/antlr-4.5.1-complete.jar differ diff --git a/Source/eu.modelwriter.core.alloyinecore/lib/antlr-src/org/antlr/v4/runtime/ANTLRErrorListener.java b/Source/eu.modelwriter.core.alloyinecore/lib/antlr-src/org/antlr/v4/runtime/ANTLRErrorListener.java new file mode 100644 index 00000000..83c7d869 --- /dev/null +++ b/Source/eu.modelwriter.core.alloyinecore/lib/antlr-src/org/antlr/v4/runtime/ANTLRErrorListener.java @@ -0,0 +1,205 @@ +/* + * [The "BSD license"] + * Copyright (c) 2012 Terence Parr + * Copyright (c) 2012 Sam Harwell + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.antlr.v4.runtime; + +import org.antlr.v4.runtime.atn.ATNConfigSet; +import org.antlr.v4.runtime.atn.DecisionInfo; +import org.antlr.v4.runtime.atn.ParserATNSimulator; +import org.antlr.v4.runtime.atn.PredictionMode; +import org.antlr.v4.runtime.dfa.DFA; + +import java.util.BitSet; + +/** How to emit recognition errors. */ +public interface ANTLRErrorListener { + /** + * Upon syntax error, notify any interested parties. This is not how to + * recover from errors or compute error messages. {@link ANTLRErrorStrategy} + * specifies how to recover from syntax errors and how to compute error + * messages. This listener's job is simply to emit a computed message, + * though it has enough information to create its own message in many cases. + * + *

The {@link RecognitionException} is non-null for all syntax errors except + * when we discover mismatched token errors that we can recover from + * in-line, without returning from the surrounding rule (via the single + * token insertion and deletion mechanism).

+ * + * @param recognizer + * What parser got the error. From this + * object, you can access the context as well + * as the input stream. + * @param offendingSymbol + * The offending token in the input token + * stream, unless recognizer is a lexer (then it's null). If + * no viable alternative error, {@code e} has token at which we + * started production for the decision. + * @param line + * The line number in the input where the error occurred. + * @param charPositionInLine + * The character position within that line where the error occurred. + * @param msg + * The message to emit. + * @param e + * The exception generated by the parser that led to + * the reporting of an error. It is null in the case where + * the parser was able to recover in line without exiting the + * surrounding rule. + */ + public void syntaxError(Recognizer recognizer, + Object offendingSymbol, + int line, + int charPositionInLine, + String msg, + RecognitionException e); + + /** + * This method is called by the parser when a full-context prediction + * results in an ambiguity. + * + *

Each full-context prediction which does not result in a syntax error + * will call either {@link #reportContextSensitivity} or + * {@link #reportAmbiguity}.

+ * + *

When {@code ambigAlts} is not null, it contains the set of potentially + * viable alternatives identified by the prediction algorithm. When + * {@code ambigAlts} is null, use {@link ATNConfigSet#getAlts} to obtain the + * represented alternatives from the {@code configs} argument.

+ * + *

When {@code exact} is {@code true}, all of the potentially + * viable alternatives are truly viable, i.e. this is reporting an exact + * ambiguity. When {@code exact} is {@code false}, at least two of + * the potentially viable alternatives are viable for the current input, but + * the prediction algorithm terminated as soon as it determined that at + * least the minimum potentially viable alternative is truly + * viable.

+ * + *

When the {@link PredictionMode#LL_EXACT_AMBIG_DETECTION} prediction + * mode is used, the parser is required to identify exact ambiguities so + * {@code exact} will always be {@code true}.

+ * + *

This method is not used by lexers.

+ * + * @param recognizer the parser instance + * @param dfa the DFA for the current decision + * @param startIndex the input index where the decision started + * @param stopIndex the input input where the ambiguity was identified + * @param exact {@code true} if the ambiguity is exactly known, otherwise + * {@code false}. This is always {@code true} when + * {@link PredictionMode#LL_EXACT_AMBIG_DETECTION} is used. + * @param ambigAlts the potentially ambiguous alternatives, or {@code null} + * to indicate that the potentially ambiguous alternatives are the complete + * set of represented alternatives in {@code configs} + * @param configs the ATN configuration set where the ambiguity was + * identified + */ + void reportAmbiguity(Parser recognizer, + DFA dfa, + int startIndex, + int stopIndex, + boolean exact, + BitSet ambigAlts, + ATNConfigSet configs); + + /** + * This method is called when an SLL conflict occurs and the parser is about + * to use the full context information to make an LL decision. + * + *

If one or more configurations in {@code configs} contains a semantic + * predicate, the predicates are evaluated before this method is called. The + * subset of alternatives which are still viable after predicates are + * evaluated is reported in {@code conflictingAlts}.

+ * + *

This method is not used by lexers.

+ * + * @param recognizer the parser instance + * @param dfa the DFA for the current decision + * @param startIndex the input index where the decision started + * @param stopIndex the input index where the SLL conflict occurred + * @param conflictingAlts The specific conflicting alternatives. If this is + * {@code null}, the conflicting alternatives are all alternatives + * represented in {@code configs}. At the moment, conflictingAlts is non-null + * (for the reference implementation, but Sam's optimized version can see this + * as null). + * @param configs the ATN configuration set where the SLL conflict was + * detected + */ + void reportAttemptingFullContext(Parser recognizer, + DFA dfa, + int startIndex, + int stopIndex, + BitSet conflictingAlts, + ATNConfigSet configs); + + /** + * This method is called by the parser when a full-context prediction has a + * unique result. + * + *

Each full-context prediction which does not result in a syntax error + * will call either {@link #reportContextSensitivity} or + * {@link #reportAmbiguity}.

+ * + *

For prediction implementations that only evaluate full-context + * predictions when an SLL conflict is found (including the default + * {@link ParserATNSimulator} implementation), this method reports cases + * where SLL conflicts were resolved to unique full-context predictions, + * i.e. the decision was context-sensitive. This report does not necessarily + * indicate a problem, and it may appear even in completely unambiguous + * grammars.

+ * + *

{@code configs} may have more than one represented alternative if the + * full-context prediction algorithm does not evaluate predicates before + * beginning the full-context prediction. In all cases, the final prediction + * is passed as the {@code prediction} argument.

+ * + *

Note that the definition of "context sensitivity" in this method + * differs from the concept in {@link DecisionInfo#contextSensitivities}. + * This method reports all instances where an SLL conflict occurred but LL + * parsing produced a unique result, whether or not that unique result + * matches the minimum alternative in the SLL conflicting set.

+ * + *

This method is not used by lexers.

+ * + * @param recognizer the parser instance + * @param dfa the DFA for the current decision + * @param startIndex the input index where the decision started + * @param stopIndex the input index where the context sensitivity was + * finally determined + * @param prediction the unambiguous result of the full-context prediction + * @param configs the ATN configuration set where the unambiguous prediction + * was determined + */ + void reportContextSensitivity(Parser recognizer, + DFA dfa, + int startIndex, + int stopIndex, + int prediction, + ATNConfigSet configs); +} diff --git a/Source/eu.modelwriter.core.alloyinecore/lib/antlr-src/org/antlr/v4/runtime/ANTLRErrorStrategy.java b/Source/eu.modelwriter.core.alloyinecore/lib/antlr-src/org/antlr/v4/runtime/ANTLRErrorStrategy.java new file mode 100644 index 00000000..f123778f --- /dev/null +++ b/Source/eu.modelwriter.core.alloyinecore/lib/antlr-src/org/antlr/v4/runtime/ANTLRErrorStrategy.java @@ -0,0 +1,141 @@ +/* + * [The "BSD license"] + * Copyright (c) 2012 Terence Parr + * Copyright (c) 2012 Sam Harwell + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.antlr.v4.runtime; + +/** + * The interface for defining strategies to deal with syntax errors encountered + * during a parse by ANTLR-generated parsers. We distinguish between three + * different kinds of errors: + * + *
    + *
  • The parser could not figure out which path to take in the ATN (none of + * the available alternatives could possibly match)
  • + *
  • The current input does not match what we were looking for
  • + *
  • A predicate evaluated to false
  • + *
+ * + * Implementations of this interface report syntax errors by calling + * {@link Parser#notifyErrorListeners}. + * + *

TODO: what to do about lexers

+ */ +public interface ANTLRErrorStrategy { + /** + * Reset the error handler state for the specified {@code recognizer}. + * @param recognizer the parser instance + */ + void reset(Parser recognizer); + + /** + * This method is called when an unexpected symbol is encountered during an + * inline match operation, such as {@link Parser#match}. If the error + * strategy successfully recovers from the match failure, this method + * returns the {@link Token} instance which should be treated as the + * successful result of the match. + * + *

This method handles the consumption of any tokens - the caller should + * not call {@link Parser#consume} after a successful recovery.

+ * + *

Note that the calling code will not report an error if this method + * returns successfully. The error strategy implementation is responsible + * for calling {@link Parser#notifyErrorListeners} as appropriate.

+ * + * @param recognizer the parser instance + * @throws RecognitionException if the error strategy was not able to + * recover from the unexpected input symbol + */ + Token recoverInline(Parser recognizer) throws RecognitionException; + + /** + * This method is called to recover from exception {@code e}. This method is + * called after {@link #reportError} by the default exception handler + * generated for a rule method. + * + * @see #reportError + * + * @param recognizer the parser instance + * @param e the recognition exception to recover from + * @throws RecognitionException if the error strategy could not recover from + * the recognition exception + */ + void recover(Parser recognizer, RecognitionException e) throws RecognitionException; + + /** + * This method provides the error handler with an opportunity to handle + * syntactic or semantic errors in the input stream before they result in a + * {@link RecognitionException}. + * + *

The generated code currently contains calls to {@link #sync} after + * entering the decision state of a closure block ({@code (...)*} or + * {@code (...)+}).

+ * + *

For an implementation based on Jim Idle's "magic sync" mechanism, see + * {@link DefaultErrorStrategy#sync}.

+ * + * @see DefaultErrorStrategy#sync + * + * @param recognizer the parser instance + * @throws RecognitionException if an error is detected by the error + * strategy but cannot be automatically recovered at the current state in + * the parsing process + */ + void sync(Parser recognizer) throws RecognitionException; + + /** + * Tests whether or not {@code recognizer} is in the process of recovering + * from an error. In error recovery mode, {@link Parser#consume} adds + * symbols to the parse tree by calling + * {@link ParserRuleContext#addErrorNode(Token)} instead of + * {@link ParserRuleContext#addChild(Token)}. + * + * @param recognizer the parser instance + * @return {@code true} if the parser is currently recovering from a parse + * error, otherwise {@code false} + */ + boolean inErrorRecoveryMode(Parser recognizer); + + /** + * This method is called by when the parser successfully matches an input + * symbol. + * + * @param recognizer the parser instance + */ + void reportMatch(Parser recognizer); + + /** + * Report any kind of {@link RecognitionException}. This method is called by + * the default exception handler generated for a rule method. + * + * @param recognizer the parser instance + * @param e the recognition exception to report + */ + void reportError(Parser recognizer, RecognitionException e); +} diff --git a/Source/eu.modelwriter.core.alloyinecore/lib/antlr-src/org/antlr/v4/runtime/ANTLRFileStream.java b/Source/eu.modelwriter.core.alloyinecore/lib/antlr-src/org/antlr/v4/runtime/ANTLRFileStream.java new file mode 100644 index 00000000..ba4a5313 --- /dev/null +++ b/Source/eu.modelwriter.core.alloyinecore/lib/antlr-src/org/antlr/v4/runtime/ANTLRFileStream.java @@ -0,0 +1,63 @@ +/* + * [The "BSD license"] + * Copyright (c) 2012 Terence Parr + * Copyright (c) 2012 Sam Harwell + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.antlr.v4.runtime; + +import org.antlr.v4.runtime.misc.Utils; + +import java.io.IOException; + +/** + * This is an {@link ANTLRInputStream} that is loaded from a file all at once + * when you construct the object. + */ +public class ANTLRFileStream extends ANTLRInputStream { + protected String fileName; + + public ANTLRFileStream(String fileName) throws IOException { + this(fileName, null); + } + + public ANTLRFileStream(String fileName, String encoding) throws IOException { + this.fileName = fileName; + load(fileName, encoding); + } + + public void load(String fileName, String encoding) + throws IOException + { + data = Utils.readFile(fileName, encoding); + this.n = data.length; + } + + @Override + public String getSourceName() { + return fileName; + } +} diff --git a/Source/eu.modelwriter.core.alloyinecore/lib/antlr-src/org/antlr/v4/runtime/ANTLRInputStream.java b/Source/eu.modelwriter.core.alloyinecore/lib/antlr-src/org/antlr/v4/runtime/ANTLRInputStream.java new file mode 100644 index 00000000..62c562e3 --- /dev/null +++ b/Source/eu.modelwriter.core.alloyinecore/lib/antlr-src/org/antlr/v4/runtime/ANTLRInputStream.java @@ -0,0 +1,250 @@ +/* + * [The "BSD license"] + * Copyright (c) 2012 Terence Parr + * Copyright (c) 2012 Sam Harwell + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.antlr.v4.runtime; + +import org.antlr.v4.runtime.misc.Interval; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.util.Arrays; + +/** + * Vacuum all input from a {@link Reader}/{@link InputStream} and then treat it + * like a {@code char[]} buffer. Can also pass in a {@link String} or + * {@code char[]} to use. + * + *

If you need encoding, pass in stream/reader with correct encoding.

+ */ +public class ANTLRInputStream implements CharStream { + public static final int READ_BUFFER_SIZE = 1024; + public static final int INITIAL_BUFFER_SIZE = 1024; + + /** The data being scanned */ + protected char[] data; + + /** How many characters are actually in the buffer */ + protected int n; + + /** 0..n-1 index into string of next char */ + protected int p=0; + + /** What is name or source of this char stream? */ + public String name; + + public ANTLRInputStream() { } + + /** Copy data in string to a local char array */ + public ANTLRInputStream(String input) { + this.data = input.toCharArray(); + this.n = input.length(); + } + + /** This is the preferred constructor for strings as no data is copied */ + public ANTLRInputStream(char[] data, int numberOfActualCharsInArray) { + this.data = data; + this.n = numberOfActualCharsInArray; + } + + public ANTLRInputStream(Reader r) throws IOException { + this(r, INITIAL_BUFFER_SIZE, READ_BUFFER_SIZE); + } + + public ANTLRInputStream(Reader r, int initialSize) throws IOException { + this(r, initialSize, READ_BUFFER_SIZE); + } + + public ANTLRInputStream(Reader r, int initialSize, int readChunkSize) throws IOException { + load(r, initialSize, readChunkSize); + } + + public ANTLRInputStream(InputStream input) throws IOException { + this(new InputStreamReader(input), INITIAL_BUFFER_SIZE); + } + + public ANTLRInputStream(InputStream input, int initialSize) throws IOException { + this(new InputStreamReader(input), initialSize); + } + + public ANTLRInputStream(InputStream input, int initialSize, int readChunkSize) throws IOException { + this(new InputStreamReader(input), initialSize, readChunkSize); + } + + public void load(Reader r, int size, int readChunkSize) + throws IOException + { + if ( r==null ) { + return; + } + if ( size<=0 ) { + size = INITIAL_BUFFER_SIZE; + } + if ( readChunkSize<=0 ) { + readChunkSize = READ_BUFFER_SIZE; + } + // System.out.println("load "+size+" in chunks of "+readChunkSize); + try { + // alloc initial buffer size. + data = new char[size]; + // read all the data in chunks of readChunkSize + int numRead=0; + int p = 0; + do { + if ( p+readChunkSize > data.length ) { // overflow? + // System.out.println("### overflow p="+p+", data.length="+data.length); + data = Arrays.copyOf(data, data.length * 2); + } + numRead = r.read(data, p, readChunkSize); + // System.out.println("read "+numRead+" chars; p was "+p+" is now "+(p+numRead)); + p += numRead; + } while (numRead!=-1); // while not EOF + // set the actual size of the data available; + // EOF subtracted one above in p+=numRead; add one back + n = p+1; + //System.out.println("n="+n); + } + finally { + r.close(); + } + } + + /** Reset the stream so that it's in the same state it was + * when the object was created *except* the data array is not + * touched. + */ + public void reset() { + p = 0; + } + + @Override + public void consume() { + if (p >= n) { + assert LA(1) == IntStream.EOF; + throw new IllegalStateException("cannot consume EOF"); + } + + //System.out.println("prev p="+p+", c="+(char)data[p]); + if ( p < n ) { + p++; + //System.out.println("p moves to "+p+" (c='"+(char)data[p]+"')"); + } + } + + @Override + public int LA(int i) { + if ( i==0 ) { + return 0; // undefined + } + if ( i<0 ) { + i++; // e.g., translate LA(-1) to use offset i=0; then data[p+0-1] + if ( (p+i-1) < 0 ) { + return IntStream.EOF; // invalid; no char before first char + } + } + + if ( (p+i-1) >= n ) { + //System.out.println("char LA("+i+")=EOF; p="+p); + return IntStream.EOF; + } + //System.out.println("char LA("+i+")="+(char)data[p+i-1]+"; p="+p); + //System.out.println("LA("+i+"); p="+p+" n="+n+" data.length="+data.length); + return data[p+i-1]; + } + + public int LT(int i) { + return LA(i); + } + + /** Return the current input symbol index 0..n where n indicates the + * last symbol has been read. The index is the index of char to + * be returned from LA(1). + */ + @Override + public int index() { + return p; + } + + @Override + public int size() { + return n; + } + + /** mark/release do nothing; we have entire buffer */ + @Override + public int mark() { + return -1; + } + + @Override + public void release(int marker) { + } + + /** consume() ahead until p==index; can't just set p=index as we must + * update line and charPositionInLine. If we seek backwards, just set p + */ + @Override + public void seek(int index) { + if ( index<=p ) { + p = index; // just jump; don't update stream state (line, ...) + return; + } + // seek forward, consume until p hits index or n (whichever comes first) + index = Math.min(index, n); + while ( p= n ) stop = n-1; + int count = stop - start + 1; + if ( start >= n ) return ""; +// System.err.println("data: "+Arrays.toString(data)+", n="+n+ +// ", start="+start+ +// ", stop="+stop); + return new String(data, start, count); + } + + @Override + public String getSourceName() { + if (name == null || name.isEmpty()) { + return UNKNOWN_SOURCE_NAME; + } + + return name; + } + + @Override + public String toString() { return new String(data); } +} diff --git a/Source/eu.modelwriter.core.alloyinecore/lib/antlr-src/org/antlr/v4/runtime/BailErrorStrategy.java b/Source/eu.modelwriter.core.alloyinecore/lib/antlr-src/org/antlr/v4/runtime/BailErrorStrategy.java new file mode 100644 index 00000000..f4874dd9 --- /dev/null +++ b/Source/eu.modelwriter.core.alloyinecore/lib/antlr-src/org/antlr/v4/runtime/BailErrorStrategy.java @@ -0,0 +1,96 @@ +/* + * [The "BSD license"] + * Copyright (c) 2012 Terence Parr + * Copyright (c) 2012 Sam Harwell + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.antlr.v4.runtime; + +import org.antlr.v4.runtime.misc.ParseCancellationException; + +/** + * This implementation of {@link ANTLRErrorStrategy} responds to syntax errors + * by immediately canceling the parse operation with a + * {@link ParseCancellationException}. The implementation ensures that the + * {@link ParserRuleContext#exception} field is set for all parse tree nodes + * that were not completed prior to encountering the error. + * + *

+ * This error strategy is useful in the following scenarios.

+ * + *
    + *
  • Two-stage parsing: This error strategy allows the first + * stage of two-stage parsing to immediately terminate if an error is + * encountered, and immediately fall back to the second stage. In addition to + * avoiding wasted work by attempting to recover from errors here, the empty + * implementation of {@link BailErrorStrategy#sync} improves the performance of + * the first stage.
  • + *
  • Silent validation: When syntax errors are not being + * reported or logged, and the parse result is simply ignored if errors occur, + * the {@link BailErrorStrategy} avoids wasting work on recovering from errors + * when the result will be ignored either way.
  • + *
+ * + *

+ * {@code myparser.setErrorHandler(new BailErrorStrategy());}

+ * + * @see Parser#setErrorHandler(ANTLRErrorStrategy) + */ +public class BailErrorStrategy extends DefaultErrorStrategy { + /** Instead of recovering from exception {@code e}, re-throw it wrapped + * in a {@link ParseCancellationException} so it is not caught by the + * rule function catches. Use {@link Exception#getCause()} to get the + * original {@link RecognitionException}. + */ + @Override + public void recover(Parser recognizer, RecognitionException e) { + for (ParserRuleContext context = recognizer.getContext(); context != null; context = context.getParent()) { + context.exception = e; + } + + throw new ParseCancellationException(e); + } + + /** Make sure we don't attempt to recover inline; if the parser + * successfully recovers, it won't throw an exception. + */ + @Override + public Token recoverInline(Parser recognizer) + throws RecognitionException + { + InputMismatchException e = new InputMismatchException(recognizer); + for (ParserRuleContext context = recognizer.getContext(); context != null; context = context.getParent()) { + context.exception = e; + } + + throw new ParseCancellationException(e); + } + + /** Make sure we don't attempt to recover from problems in subrules. */ + @Override + public void sync(Parser recognizer) { } +} diff --git a/Source/eu.modelwriter.core.alloyinecore/lib/antlr-src/org/antlr/v4/runtime/BaseErrorListener.java b/Source/eu.modelwriter.core.alloyinecore/lib/antlr-src/org/antlr/v4/runtime/BaseErrorListener.java new file mode 100644 index 00000000..3a752f2d --- /dev/null +++ b/Source/eu.modelwriter.core.alloyinecore/lib/antlr-src/org/antlr/v4/runtime/BaseErrorListener.java @@ -0,0 +1,85 @@ +/* + * [The "BSD license"] + * Copyright (c) 2012 Terence Parr + * Copyright (c) 2012 Sam Harwell + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.antlr.v4.runtime; + +import org.antlr.v4.runtime.atn.ATNConfigSet; +import org.antlr.v4.runtime.dfa.DFA; + +import java.util.BitSet; + +/** + * Provides an empty default implementation of {@link ANTLRErrorListener}. The + * default implementation of each method does nothing, but can be overridden as + * necessary. + * + * @author Sam Harwell + */ +public class BaseErrorListener implements ANTLRErrorListener { + @Override + public void syntaxError(Recognizer recognizer, + Object offendingSymbol, + int line, + int charPositionInLine, + String msg, + RecognitionException e) + { + } + + @Override + public void reportAmbiguity(Parser recognizer, + DFA dfa, + int startIndex, + int stopIndex, + boolean exact, + BitSet ambigAlts, + ATNConfigSet configs) + { + } + + @Override + public void reportAttemptingFullContext(Parser recognizer, + DFA dfa, + int startIndex, + int stopIndex, + BitSet conflictingAlts, + ATNConfigSet configs) + { + } + + @Override + public void reportContextSensitivity(Parser recognizer, + DFA dfa, + int startIndex, + int stopIndex, + int prediction, + ATNConfigSet configs) + { + } +} diff --git a/Source/eu.modelwriter.core.alloyinecore/lib/antlr-src/org/antlr/v4/runtime/BufferedTokenStream.java b/Source/eu.modelwriter.core.alloyinecore/lib/antlr-src/org/antlr/v4/runtime/BufferedTokenStream.java new file mode 100644 index 00000000..ba1417c6 --- /dev/null +++ b/Source/eu.modelwriter.core.alloyinecore/lib/antlr-src/org/antlr/v4/runtime/BufferedTokenStream.java @@ -0,0 +1,509 @@ +/* + * [The "BSD license"] + * Copyright (c) 2012 Terence Parr + * Copyright (c) 2012 Sam Harwell + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.antlr.v4.runtime; + +import org.antlr.v4.runtime.misc.Interval; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * This implementation of {@link TokenStream} loads tokens from a + * {@link TokenSource} on-demand, and places the tokens in a buffer to provide + * access to any previous token by index. + * + *

+ * This token stream ignores the value of {@link Token#getChannel}. If your + * parser requires the token stream filter tokens to only those on a particular + * channel, such as {@link Token#DEFAULT_CHANNEL} or + * {@link Token#HIDDEN_CHANNEL}, use a filtering token stream such a + * {@link CommonTokenStream}.

+ */ +public class BufferedTokenStream implements TokenStream { + /** + * The {@link TokenSource} from which tokens for this stream are fetched. + */ + protected TokenSource tokenSource; + + /** + * A collection of all tokens fetched from the token source. The list is + * considered a complete view of the input once {@link #fetchedEOF} is set + * to {@code true}. + */ + protected List tokens = new ArrayList(100); + + /** + * The index into {@link #tokens} of the current token (next token to + * {@link #consume}). {@link #tokens}{@code [}{@link #p}{@code ]} should be + * {@link #LT LT(1)}. + * + *

This field is set to -1 when the stream is first constructed or when + * {@link #setTokenSource} is called, indicating that the first token has + * not yet been fetched from the token source. For additional information, + * see the documentation of {@link IntStream} for a description of + * Initializing Methods.

+ */ + protected int p = -1; + + /** + * Indicates whether the {@link Token#EOF} token has been fetched from + * {@link #tokenSource} and added to {@link #tokens}. This field improves + * performance for the following cases: + * + *