001package org.clafer.javascript; 002 003import java.io.BufferedReader; 004import java.io.File; 005import java.io.IOException; 006import java.io.InputStreamReader; 007import java.util.Set; 008import javax.script.ScriptException; 009import org.clafer.ast.AstClafer; 010import org.clafer.ast.AstConstraint; 011import org.clafer.ast.AstModel; 012import org.clafer.ast.AstUtil; 013import org.clafer.ast.Asts; 014import org.clafer.collection.Pair; 015import org.clafer.collection.Triple; 016import org.clafer.compiler.ClaferCompiler; 017import org.clafer.compiler.ClaferOptions; 018import org.clafer.compiler.ClaferSearch; 019import org.clafer.compiler.ClaferUnsat; 020import org.clafer.instance.InstanceModel; 021import org.clafer.objective.Objective; 022import org.clafer.scope.Scope; 023import org.mozilla.javascript.Context; 024import org.mozilla.javascript.RhinoException; 025import org.mozilla.javascript.Scriptable; 026 027/** 028 * The Javascript CLI. 029 * 030 * @author jimmy 031 */ 032public class JavascriptShell { 033 034 // The Javascript engine. 035 private Scriptable engine = Javascript.newEngine(); 036 // The model. Null if the model is not successfully loaded. 037 private AstModel model; 038 // The scope. Null if the model is not successfully loaded. 039 private Scope scope; 040 // The objectives. 041 private Objective[] objectives; 042 // The solver which solved the previous instance. 043 // Null if first instance not yet solved. 044 private ClaferSearch solver; 045 // The last file successfully loaded. 046 private File modelFile; 047 // Configurable options. 048 private final Options options = new Options(); 049 050 public void init() throws IOException { 051 Context cxt = Context.enter(); 052 cxt.setOptimizationLevel(-1); 053 try { 054 engine.put("rsc", engine, this); 055 // solver.js contains the mapping from Javascript to the methods 056 // in this class. 057 cxt.evaluateReader(engine, 058 new InputStreamReader(JavascriptShell.class.getResourceAsStream("solver.js")), 059 "solver.js", 1, null); 060 engine.put("help", engine, 061 "help display this helpful message\n" 062 + "load() reload the model\n" 063 + "load(filename) load a new model\n" 064 + "solve() find the next solution\n" 065 + "maximize(Clafer) find a solution where Clafer.ref is maximal\n" 066 + "minimize(Clafer) find a solution where Clafer.ref is minimal\n" 067 + "minUnsat() find the smallest set of unsatisfiable constraints and a near-miss\n" 068 + "unsatCore() find a small set of mutually unsatisfiable constraints\n" 069 + "stats() display statistics about the current search\n" 070 + "options display and modify the compiler options\n" 071 + "exit() stop the session"); 072 engine.put("options", engine, options); 073 } finally { 074 Context.exit(); 075 } 076 } 077 078 /** 079 * Returns information about the final Choco CSP. 080 * 081 * @return information for debugging 082 */ 083 public String internals() { 084 return internals(false); 085 } 086 087 /** 088 * Returns information about the final Choco CSP. 089 * 090 * @param initial debug initial state or current state 091 * @return information for debugging 092 */ 093 public String internals(boolean initial) { 094 if (model == null) { 095 return "No model. Use \"load(filename)\" to load in a new model."; 096 } 097 if (initial) { 098 return ClaferCompiler.compile(getModel(), scope).getInternalSolver().toString(); 099 } 100 if (solver == null) { 101 return "Solve an instance first. Try \"solve()\"."; 102 } 103 return solver.getInternalSolver().toString() + "\n" + solver.getInternalSolver().getMeasures(); 104 } 105 106 /** 107 * Execute Javascript. 108 * 109 * @param script executable Javascript 110 * @return the value of the script, or null if it is a statement 111 */ 112 String eval(String script) { 113 Context cxt = Context.enter(); 114 cxt.setOptimizationLevel(-1); 115 try { 116 Object obj = cxt.evaluateString(engine, script, "commandline", 1, null); 117 if (obj instanceof Messagable) { 118 return ((Messagable) obj).toMessage(); 119 } 120 return Context.toString(obj); 121 } finally { 122 Context.exit(); 123 } 124 } 125 126 /** 127 * Reload the previous successful file. 128 * 129 * @return a message 130 * @throws IOException something happened to the file 131 */ 132 public String load() throws IOException { 133 if (modelFile == null) { 134 return "No model. Use \"load(filename)\" to load in a new model."; 135 } 136 return load(modelFile); 137 } 138 139 /** 140 * Load a new file. The file must contain valid Javascript. 141 * 142 * @param filename the name of the file 143 * @return a message 144 * @throws IOException something happened to the file 145 */ 146 public String load(String filename) throws IOException { 147 return load(new File(filename)); 148 } 149 150 /** 151 * Load a new file. The file must contain valid Javascript. 152 * 153 * @param file the file 154 * @return a message 155 * @throws IOException something happened to the file 156 */ 157 public String load(File file) throws IOException { 158 model = null; 159 scope = null; 160 solver = null; 161 engine = Javascript.newEngine(); 162 try { 163 Triple<AstModel, Scope, Objective[]> pair = Javascript.readModel(file, engine); 164 model = pair.getFst(); 165 scope = pair.getSnd(); 166 objectives = pair.getThd(); 167 } finally { 168 init(); 169 } 170 modelFile = file; 171 return "Loaded " + AstUtil.getNames(AstUtil.getClafers(model)) + "."; 172 } 173 174 private AstModel getModel() { 175 if (model == null) { 176 throw new JavascriptException("No model. Use \"load(filename)\" to load in a new model."); 177 } 178 return model; 179 } 180 181 /** 182 * Find the first instance or next instances upon subsequent invocations. 183 * 184 * @return an instance 185 */ 186 public Object solve() { 187 if (solver == null) { 188 if (objectives.length == 0) { 189 solver = ClaferCompiler.compile(getModel(), scope); 190 } else if (objectives.length == 1) { 191 solver = ClaferCompiler.compile(getModel(), scope, objectives[0]); 192 } else { 193 return "Muliobjective optimization not yet supported."; 194 } 195 } 196 return solver.find() ? solver.instance() : null; 197 } 198 199 /** 200 * Find an instance where the Clafer's value is maximal. 201 * 202 * @param clafer maximize this Clafer's value 203 * @return the maximal value and the optimal instance 204 */ 205 public Object maximize(AstClafer clafer) { 206 if (!clafer.hasRef()) { 207 throw new JavascriptException("Cannot maximize " + clafer + "."); 208 } 209 solver = ClaferCompiler.compile(getModel(), scope, 210 Objective.maximize(Asts.sum(Asts.global(clafer)))); 211 return solver.find() ? solver.instance() : null; 212 213 } 214 215 /** 216 * Find an instance where the Clafer's value is minimal. 217 * 218 * @param clafer minimize this Clafer's value 219 * @return the minimal value and the optimal instance 220 */ 221 public Object minimize(AstClafer clafer) { 222 if (!clafer.hasRef()) { 223 throw new JavascriptException("Cannot minimize " + clafer + "."); 224 } 225 solver = ClaferCompiler.compile(getModel(), scope, 226 Objective.minimize(Asts.sum(Asts.global(clafer)))); 227 return solver.find() ? solver.instance() : null; 228 } 229 230 /** 231 * Find the Min-Unsat and near-miss example. The Min-Unsat is empty if the 232 * model is satisfiable, in which case the near-miss example is a real 233 * instance of the model. 234 * 235 * @return the Min-Unsat and near-miss example 236 */ 237 public Pair<Set<AstConstraint>, InstanceModel> minUnsat() { 238 ClaferUnsat unsat = ClaferCompiler.compileUnsat(getModel(), scope); 239 return unsat.minUnsat(); 240 } 241 242 /** 243 * Find the Min-Unsat-Core. Undefined if the model is satisfiable. 244 * 245 * @return the Min-Unsat-Core 246 */ 247 public Set<AstConstraint> unsatCore() { 248 ClaferUnsat unsat = ClaferCompiler.compileUnsat(getModel(), scope); 249 return unsat.unsatCore(); 250 } 251 252 public String stats() { 253 if (solver == null) { 254 throw new JavascriptException("No solver. Use \"solve()\" to solve the model."); 255 } 256 return solver.getInternalSolver().getMeasures().toString(); 257 } 258 259 /** 260 * Exit the program. 261 * 262 * @return a message 263 */ 264 public String exit() { 265 // Use interrupts to signal end. 266 Thread.currentThread().interrupt(); 267 return "Exitting ..."; 268 } 269 270 public static void main(String[] args) throws IOException, ScriptException { 271 JavascriptShell context = new JavascriptShell(); 272 if (args.length > 0) { 273 try { 274 System.out.println(context.load(args[0])); 275 } catch (IOException e) { 276 System.out.println("Error: " + getOriginalMessage(e)); 277 } catch (RhinoException e) { 278 System.out.println("Error: " + getOriginalMessage(e)); 279 } 280 } else { 281 context.init(); 282 } 283 284 System.out.println("Type \"help\" for a list of commands."); 285 System.out.print("> "); 286 System.out.flush(); 287 BufferedReader r = new BufferedReader(new InputStreamReader(System.in)); 288 String line; 289 while ((line = r.readLine()) != null) { 290 line = line.trim(); 291 // Do not evaluate empty lines. 292 if (line.length() > 0) { 293 try { 294 // Evaluate the Javascript command. 295 System.out.println(context.eval(line)); 296 if (Thread.interrupted()) { 297 // Exit was invoked. 298 break; 299 } 300 } catch (RhinoException e) { 301 System.out.println("Error: " + getOriginalMessage(e)); 302 } 303 } 304 System.out.print("> "); 305 System.out.flush(); 306 } 307 System.out.println(); 308 } 309 310 /** 311 * Find the message in the innermost cause. The Rhino engine tends to have 312 * several layers of wrapped exceptions, which do not look great when 313 * printing on the command line. 314 * 315 * @param e the outermost exception 316 * @return the innermost message 317 */ 318 private static String getOriginalMessage(Throwable e) { 319 if (e.getCause() != null) { 320 return getOriginalMessage(e.getCause()); 321 } 322 return e.getMessage(); 323 } 324 325 public static interface Messagable { 326 327 /** 328 * @return a message for the shell 329 */ 330 public String toMessage(); 331 } 332 333 public static class Options implements Messagable { 334 335 private ClaferOptions options = ClaferOptions.Default; 336 337 public String preferSmallerInstances() { 338 options = options.preferSmallerInstances(); 339 return "Updated options."; 340 } 341 342 public String preferLargerInstances() { 343 options = options.preferLargerInstances(); 344 return "Updated options."; 345 } 346 347 public String basicSymmetryBreaking() { 348 options = options.basicSymmetryBreaking(); 349 return "Updated options."; 350 } 351 352 public String fullSymmetryBreaking() { 353 options = options.fullSymmetryBreaking(); 354 return "Updated options."; 355 } 356 357 public String basicOptimizations() { 358 options = options.basicOptimizations(); 359 return "Updated options."; 360 } 361 362 public String fullOptimizations() { 363 options = options.fullOptimizations(); 364 return "Updated options."; 365 } 366 367 // Convenience function for the toString method. 368 private static String star(boolean bool) { 369 return bool ? " * " : " "; 370 } 371 372 /** 373 * {@inheritDoc} 374 */ 375 @Override 376 public String toMessage() { 377 return "(*) marks the current setting\n" 378 + star(options.isPreferSmallerInstances()) + "options.preferSmallerInstances() bias the search towards smaller instances\n" 379 + star(options.isPreferLargerInstances()) + "options.preferLargerInstances() bias the search towards larger instances\n" 380 + star(options.isBasicSymmetryBreaking()) + "options.basicSymmetryBreaking() basic symmetry breaking\n" 381 + star(options.isFullSymmetryBreaking()) + "options.fullSymmetryBreaking() full symmetry breaking\n" 382 + star(options.isBasicOptimizations()) + "options.basicOptimizations() basic optimizations\n" 383 + star(options.isFullOptimizations()) + "options.fullOptimizations() full optimizations"; 384 } 385 } 386}