From e130a76028803367e103586a5b12646695b4f076 Mon Sep 17 00:00:00 2001 From: Dave Parker Date: Sun, 28 Jun 2026 09:22:07 +0100 Subject: [PATCH 01/15] CLI switch handling refactoring. Code to process switches in PrismCL cleaned/refactored. ArgConsumer: handles iteration through argument list. SwitchHandler: interface for code to deal with a switch, plus instantiations for switches that are flags or that take strings, ints, doubles, enums. OptionParser: parses an option list opt1,key=val,... Switch definitions in PrismCL become calls rto addSwitch methods that populate a switchHandlers map. Complex switches (e.g. -exportmodel) now use OptionsParser. The same idea is applied in PrismSettings, where switches that set values in the settings object are defined. --- prism/src/prism/ArgConsumer.java | 151 +++ prism/src/prism/OptionParser.java | 165 ++++ prism/src/prism/PrismCL.java | 1482 +++++++--------------------- prism/src/prism/PrismSettings.java | 1052 ++++++-------------- prism/src/prism/SwitchHandler.java | 135 +++ 5 files changed, 1121 insertions(+), 1864 deletions(-) create mode 100644 prism/src/prism/ArgConsumer.java create mode 100644 prism/src/prism/OptionParser.java create mode 100644 prism/src/prism/SwitchHandler.java diff --git a/prism/src/prism/ArgConsumer.java b/prism/src/prism/ArgConsumer.java new file mode 100644 index 0000000000..b780ddb1e7 --- /dev/null +++ b/prism/src/prism/ArgConsumer.java @@ -0,0 +1,151 @@ +//============================================================================== +// +// Copyright (c) 2026- +// Authors: +// * Dave Parker (University of Oxford) +// +//------------------------------------------------------------------------------ +// +// This file is part of PRISM. +// +// PRISM is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// PRISM is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with PRISM; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +//============================================================================== + +package prism; + +/** + * Wraps a CLI argument array and current position for cleaner switch handling. + * Provides typed argument consumption that throws {@link PrismException}s on error. + */ +class ArgConsumer +{ + private final String[] args; + private int i; + private String optionsString; + + /** + * Creates a consumer over the full argument array, positioned before the first element. + * The caller drives iteration by calling {@link #advance()} then {@link #parseSwitch(String)}. + */ + ArgConsumer(String[] args) + { + this.args = args; + this.i = -1; + this.optionsString = null; + } + + /** + * Advances to and returns the next raw argument. + * The caller must check {@link #hasNext()} before calling this. + */ + String advance() + { + return args[++i]; + } + + /** + * Parses a raw CLI token as a switch: strips the leading {@code -} or {@code --}, + * splits on the first {@code :} to extract any sub-options, stores them in + * {@link #optionsString()}, and returns the switch name. + * Returns {@code null} if the token does not start with {@code -} (i.e. it is a filename). + * Throws if the switch name would be empty (bare {@code -} or {@code --}). + */ + String parseSwitch(String arg) throws PrismException + { + if (arg.isEmpty() || arg.charAt(0) != '-') { + optionsString = null; + return null; + } + String sw = arg.substring(1); + if (sw.isEmpty()) throw new PrismException("Invalid empty switch"); + if (sw.charAt(0) == '-') { + sw = sw.substring(1); + if (sw.isEmpty()) throw new PrismException("Invalid empty switch"); + } + int colon = sw.indexOf(':'); + if (colon == -1) { + optionsString = null; + return sw; + } + optionsString = sw.substring(colon + 1); + return sw.substring(0, colon); + } + + /** Colon sub-options from {@code -switch:opts} syntax (e.g. {@code "upper,lower"} from {@code -intervaliter:upper,lower}), or {@code null}. */ + String optionsString() + { + return optionsString; + } + + /** Returns the current index (updated by any {@code next*()} calls). */ + int index() + { + return i; + } + + /** Whether a next argument is available. */ + boolean hasNext() + { + return i < args.length - 1; + } + + /** Returns the next argument without consuming it, or {@code null} if there is none. */ + String peek() + { + return hasNext() ? args[i + 1] : null; + } + + /** Consume and return the next argument, or throw a descriptive error for switch {@code sw}. */ + String next(String sw) throws PrismException + { + if (!hasNext()) + throw new PrismException("No value specified for -" + sw + " switch"); + return args[++i]; + } + + /** Consume the next argument, parse it as an {@code int}, and return it. */ + int nextInt(String sw) throws PrismException + { + String s = next(sw); + try { + return Integer.parseInt(s); + } catch (NumberFormatException e) { + throw new PrismException("Invalid value \"" + s + "\" for -" + sw + " switch"); + } + } + + /** Consume the next argument, parse it as a {@code double}, and return it. */ + double nextDouble(String sw) throws PrismException + { + String s = next(sw); + try { + return Double.parseDouble(s); + } catch (NumberFormatException e) { + throw new PrismException("Invalid value \"" + s + "\" for -" + sw + " switch"); + } + } + + /** Consume the next argument, parse it as a {@code long}, and return it. */ + long nextLong(String sw) throws PrismException + { + String s = next(sw); + try { + return Long.parseLong(s); + } catch (NumberFormatException e) { + throw new PrismException("Invalid value \"" + s + "\" for -" + sw + " switch"); + } + } +} diff --git a/prism/src/prism/OptionParser.java b/prism/src/prism/OptionParser.java new file mode 100644 index 0000000000..b08e1888f4 --- /dev/null +++ b/prism/src/prism/OptionParser.java @@ -0,0 +1,165 @@ +//============================================================================== +// +// Copyright (c) 2026- +// Authors: +// * Dave Parker (University of Oxford) +// +//------------------------------------------------------------------------------ +// +// This file is part of PRISM. +// +// PRISM is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// PRISM is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with PRISM; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +//============================================================================== + +package prism; + +import java.util.LinkedHashMap; + +/** + * Builder for parsing a comma-separated list of sub-options within a CLI switch argument + * (e.g. the {@code opt1,key=val,...} portion of {@code -switch files:opt1,key=val,...}). + * Handles three option styles: bare flags, boolean {@code key=true|false}, fixed-choice + * {@code key=val}, and raw-string {@code key=anything}. + * All error messages include the option name and switch name automatically. + */ +class OptionParser +{ + @FunctionalInterface interface FlagAction { void run() throws PrismException; } + @FunctionalInterface interface StringAction { void accept(String v) throws PrismException; } + @FunctionalInterface interface BoolAction { void accept(boolean b) throws PrismException; } + + /** Internal handler for key=value options. */ + @FunctionalInterface + private interface ValueHandler + { + void accept(String key, String val, String switchName) throws PrismException; + } + + private final LinkedHashMap flagHandlers = new LinkedHashMap<>(); + private final LinkedHashMap valueHandlers = new LinkedHashMap<>(); + + /** Register a bare flag option (no {@code =value}). */ + OptionParser flag(String name, FlagAction action) + { + flagHandlers.put(name, action); + return this; + } + + /** Register a {@code key=value} option; action receives the raw value string. */ + OptionParser string(String name, StringAction action) + { + valueHandlers.put(name, (key, val, sw) -> action.accept(val)); + return this; + } + + /** Register a {@code key=true|false} option. */ + OptionParser bool(String name, BoolAction action) + { + valueHandlers.put(name, (key, val, sw) -> { + if (val.equals("true")) + action.accept(true); + else if (val.equals("false")) + action.accept(false); + else + throw new PrismException("Unknown value \"" + val + "\" for \"" + key + "\" option of -" + sw + + " (expected true or false)"); + }); + return this; + } + + /** Register a {@code key=val} option where {@code val} must be one of a fixed set. */ + OptionParser choice(String name, Choice c) + { + valueHandlers.put(name, c.toHandler()); + return this; + } + + /** + * Parse a comma-separated options string, dispatching each token to the registered handler. + * @param optionsString comma-separated options (may be empty or null) + * @param switchName CLI switch name used in error messages (without leading {@code -}) + */ + void parse(String optionsString, String switchName) throws PrismException + { + if (optionsString == null || optionsString.isEmpty()) return; + for (String opt : optionsString.split(",")) { + if (opt.isEmpty()) continue; + int eq = opt.indexOf('='); + if (eq == -1) { + // Bare option — must be a registered flag + FlagAction fa = flagHandlers.get(opt); + if (fa != null) { fa.run(); continue; } + if (valueHandlers.containsKey(opt)) + throw new PrismException("No value provided for \"" + opt + "\" option of -" + switchName); + throw new PrismException("Unknown option \"" + opt + "\" for -" + switchName + " switch"); + } else { + // key=value option + String key = opt.substring(0, eq); + String val = opt.substring(eq + 1); + ValueHandler vh = valueHandlers.get(key); + if (vh != null) { vh.accept(key, val, switchName); continue; } + if (flagHandlers.containsKey(key)) + throw new PrismException("Option \"" + key + "\" for -" + switchName + " does not take a value"); + throw new PrismException("Unknown option \"" + key + "\" for -" + switchName + " switch"); + } + } + } + + /** + * Builder for a fixed set of enumerated values for a {@link #choice} option. + * Mirrors the fluent {@code .when()} style of {@link EnumSwitch}. + */ + static class Choice + { + private final LinkedHashMap map = new LinkedHashMap<>(); + + /** Register a single accepted value and its action. */ + Choice when(String key, FlagAction action) + { + map.put(key, action); + return this; + } + + /** Register two accepted values (aliases) that trigger the same action. */ + Choice when(String k1, String k2, FlagAction action) + { + map.put(k1, action); + map.put(k2, action); + return this; + } + + /** Register three accepted values (aliases) that trigger the same action. */ + Choice when(String k1, String k2, String k3, FlagAction action) + { + map.put(k1, action); + map.put(k2, action); + map.put(k3, action); + return this; + } + + /** Build the {@link ValueHandler} that dispatches on the registered values. */ + ValueHandler toHandler() + { + return (key, val, sw) -> { + FlagAction action = map.get(val); + if (action == null) + throw new PrismException("Unknown value \"" + val + "\" for \"" + key + "\" option of -" + sw + + " (options are: " + String.join(", ", map.keySet()) + ")"); + action.run(); + }; + } + } +} diff --git a/prism/src/prism/PrismCL.java b/prism/src/prism/PrismCL.java index fd5744ea06..ae33941bb3 100644 --- a/prism/src/prism/PrismCL.java +++ b/prism/src/prism/PrismCL.java @@ -2,7 +2,7 @@ // // Copyright (c) 2002- // Authors: -// * Dave Parker (University of Oxford, formerly University of Birmingham) +// * Dave Parker (University of Oxford) // //------------------------------------------------------------------------------ // @@ -36,7 +36,9 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import java.util.Map.Entry; import common.StackTraceHelper; @@ -144,6 +146,9 @@ public class PrismCL implements PrismModelListener private String exportStratFilename = null; private String simpathFilename = null; + // CLI switch handler map (populated by initSwitchHandlers) + private Map switchHandlers; + // logs private PrismLog mainLog = null; @@ -1050,827 +1055,302 @@ public void notifyModelBuildFailed(PrismException e) */ private void parseArguments(String[] args) throws PrismException { - int i, j; - String sw, s; - PrismLog log; - constSwitch = ""; paramSwitch = ""; List filenameArgs = new ArrayList<>(); + initSwitchHandlers(); + + ArgConsumer consumer = new ArgConsumer(args); + while (consumer.hasNext()) { + String arg = consumer.advance(); + String sw = consumer.parseSwitch(arg); + if (sw != null) { + SwitchHandler handler = switchHandlers.get(sw); + if (handler != null) + handler.handle(sw, consumer); + else + prism.getSettings().setFromCommandLineSwitch(sw, consumer); + } else { + // argument is assumed to be a (model/properties) filename + filenameArgs.add(arg); + } + } - for (i = 0; i < args.length; i++) { - - // if is a switch... - if (args[i].length() > 0 && args[i].charAt(0) == '-') { - - // Remove "-" - sw = args[i].substring(1); - if (sw.length() == 0) { - errorAndExit("Invalid empty switch"); - } - // Remove optional second "-" (i.e. we allow switches of the form --sw too) - if (sw.charAt(0) == '-') - sw = sw.substring(1); - - // Note: the order of these switches should match the -help output (just to help keep track of things). - // But: processing of "PRISM" options is done elsewhere in PrismSettings - // Any "hidden" options, i.e. not in -help text/manual, are indicated as such. - - // print help - if (sw.equals("help") || sw.equals("?")) { - // see if user requested help for a specific switch, e.g. -help simpath - // note: this is one of the few places where a second argument is optional, - // which is possible here because -help should usually be the only switch provided - if (i < args.length - 1) { - printHelpSwitch(args[++i]); - } else { - printHelp(); - } - exit(); - } - // java max mem & java stack size & java parameters - else if (sw.equals("javamaxmem") || sw.equals("javastack") || sw.equals("javaparams")) { - // ignore argument and subsequent value, this is dealt with before java is launched - i++; - } - // timeout - else if (sw.equals("timeout")) { - if (i < args.length - 1) { - int timeout = PrismUtils.convertTimeStringtoSeconds(args[++i]); - if (timeout < 0) { - errorAndExit("Negative timeout value \"" + timeout + "\" for -" + sw + " switch"); - } - if (timeout > 0) { - setTimeout(timeout); - } - // timeout == 0 -> no timeout - } else { - errorAndExit("Missing timeout value for -" + sw + " switch"); - } - } - // print version - else if (sw.equals("version")) { - printVersion(); - exit(); - } - // set working directory - else if (sw.equals("dir")) { - if (i < args.length - 1) { - Prism.setWorkingDirectory(args[++i]); - } else { - errorAndExit("No property specified for -" + sw + " switch"); - } - } - // load settings - else if (sw.equals("settings")) { - if (i < args.length - 1) { - settingsFilename = args[++i].trim(); - } else { - errorAndExit("Incomplete -" + sw + " switch"); - } - } - // print a list of all keywords (hidden option) - else if (sw.equals("keywords")) { - printListOfKeywords(); - exit(); - } - - // property/properties given in command line - else if (sw.equals("pf") || sw.equals("pctl") || sw.equals("csl")) { - if (i < args.length - 1) { - propertyString = args[++i]; - } else { - errorAndExit("No property specified for -" + sw + " switch"); - } - } - // which property to check (int index or string name) - else if (sw.equals("prop") || sw.equals("property")) { - if (i < args.length - 1) { - String[] props = args[++i].trim().split(","); - propertyIndices = new ArrayList(); - for (String p : props) { - if (!p.isEmpty()) { - try { - propertyIndices.add(Integer.parseInt(p)); - } catch (NumberFormatException e) { - propertyIndices.add(p); - } - } - } - } else { - errorAndExit("No value specified for -" + sw + " switch"); - } - } - // definition of undefined constants - else if (sw.equals("const")) { - if (i < args.length - 1) { - // store argument for later use (append if already partially specified) - if ("".equals(constSwitch)) - constSwitch = args[++i].trim(); - else - constSwitch += "," + args[++i].trim(); - } else { - errorAndExit("Incomplete -" + sw + " switch"); - } - } - // defining a parameter - else if (sw.equals("param")) { - param = true; - if (i < args.length - 1) { - // store argument for later use (append if already partially specified) - if ("".equals(paramSwitch)) { - paramSwitch = args[++i].trim(); - } else { - paramSwitch += "," + args[++i].trim(); - } - } else { - errorAndExit("Incomplete -" + sw + " switch"); - } - } - // do steady-state probability computation - else if (sw.equals("steadystate") || sw.equals("ss")) { - steadystate = true; - } - // do transient probability computation - else if (sw.equals("transient") || sw.equals("tr")) { - if (i < args.length - 1) { - dotransient = true; - transientTime = args[++i]; - } else { - errorAndExit("No value specified for -" + sw + " switch"); - } - } - // generate random path with simulator - else if (sw.equals("simpath")) { - if (i < args.length - 2) { - simpath = true; - simpathDetails = args[++i]; - simpathFilename = args[++i]; - } else { - errorAndExit("The -" + sw + " switch requires two arguments (path details, filename)"); - } - } - // disable model construction - else if (sw.equals("nobuild")) { - nobuild = true; - } - // enable "testing" mode - else if (sw.equals("test")) { - test = true; - } - // enable "test all" mode (don't stop on errors) - // (overrides -test switch) - else if (sw.equals("testall")) { - test = true; - testExitsOnFail = false; - } - // enable UMB test mode - else if (sw.equals("test:umb")) { - prism.setTestUMB(true); - } - - // DD Debugging options - else if (sw.equals("dddebug")) { - jdd.DebugJDD.enable(); - } - else if (sw.equals("ddtraceall")) { - jdd.DebugJDD.traceAll = true; - } - else if (sw.equals("ddtracefollowcopies")) { - jdd.DebugJDD.traceFollowCopies = true; - } - else if (sw.equals("dddebugwarnfatal")) { - jdd.DebugJDD.warningsAreFatal = true; - } - else if (sw.equals("dddebugwarnoff")) { - jdd.DebugJDD.warningsOff = true; - } - else if (sw.equals("ddtrace")) { - if (i < args.length - 1) { - String idString = args[++i]; - try { - int id = Integer.parseInt(idString); - jdd.DebugJDD.enableTracingForID(id); - } catch (NumberFormatException e) { - errorAndExit("The -" + sw + " switch requires an integer argument (JDDNode ID)"); - } - } else { - errorAndExit("The -" + sw + " switch requires an additional argument (JDDNode ID)"); - } - } - - // IMPORT OPTIONS: - - // change model type to pepa - else if (sw.equals("importpepa")) { - importpepa = true; - } - // Import model from PRISM preprocessor (hidden option) - else if (sw.equals("importprismpp")) { - if (i < args.length - 1) { - importprismpp = true; - prismppParams = args[++i]; - } else { - errorAndExit("No parameters specified for -" + sw + " switch"); - } - } - // import model from explicit file(s) - else if (sw.equals("importmodel")) { - if (i < args.length - 1) { - processImportModelSwitch(args[++i]); - } else { - errorAndExit("No file/options specified for -" + sw + " switch"); - } - } - // import transition matrix from explicit format - else if (sw.equals("importtrans")) { - if (i < args.length - 1) { - // Recall model name in case needed as basename for model exprts - modelFilename = args[++i]; - modelImportSources.add(new ModelImportSource(ModelExportTask.ModelExportEntity.MODEL, ModelExportFormat.EXPLICIT, new File(modelFilename))); - } else { - errorAndExit("No file specified for -" + sw + " switch"); - } - } - // import states for explicit model import - else if (sw.equals("importstates")) { - if (i < args.length - 1) { - modelImportSources.add(new ModelImportSource(ModelExportTask.ModelExportEntity.STATES, ModelExportFormat.EXPLICIT, new File(args[++i]))); - } else { - errorAndExit("No file specified for -" + sw + " switch"); - } - } - // import observations for explicit model import - else if (sw.equals("importobs")) { - if (i < args.length - 1) { - modelImportSources.add(new ModelImportSource(ModelExportTask.ModelExportEntity.OBSERVATIONS, ModelExportFormat.EXPLICIT, new File(args[++i]))); - } else { - errorAndExit("No file specified for -" + sw + " switch"); - } - } - // import labels for explicit model import - else if (sw.equals("importlabels")) { - if (i < args.length - 1) { - modelImportSources.add(new ModelImportSource(ModelExportTask.ModelExportEntity.LABELS, ModelExportFormat.EXPLICIT, new File(args[++i]))); - } else { - errorAndExit("No file specified for -" + sw + " switch"); - } - } - // import state rewards for explicit model import - else if (sw.equals("importstaterewards")) { - if (i < args.length - 1) { - modelImportSources.add(new ModelImportSource(ModelExportTask.ModelExportEntity.STATE_REWARDS, ModelExportFormat.EXPLICIT, new File(args[++i]))); - } else { - errorAndExit("No file specified for -" + sw + " switch"); - } - } - // import trans rewards for explicit model import - else if (sw.equals("importtransrewards")) { - if (i < args.length - 1) { - modelImportSources.add(new ModelImportSource(ModelExportTask.ModelExportEntity.TRANSITION_REWARDS, ModelExportFormat.EXPLICIT, new File(args[++i]))); - } else { - errorAndExit("No file specified for -" + sw + " switch"); - } - } - // import initial distribution e.g. for transient probability distribution - else if (sw.equals("importinitdist")) { - if (i < args.length - 1) { - importinitdist = true; - importInitDistFilename = args[++i]; - } else { - errorAndExit("No file specified for -" + sw + " switch"); - } - } - // import results - else if (sw.equals("importresults")) { - if (i < args.length - 1) { - importresults = true; - modelFilename = "no-model-file.prism"; - importResultsFilename = args[++i]; - } else { - errorAndExit("No file specified for -" + sw + " switch"); - } - } - // override model type to dtmc - else if (sw.equals("dtmc")) { - typeOverride = ModelType.DTMC; - } - // override model type to mdp - else if (sw.equals("mdp")) { - typeOverride = ModelType.MDP; - } - // override model type to ctmc - else if (sw.equals("ctmc")) { - typeOverride = ModelType.CTMC; - } - - // EXPORT OPTIONS: - - // export prism model to file - else if (sw.equals("exportprism")) { - if (i < args.length - 1) { - exportprism = true; - exportPrismFilename = args[++i]; - } else { - errorAndExit("No file specified for -" + sw + " switch"); - } - } - // export prism model to file (with consts expanded) - else if (sw.equals("exportprismconst")) { - if (i < args.length - 1) { - exportprismconst = true; - exportPrismConstFilename = args[++i]; - } else { - errorAndExit("No file specified for -" + sw + " switch"); - } - } - // export results - else if (sw.equals("exportresults")) { - if (i < args.length - 1) { - exportresults = true; - // Parse filename/options - s = args[++i]; - // Assume use of : to split filename/options but check for , if : not found - // (this was the old notation) - String halves[] = splitFilesAndOptions(s); - if (halves[1].length() == 0 && halves[0].indexOf(',') > -1) { - int comma = halves[0].indexOf(','); - halves[1] = halves[0].substring(comma + 1); - halves[0] = halves[0].substring(0, comma); - } - exportResultsFilename = halves[0]; - String ss[] = halves[1].split(","); - exportShape = ResultsExportShape.LIST_PLAIN; - for (j = 0; j < ss.length; j++) { - if (ss[j].equals("")) { - } else if (ss[j].equals("csv")) - exportShape = exportShape.isMatrix ? ResultsExportShape.MATRIX_CSV : ResultsExportShape.LIST_CSV; - else if (ss[j].equals("matrix")) - switch (exportShape) { - case LIST_PLAIN: - exportShape = ResultsExportShape.MATRIX_PLAIN; - break; - case LIST_CSV: - exportShape = ResultsExportShape.MATRIX_CSV; - break; - default: - // switch does not apply - } - else if (ss[j].equals("dataframe")) - exportShape = ResultsExportShape.DATA_FRAME; - else if (ss[j].equals("comment")) - exportShape = ResultsExportShape.COMMENT; - else - errorAndExit("Unknown option \"" + ss[j] + "\" for -" + sw + " switch"); - } - } else { - errorAndExit("No file/options specified for -" + sw + " switch"); - } - } - // export vector of results - else if (sw.equals("exportvector")) { - if (i < args.length - 1) { - exportvector = true; - exportVectorFilename = args[++i]; - prism.setStoreVector(true); - } else { - errorAndExit("No file specified for -" + sw + " switch"); - } - } - // export model to explicit file(s) - else if (sw.equals("exportmodel")) { - if (i < args.length - 1) { - processExportModelSwitch(args[++i]); - } else { - errorAndExit("No file/options specified for -" + sw + " switch"); - } - } - // process -exportmodelprecision in PrismSettings - // export transition matrix to file - else if (sw.equals("exporttrans")) { - if (i < args.length - 1) { - modelExportTasks.add(new ModelExportTask(ModelExportTask.ModelExportEntity.MODEL, args[++i])); - } else { - errorAndExit("No file specified for -" + sw + " switch"); - } - } - // export state rewards to file - else if (sw.equals("exportstaterewards")) { - if (i < args.length - 1) { - modelExportTasks.add(new ModelExportTask(ModelExportTask.ModelExportEntity.STATE_REWARDS, args[++i])); - } else { - errorAndExit("No file specified for -" + sw + " switch"); - } - } - // export transition rewards to file - else if (sw.equals("exporttransrewards")) { - if (i < args.length - 1) { - modelExportTasks.add(new ModelExportTask(ModelExportTask.ModelExportEntity.TRANSITION_REWARDS, args[++i])); - } else { - errorAndExit("No file specified for -" + sw + " switch"); - } - } - // export both state/transition rewards to file - else if (sw.equals("exportrewards")) { - if (i < args.length - 2) { - modelExportTasks.add(new ModelExportTask(ModelExportTask.ModelExportEntity.STATE_REWARDS, args[++i])); - modelExportTasks.add(new ModelExportTask(ModelExportTask.ModelExportEntity.TRANSITION_REWARDS, args[++i])); - } else { - errorAndExit("Two files must be specified for -" + sw + " switch"); - } - } - // export states - else if (sw.equals("exportstates")) { - if (i < args.length - 1) { - modelExportTasks.add(new ModelExportTask(ModelExportTask.ModelExportEntity.STATES, args[++i])); - } else { - errorAndExit("No file specified for -" + sw + " switch"); - } - } - // export observations - else if (sw.equals("exportobs")) { - if (i < args.length - 1) { - modelExportTasks.add(new ModelExportTask(ModelExportTask.ModelExportEntity.OBSERVATIONS, args[++i])); - } else { - errorAndExit("No file specified for -" + sw + " switch"); - } - } - // export labels/states - else if (sw.equals("exportlabels")) { - if (i < args.length - 1) { - processExportLabelsSwitch(args[++i]); - } else { - errorAndExit("No file specified for -" + sw + " switch"); - } - } - // export labels/states from properties file - else if (sw.equals("exportproplabels")) { - if (i < args.length - 1) { - processExportPropLabelsSwitch(args[++i]); - } else { - errorAndExit("No file specified for -" + sw + " switch"); - } - } - // switch export mode to "matlab" - else if (sw.equals("exportmatlab")) { - exportType = Prism.EXPORT_MATLAB; - modelExportOptionsGlobal.setFormat(ModelExportFormat.MATLAB); - } - // switch export mode to "mrmc" - else if (sw.equals("exportmrmc")) { - errorAndExit("Export to MRMC format no longer supported"); - } - // switch export mode to "rows" - else if (sw.equals("exportrows")) { - exportType = Prism.EXPORT_ROWS; - modelExportOptionsGlobal.setExplicitRows(true); - } - // exported matrix entries are ordered - else if (sw.equals("exportordered") || sw.equals("ordered")) { - // this is always done now, so ignore - } - // exported matrix entries are unordered - else if (sw.equals("exportunordered") || sw.equals("unordered")) { - errorAndExit("Switch -" + sw + " is no longer supported"); - } - // export transition matrix graph to dot file - else if (sw.equals("exporttransdot")) { - if (i < args.length - 1) { - ModelExportOptions exportOptions = new ModelExportOptions().setFormat(ModelExportFormat.DOT).setShowStates(false).setShowObservations(false); - modelExportTasks.add(new ModelExportTask(ModelExportTask.ModelExportEntity.MODEL, args[++i], exportOptions)); - } else { - errorAndExit("No file specified for -" + sw + " switch"); - } - } - // export transition matrix graph to dot file (with states) - else if (sw.equals("exporttransdotstates")) { - if (i < args.length - 1) { - ModelExportOptions exportOptions = new ModelExportOptions().setFormat(ModelExportFormat.DOT).setShowStates(true).setShowObservations(true); - modelExportTasks.add(new ModelExportTask(ModelExportTask.ModelExportEntity.MODEL, args[++i], exportOptions)); - } else { - errorAndExit("No file specified for -" + sw + " switch"); - } - } - // export transition matrix MTBDD to dot file - else if (sw.equals("exportdot")) { - if (i < args.length - 1) { - ModelExportOptions exportOptions = new ModelExportOptions().setFormat(ModelExportFormat.DD_DOT); - modelExportTasks.add(new ModelExportTask(ModelExportTask.ModelExportEntity.MODEL, args[++i], exportOptions)); - } else { - errorAndExit("No file specified for -" + sw + " switch"); - } - } - // export sccs to file - else if (sw.equals("exportsccs")) { - if (i < args.length - 1) { - exportsccs = true; - exportSCCsFilename = args[++i]; - } else { - errorAndExit("No file specified for -" + sw + " switch"); - } - } - // export bsccs to file - else if (sw.equals("exportbsccs")) { - if (i < args.length - 1) { - exportbsccs = true; - exportBSCCsFilename = args[++i]; - } else { - errorAndExit("No file specified for -" + sw + " switch"); - } - } - // export mecs to file - else if (sw.equals("exportmecs")) { - if (i < args.length - 1) { - exportmecs = true; - exportMECsFilename = args[++i]; - } else { - errorAndExit("No file specified for -" + sw + " switch"); - } - } - // export steady-state probs (as opposed to displaying on screen) - else if (sw.equals("exportsteadystate") || sw.equals("exportss")) { - if (i < args.length - 1) { - exportSteadyStateFilename = args[++i]; - } else { - errorAndExit("No file specified for -" + sw + " switch"); - } - // if we are asked to export the steady-state probs, we should compute them - steadystate = true; - } - // export transient probs (as opposed to displaying on screen) - else if (sw.equals("exporttransient") || sw.equals("exporttr")) { - if (i < args.length - 1) { - exportTransientFilename = args[++i]; - } else { - errorAndExit("No file specified for -" + sw + " switch"); - } - } - // export strategy - else if (sw.equals("exportstrat")) { - if (i < args.length - 1) { - processExportStratSwitch(args[++i]); - } else { - errorAndExit("No file/options specified for -" + sw + " switch"); - } - } - // export digital clocks translation prism model to file - else if (sw.equals("exportdigital")) { - if (i < args.length - 1) { - String filename = args[++i]; - File f = (filename.equals("stdout")) ? null : new File(filename); - prism.setExportDigital(true); - prism.setExportDigitalFile(f); - } else { - errorAndExit("No file specified for -" + sw + " switch"); - } - } - // export reachability target info to file (hidden option) - else if (sw.equals("exporttarget")) { - if (i < args.length - 1) { - prism.setExportTarget(true); - prism.setExportTargetFilename(args[++i]); - } else { - errorAndExit("No file specified for -" + sw + " switch"); - } - } - // export product transition matrix to file (hidden option) - else if (sw.equals("exportprodtrans")) { - if (i < args.length - 1) { - prism.setExportProductTrans(true); - prism.setExportProductTransFilename(args[++i]); - } else { - errorAndExit("No file specified for -" + sw + " switch"); - } - } - // export product states to file (hidden option) - else if (sw.equals("exportprodstates")) { - if (i < args.length - 1) { - prism.setExportProductStates(true); - prism.setExportProductStatesFilename(args[++i]); - } else { - errorAndExit("No file specified for -" + sw + " switch"); - } - } - // export product vector to file (hidden option) - else if (sw.equals("exportprodvector")) { - if (i < args.length - 1) { - prism.setExportProductVector(true); - prism.setExportProductVectorFilename(args[++i]); - } else { - errorAndExit("No file specified for -" + sw + " switch"); - } - } - - // NB: Following the ordering of the -help text, more options go here, - // but these are processed in the PrismSettings class; see below + processFileNames(filenameArgs); + } - // SIMULATION OPTIONS: + /** + * Populate {@link #switchHandlers} with a handler for every switch recognised by PrismCL. + * Switches not registered here fall through to {@link PrismSettings#setFromCommandLineSwitch}. + * The insertion order matches the -help output ordering. + */ + private void initSwitchHandlers() + { + switchHandlers = new LinkedHashMap<>(); - // use simulator for approximate/statistical model checking - else if (sw.equals("sim")) { - simulate = true; - } - // simulation-based model checking methods - else if (sw.equals("simmethod")) { - if (i < args.length - 1) { - s = args[++i]; - if (s.equals("ci") || s.equals("aci") || s.equals("apmc") || s.equals("sprt")) - simMethodName = s; - else - errorAndExit("Unrecognised option for -" + sw + " switch (options are: ci, aci, apmc, sprt)"); - } else { - errorAndExit("No parameter specified for -" + sw + " switch"); - } - } - // simulation number of samples - else if (sw.equals("simsamples")) { - if (i < args.length - 1) { - try { - simNumSamples = Integer.parseInt(args[++i]); - if (simNumSamples <= 0) - throw new NumberFormatException(""); - simNumSamplesGiven = true; - } catch (NumberFormatException e) { - errorAndExit("Invalid value for -" + sw + " switch"); - } - } else { - errorAndExit("No value specified for -" + sw + " switch"); - } - } - // simulation confidence parameter - else if (sw.equals("simconf")) { - if (i < args.length - 1) { - try { - simConfidence = Double.parseDouble(args[++i]); - if (simConfidence <= 0 || simConfidence >= 1) - throw new NumberFormatException(""); - simConfidenceGiven = true; - } catch (NumberFormatException e) { - errorAndExit("Invalid value for -" + sw + " switch"); - } - } else { - errorAndExit("No value specified for -" + sw + " switch"); - } - } - // simulation confidence interval width - else if (sw.equals("simwidth")) { - if (i < args.length - 1) { - try { - simWidth = Double.parseDouble(args[++i]); - if (simWidth <= 0) - throw new NumberFormatException(""); - simWidthGiven = true; - } catch (NumberFormatException e) { - errorAndExit("Invalid value for -" + sw + " switch"); - } - } else { - errorAndExit("No value specified for -" + sw + " switch"); - } - } - // simulation approximation parameter - else if (sw.equals("simapprox")) { - if (i < args.length - 1) { - try { - simApprox = Double.parseDouble(args[++i]); - if (simApprox <= 0) - throw new NumberFormatException(""); - simApproxGiven = true; - } catch (NumberFormatException e) { - errorAndExit("Invalid value for -" + sw + " switch"); - } - } else { - errorAndExit("No value specified for -" + sw + " switch"); - } - } - // use the number of iterations given instead of automatically deciding whether the variance is null ot not - else if (sw.equals("simmanual")) { - simManual = true; - } - // simulation number of samples to conclude S^2=0 or not - else if (sw.equals("simvar")) { - if (i < args.length - 1) { - try { - reqIterToConclude = Integer.parseInt(args[++i]); - if (reqIterToConclude <= 0) - throw new NumberFormatException(""); - reqIterToConcludeGiven = true; - } catch (NumberFormatException e) { - errorAndExit("Invalid value for -" + sw + " switch"); - } - } else { - errorAndExit("No value specified for -" + sw + " switch"); - } - } - // maximum value of reward - else if (sw.equals("simmaxrwd")) { - if (i < args.length - 1) { - try { - simMaxReward = Double.parseDouble(args[++i]); - if (simMaxReward <= 0.0) - throw new NumberFormatException(""); - simMaxRewardGiven = true; - } catch (NumberFormatException e) { - errorAndExit("Invalid value for -" + sw + " switch"); - } - } else { - errorAndExit("No value specified for -" + sw + " switch"); - } - } - // simulation max path length - else if (sw.equals("simpathlen")) { - if (i < args.length - 1) { - try { - simMaxPath = Long.parseLong(args[++i]); - if (simMaxPath <= 0) - throw new NumberFormatException(""); - simMaxPathGiven = true; - } catch (NumberFormatException e) { - errorAndExit("Invalid value for -" + sw + " switch"); - } - } else { - errorAndExit("No value specified for -" + sw + " switch"); - } + // Help / version / meta + addSwitch("help", "?", (sw, a) -> { + // -help [switch] is one of the few cases where the value arg is optional + if (a.hasNext()) printHelpSwitch(a.next(sw)); else printHelp(); + exit(); + }); + addSwitch("javamaxmem", + (sw, a) -> a.next(sw)); // consumed before JVM launch; value discarded + addSwitch("javastack", + (sw, a) -> a.next(sw)); // consumed before JVM launch; value discarded + addSwitch("javaparams", + (sw, a) -> a.next(sw)); // consumed before JVM launch; value discarded + addSwitch("timeout", (sw, a) -> { + int t = PrismUtils.convertTimeStringtoSeconds(a.next(sw)); + if (t < 0) errorAndExit("Negative timeout value \"" + t + "\" for -" + sw + " switch"); + if (t > 0) setTimeout(t); + // t == 0 -> no timeout + }); + addSwitch("version", new FlagSwitch(() -> { printVersion(); exit(); })); + addSwitch("dir", new StringSwitch(Prism::setWorkingDirectory)); + addSwitch("settings", new StringSwitch(s -> settingsFilename = s.trim())); + addSwitch("keywords", new FlagSwitch(() -> { printListOfKeywords(); exit(); })); // hidden + + // Property / constant / parameter info + addSwitch("pf", "pctl", "csl", new StringSwitch(s -> propertyString = s)); + addSwitch("prop", "property", (sw, a) -> { + String[] props = a.next(sw).trim().split(","); + propertyIndices = new ArrayList<>(); + for (String p : props) { + if (!p.isEmpty()) { + try { propertyIndices.add(Integer.parseInt(p)); } + catch (NumberFormatException e) { propertyIndices.add(p); } } + } + }); + addSwitch("const", (sw, a) -> { + String v = a.next(sw).trim(); + if ("".equals(constSwitch)) constSwitch = v; else constSwitch += "," + v; + }); + addSwitch("param", (sw, a) -> { + param = true; + String v = a.next(sw).trim(); + if ("".equals(paramSwitch)) paramSwitch = v; else paramSwitch += "," + v; + }); - // FURTHER OPTIONS - NEED TIDYING/FIXING - - // zero-reward loops check on - else if (sw.equals("zerorewardcheck")) { - prism.setCheckZeroLoops(true); - } - // explicit-state model construction - else if (sw.equals("explicitbuild")) { - explicitbuild = true; - } - // (hidden) option for testing of prototypical explicit-state model construction - else if (sw.equals("explicitbuildtest")) { - explicitbuildtest = true; - } + // Computation modes + addSwitch("steadystate", "ss", new FlagSwitch(() -> steadystate = true)); + addSwitch("transient", "tr", (sw, a) -> { dotransient = true; transientTime = a.next(sw); }); + addSwitch("simpath", (sw, a) -> { + simpath = true; + simpathDetails = a.next(sw); + simpathFilename = a.next(sw); + }); + addSwitch("nobuild", new FlagSwitch(() -> nobuild = true)); + addSwitch("test", new FlagSwitch(() -> test = true)); + addSwitch("testall", new FlagSwitch(() -> { test = true; testExitsOnFail = false; })); + addSwitch("test:umb", new FlagSwitch(() -> prism.setTestUMB(true))); + + // DD debugging (hidden) + addSwitch("dddebug", new FlagSwitch(() -> jdd.DebugJDD.enable())); + addSwitch("ddtraceall", new FlagSwitch(() -> jdd.DebugJDD.traceAll = true)); + addSwitch("ddtracefollowcopies", new FlagSwitch(() -> jdd.DebugJDD.traceFollowCopies = true)); + addSwitch("dddebugwarnfatal", new FlagSwitch(() -> jdd.DebugJDD.warningsAreFatal = true)); + addSwitch("dddebugwarnoff", new FlagSwitch(() -> jdd.DebugJDD.warningsOff = true)); + addSwitch("ddtrace", (sw, a) -> { + String idStr = a.next(sw); + try { + jdd.DebugJDD.enableTracingForID(Integer.parseInt(idStr)); + } catch (NumberFormatException e) { + errorAndExit("The -" + sw + " switch requires an integer argument (JDDNode ID)"); + } + }); - // MISCELLANEOUS UNDOCUMENTED/UNUSED OPTIONS: + // IMPORT OPTIONS: + addSwitch("importpepa", new FlagSwitch(() -> importpepa = true)); + addSwitch("importprismpp", (sw, a) -> { importprismpp = true; prismppParams = a.next(sw); }); // hidden + addSwitch("importmodel", new StringSwitch(this::processImportModelSwitch)); + addSwitch("importtrans", (sw, a) -> { + // Recall model name in case needed as basename for model exports + modelFilename = a.next(sw); + modelImportSources.add(new ModelImportSource(ModelExportTask.ModelExportEntity.MODEL, ModelExportFormat.EXPLICIT, new File(modelFilename))); + }); + addSwitch("importstates", new StringSwitch(s -> modelImportSources.add( + new ModelImportSource(ModelExportTask.ModelExportEntity.STATES, ModelExportFormat.EXPLICIT, new File(s))))); + addSwitch("importobs", new StringSwitch(s -> modelImportSources.add( + new ModelImportSource(ModelExportTask.ModelExportEntity.OBSERVATIONS, ModelExportFormat.EXPLICIT, new File(s))))); + addSwitch("importlabels", new StringSwitch(s -> modelImportSources.add( + new ModelImportSource(ModelExportTask.ModelExportEntity.LABELS, ModelExportFormat.EXPLICIT, new File(s))))); + addSwitch("importstaterewards", new StringSwitch(s -> modelImportSources.add( + new ModelImportSource(ModelExportTask.ModelExportEntity.STATE_REWARDS, ModelExportFormat.EXPLICIT, new File(s))))); + addSwitch("importtransrewards", new StringSwitch(s -> modelImportSources.add( + new ModelImportSource(ModelExportTask.ModelExportEntity.TRANSITION_REWARDS, ModelExportFormat.EXPLICIT, new File(s))))); + addSwitch("importinitdist", (sw, a) -> { importinitdist = true; importInitDistFilename = a.next(sw); }); + addSwitch("importresults", (sw, a) -> { + importresults = true; + modelFilename = "no-model-file.prism"; + importResultsFilename = a.next(sw); + }); + addSwitch("dtmc", new FlagSwitch(() -> typeOverride = ModelType.DTMC)); + addSwitch("mdp", new FlagSwitch(() -> typeOverride = ModelType.MDP)); + addSwitch("ctmc", new FlagSwitch(() -> typeOverride = ModelType.CTMC)); + + // EXPORT OPTIONS: + addSwitch("exportprism", (sw, a) -> { exportprism = true; exportPrismFilename = a.next(sw); }); + addSwitch("exportprismconst", (sw, a) -> { exportprismconst = true; exportPrismConstFilename = a.next(sw); }); + addSwitch("exportresults", new StringSwitch(this::processExportResultsSwitch)); + addSwitch("exportvector", (sw, a) -> { + exportvector = true; exportVectorFilename = a.next(sw); prism.setStoreVector(true); + }); + addSwitch("exportmodel", new StringSwitch(this::processExportModelSwitch)); + // process -exportmodelprecision in PrismSettings + addSwitch("exporttrans", new StringSwitch(s -> modelExportTasks.add( + new ModelExportTask(ModelExportTask.ModelExportEntity.MODEL, s)))); + addSwitch("exportstaterewards", new StringSwitch(s -> modelExportTasks.add( + new ModelExportTask(ModelExportTask.ModelExportEntity.STATE_REWARDS, s)))); + addSwitch("exporttransrewards", new StringSwitch(s -> modelExportTasks.add( + new ModelExportTask(ModelExportTask.ModelExportEntity.TRANSITION_REWARDS, s)))); + addSwitch("exportrewards", (sw, a) -> { + modelExportTasks.add(new ModelExportTask(ModelExportTask.ModelExportEntity.STATE_REWARDS, a.next(sw))); + modelExportTasks.add(new ModelExportTask(ModelExportTask.ModelExportEntity.TRANSITION_REWARDS, a.next(sw))); + }); + addSwitch("exportstates", new StringSwitch(s -> modelExportTasks.add( + new ModelExportTask(ModelExportTask.ModelExportEntity.STATES, s)))); + addSwitch("exportobs", new StringSwitch(s -> modelExportTasks.add( + new ModelExportTask(ModelExportTask.ModelExportEntity.OBSERVATIONS, s)))); + addSwitch("exportlabels", new StringSwitch(this::processExportLabelsSwitch)); + addSwitch("exportproplabels", new StringSwitch(this::processExportPropLabelsSwitch)); + addSwitch("exportmatlab", new FlagSwitch(() -> { + exportType = Prism.EXPORT_MATLAB; + modelExportOptionsGlobal.setFormat(ModelExportFormat.MATLAB); + })); + addSwitch("exportmrmc", new FlagSwitch(() -> errorAndExit("Export to MRMC format no longer supported"))); + addSwitch("exportrows", new FlagSwitch(() -> { + exportType = Prism.EXPORT_ROWS; + modelExportOptionsGlobal.setExplicitRows(true); + })); + addSwitch("exportordered", "ordered", new FlagSwitch(() -> {})); // always done now, no-op + addSwitch("exportunordered", "unordered", new FlagSwitch(() -> errorAndExit("Switch -exportunordered is no longer supported"))); + addSwitch("exporttransdot", new StringSwitch(s -> { + ModelExportOptions exportOptions = new ModelExportOptions().setFormat(ModelExportFormat.DOT).setShowStates(false).setShowObservations(false); + modelExportTasks.add(new ModelExportTask(ModelExportTask.ModelExportEntity.MODEL, s, exportOptions)); + })); + addSwitch("exporttransdotstates", new StringSwitch(s -> { + ModelExportOptions exportOptions = new ModelExportOptions().setFormat(ModelExportFormat.DOT).setShowStates(true).setShowObservations(true); + modelExportTasks.add(new ModelExportTask(ModelExportTask.ModelExportEntity.MODEL, s, exportOptions)); + })); + addSwitch("exportdot", new StringSwitch(s -> { + ModelExportOptions exportOptions = new ModelExportOptions().setFormat(ModelExportFormat.DD_DOT); + modelExportTasks.add(new ModelExportTask(ModelExportTask.ModelExportEntity.MODEL, s, exportOptions)); + })); + addSwitch("exportsccs", (sw, a) -> { exportsccs = true; exportSCCsFilename = a.next(sw); }); + addSwitch("exportbsccs", (sw, a) -> { exportbsccs = true; exportBSCCsFilename = a.next(sw); }); + addSwitch("exportmecs", (sw, a) -> { exportmecs = true; exportMECsFilename = a.next(sw); }); + addSwitch("exportsteadystate", "exportss", (sw, a) -> { + exportSteadyStateFilename = a.next(sw); + steadystate = true; // compute if asked to export + }); + addSwitch("exporttransient", "exporttr", new StringSwitch(s -> exportTransientFilename = s)); + addSwitch("exportstrat", new StringSwitch(this::processExportStratSwitch)); + addSwitch("exportdigital", new StringSwitch(s -> { + File f = s.equals("stdout") ? null : new File(s); + prism.setExportDigital(true); + prism.setExportDigitalFile(f); + })); + addSwitch("exporttarget", new StringSwitch(s -> { // hidden + prism.setExportTarget(true); prism.setExportTargetFilename(s); + })); + addSwitch("exportprodtrans", new StringSwitch(s -> { // hidden + prism.setExportProductTrans(true); prism.setExportProductTransFilename(s); + })); + addSwitch("exportprodstates", new StringSwitch(s -> { // hidden + prism.setExportProductStates(true); prism.setExportProductStatesFilename(s); + })); + addSwitch("exportprodvector", new StringSwitch(s -> { // hidden + prism.setExportProductVector(true); prism.setExportProductVectorFilename(s); + })); + + // NB: Following the ordering of the -help text, more options go here, + // but these are processed in the PrismSettings class; see below. + + // SIMULATION OPTIONS: + addSwitch("sim", new FlagSwitch(() -> simulate = true)); + addSwitch("simmethod", new EnumSwitch() + .when("ci", () -> simMethodName = "ci") + .when("aci", () -> simMethodName = "aci") + .when("apmc", () -> simMethodName = "apmc") + .when("sprt", () -> simMethodName = "sprt")); + addSwitch("simsamples", (sw, a) -> { + int n = a.nextInt(sw); + if (n <= 0) errorAndExit("Invalid value for -" + sw + " switch"); + simNumSamples = n; simNumSamplesGiven = true; + }); + addSwitch("simconf", (sw, a) -> { + double d = a.nextDouble(sw); + if (d <= 0 || d >= 1) errorAndExit("Invalid value for -" + sw + " switch"); + simConfidence = d; simConfidenceGiven = true; + }); + addSwitch("simwidth", (sw, a) -> { + double d = a.nextDouble(sw); + if (d <= 0) errorAndExit("Invalid value for -" + sw + " switch"); + simWidth = d; simWidthGiven = true; + }); + addSwitch("simapprox", (sw, a) -> { + double d = a.nextDouble(sw); + if (d <= 0) errorAndExit("Invalid value for -" + sw + " switch"); + simApprox = d; simApproxGiven = true; + }); + addSwitch("simmanual", new FlagSwitch(() -> simManual = true)); + addSwitch("simvar", (sw, a) -> { + int n = a.nextInt(sw); + if (n <= 0) errorAndExit("Invalid value for -" + sw + " switch"); + reqIterToConclude = n; reqIterToConcludeGiven = true; + }); + addSwitch("simmaxrwd", (sw, a) -> { + double d = a.nextDouble(sw); + if (d <= 0.0) errorAndExit("Invalid value for -" + sw + " switch"); + simMaxReward = d; simMaxRewardGiven = true; + }); + addSwitch("simpathlen", (sw, a) -> { + long n = a.nextLong(sw); + if (n <= 0) errorAndExit("Invalid value for -" + sw + " switch"); + simMaxPath = n; simMaxPathGiven = true; + }); - // specify main log (hidden option) - else if (sw.equals("mainlog")) { - if (i < args.length - 1) { - processMainLogSwitch(args[++i]); - } else { - errorAndExit("No file specified for -" + sw + " switch"); - } - } - // export transition matrix graph to dot file and view it (hidden option, for now) - else if (sw.equals("exportmodeldotview")) { - exportmodeldotview = true; - } - // mtbdd construction method (hidden option) - else if (sw.equals("c1")) { - prism.setConstruction(1); - } else if (sw.equals("c2")) { - prism.setConstruction(2); - } else if (sw.equals("c3")) { - prism.setConstruction(3); - } - // mtbdd variable ordering (hidden option) - else if (sw.equals("o1")) { - prism.setOrdering(1); - orderingOverride = true; - } else if (sw.equals("o2")) { - } else if (sw.equals("o2")) { - prism.setOrdering(2); - orderingOverride = true; - } else if (sw.equals("o2")) { - } - // reachability off (hidden option) - else if (sw.equals("noreach")) { - prism.setDoReach(false); - } - // no bscc computation (hidden option) - else if (sw.equals("nobscc")) { - prism.setBSCCComp(false); - } - // reachability options (hidden options) - else if (sw.equals("frontier")) { - prism.setReachMethod(Prism.REACH_FRONTIER); - } else if (sw.equals("bfs")) { - prism.setReachMethod(Prism.REACH_BFS); - } - // enable bisimulation minimisation before model checking (hidden option) - else if (sw.equals("bisim")) { - prism.setDoBisim(true); - } + // FURTHER OPTIONS: + addSwitch("zerorewardcheck", new FlagSwitch(() -> prism.setCheckZeroLoops(true))); + addSwitch("explicitbuild", new FlagSwitch(() -> explicitbuild = true)); + addSwitch("explicitbuildtest", new FlagSwitch(() -> explicitbuildtest = true)); // hidden + + // MISCELLANEOUS HIDDEN OPTIONS: + addSwitch("mainlog", new StringSwitch(this::processMainLogSwitch)); + addSwitch("exportmodeldotview", new FlagSwitch(() -> exportmodeldotview = true)); + addSwitch("c1", new FlagSwitch(() -> prism.setConstruction(1))); + addSwitch("c2", new FlagSwitch(() -> prism.setConstruction(2))); + addSwitch("c3", new FlagSwitch(() -> prism.setConstruction(3))); + addSwitch("o1", new FlagSwitch(() -> { prism.setOrdering(1); orderingOverride = true; })); + addSwitch("o2", new FlagSwitch(() -> { prism.setOrdering(2); orderingOverride = true; })); + addSwitch("noreach", new FlagSwitch(() -> prism.setDoReach(false))); + addSwitch("nobscc", new FlagSwitch(() -> prism.setBSCCComp(false))); + addSwitch("frontier", new FlagSwitch(() -> prism.setReachMethod(Prism.REACH_FRONTIER))); + addSwitch("bfs", new FlagSwitch(() -> prism.setReachMethod(Prism.REACH_BFS))); + addSwitch("bisim", new FlagSwitch(() -> prism.setDoBisim(true))); + } - // Other switches - pass to PrismSettings + /** Register a switch handler under a single name. */ + private void addSwitch(String name, SwitchHandler h) + { + switchHandlers.put(name, h); + } - else { - i = prism.getSettings().setFromCommandLineSwitch(args, i) - 1; - } - } - // otherwise argument is assumed to be a (model/properties) filename - else { - filenameArgs.add(args[i]); - } - } + /** Register a switch handler under two names (primary + alias). */ + private void addSwitch(String n1, String n2, SwitchHandler h) + { + switchHandlers.put(n1, h); + switchHandlers.put(n2, h); + } - processFileNames(filenameArgs); + /** Register a switch handler under three names (primary + two aliases). */ + private void addSwitch(String n1, String n2, String n3, SwitchHandler h) + { + switchHandlers.put(n1, h); + switchHandlers.put(n2, h); + switchHandlers.put(n3, h); } /** @@ -1901,6 +1381,31 @@ private void processFileNames(List filenameArgs) throws PrismException } } + /** Process the argument to the -exportresults switch. */ + private void processExportResultsSwitch(String filesOptionsString) throws PrismException + { + exportresults = true; + // Split into filename/options; also accept , as a legacy separator if : is absent + String[] halves = splitFilesAndOptions(filesOptionsString); + if (halves[1].isEmpty() && halves[0].indexOf(',') > -1) { + int comma = halves[0].indexOf(','); + halves[1] = halves[0].substring(comma + 1); + halves[0] = halves[0].substring(0, comma); + } + exportResultsFilename = halves[0]; + exportShape = ResultsExportShape.LIST_PLAIN; + boolean[] isCsv = {false}, isMatrix = {false}; + new OptionParser() + .flag("csv", () -> isCsv[0] = true) + .flag("matrix", () -> isMatrix[0] = true) + .flag("dataframe", () -> exportShape = ResultsExportShape.DATA_FRAME) + .flag("comment", () -> exportShape = ResultsExportShape.COMMENT) + .parse(halves[1], "exportresults"); + if (exportShape == ResultsExportShape.LIST_PLAIN) + exportShape = isCsv[0] ? (isMatrix[0] ? ResultsExportShape.MATRIX_CSV : ResultsExportShape.LIST_CSV) + : (isMatrix[0] ? ResultsExportShape.MATRIX_PLAIN : ResultsExportShape.LIST_PLAIN); + } + /** * Process the arguments (files, options) to the -importmodel switch * NB: This is done at the time of parsing switches (not later) @@ -1954,38 +1459,11 @@ private void processImportModelSwitch(String filesOptionsString) throws PrismExc } } // Process options - String options[] = optionsString.split(","); - for (String opt : options) { - // Ignore "" - if (opt.equals("")) { - } - // Import format - else if (opt.startsWith("format")) { - if (!opt.startsWith("format=")) { - throw new PrismException("No value provided for \"format\" option of -importmodel"); - } - String optVal = opt.substring(7); - ModelExportFormat importFormat = null; - switch (optVal) { - case "explicit": - importFormat = ModelExportFormat.EXPLICIT; - break; - case "umb": - importFormat = ModelExportFormat.UMB; - break; - default: - throw new PrismException("Unknown value \"" + optVal + "\" provided for \"format\" option of -importmodel"); - } - // Apply format to the import sources - for (ModelImportSource source : modelImportSources) { - source.format = importFormat; - } - } - // Unknown option - else { - throw new PrismException("Unknown option \"" + opt + "\" for -importmodel switch"); - } - } + new OptionParser() + .choice("format", new OptionParser.Choice() + .when("explicit", () -> { for (ModelImportSource s : modelImportSources) s.format = ModelExportFormat.EXPLICIT; }) + .when("umb", () -> { for (ModelImportSource s : modelImportSources) s.format = ModelExportFormat.UMB; })) + .parse(optionsString, "importmodel"); } /** @@ -2073,22 +1551,10 @@ private void processExportLabelsSwitch(String filesOptionsString) throws PrismEx // Split into files/options (on :) String pair[] = splitFilesAndOptions(filesOptionsString); ModelExportTask newExportTask = new ModelExportTask(ModelExportTask.ModelExportEntity.LABELS, pair[0]); - String options[] = pair[1].split(","); - for (String opt : options) { - // Ignore "" - if (opt.equals("")) { - } - // Export type - else if (opt.equals("matlab")) { - newExportTask.getExportOptions().setFormat(ModelExportFormat.MATLAB); - } else if (opt.equals("proplabels")) { - newExportTask.setLabelExportSet(ModelExportTask.LabelExportSet.ALL); - } - // Unknown option - else { - throw new PrismException("Unknown option \"" + opt + "\" for -exportlabels switch"); - } - } + new OptionParser() + .flag("matlab", () -> newExportTask.getExportOptions().setFormat(ModelExportFormat.MATLAB)) + .flag("proplabels", () -> newExportTask.setLabelExportSet(ModelExportTask.LabelExportSet.ALL)) + .parse(pair[1], "exportlabels"); modelExportTasks.add(newExportTask); } @@ -2101,20 +1567,9 @@ private void processExportPropLabelsSwitch(String filesOptionsString) throws Pri String pair[] = splitFilesAndOptions(filesOptionsString); ModelExportTask newExportTask = new ModelExportTask(ModelExportTask.ModelExportEntity.LABELS, pair[0]); newExportTask.setLabelExportSet(ModelExportTask.LabelExportSet.EXTRA); - String options[] = pair[1].split(","); - for (String opt : options) { - // Ignore "" - if (opt.equals("")) { - } - // Export type - else if (opt.equals("matlab")) { - newExportTask.getExportOptions().setFormat(ModelExportFormat.MATLAB); - } - // Unknown option - else { - throw new PrismException("Unknown option \"" + opt + "\" for -exportproplabels switch"); - } - } + new OptionParser() + .flag("matlab", () -> newExportTask.getExportOptions().setFormat(ModelExportFormat.MATLAB)) + .parse(pair[1], "exportproplabels"); modelExportTasks.add(newExportTask); } @@ -2157,161 +1612,42 @@ private void processExportModelSwitch(String filesOptionsString) throws PrismExc } // Process options ModelExportOptions exportOptions = new ModelExportOptions(); - String options[] = optionsString.split(","); - for (String opt : options) { - String sOpt; - // Ignore "" - if (opt.equals("")) { - } - // Export format - else if (opt.startsWith("format")) { - if (!opt.startsWith("format=")) { - throw new PrismException("No value provided for \"format\" option of -exportmodel"); - } - String optVal = opt.substring(7); - switch (optVal) { - case "explicit": - exportOptions.setFormat(ModelExportFormat.EXPLICIT); - break; - case "matlab": - exportOptions.setFormat(ModelExportFormat.MATLAB); - break; - case "dot": - exportOptions.setFormat(ModelExportFormat.DOT); - break; - case "drn": - exportOptions.setFormat(ModelExportFormat.DRN); - break; - case "umb": - exportOptions.setFormat(ModelExportFormat.UMB); - break; - default: - throw new PrismException("Unknown value \"" + optVal + "\" provided for \"format\" option of -exportmodel"); - } - } - // Export type - else if (opt.equals("matlab")) { - exportOptions.setFormat(ModelExportFormat.MATLAB); - exportType = Prism.EXPORT_MATLAB; - } else if (opt.equals("rows")) { - exportOptions.setExplicitRows(true); - exportType = Prism.EXPORT_ROWS; - } else if (opt.equals("text")) { - exportOptions.setBinaryAsText(true); - } - else if (opt.equals("proplabels")) { - for (ModelExportTask exportTask : newModelExportTasks) { - if (exportTask.getEntity() == ModelExportTask.ModelExportEntity.LABELS) { - exportTask.setLabelExportSet(ModelExportTask.LabelExportSet.ALL); - } - } - } - else if (opt.startsWith(sOpt = "labels")) { - if (!opt.startsWith(sOpt + "=")) - throw new PrismException("No value provided for \"" + sOpt + "\" option of -exportmodel"); - String optVal = opt.substring(sOpt.length() + 1); - if (optVal.equals("true")) - exportOptions.setShowLabels(true); - else if (optVal.equals("false")) - exportOptions.setShowLabels(false); - else - throw new PrismException("Unknown value \"" + optVal + "\" provided for \"" + sOpt + "\" option of -exportmodel"); - } - else if (opt.startsWith(sOpt = "rewards")) { - if (!opt.startsWith(sOpt + "=")) - throw new PrismException("No value provided for \"" + sOpt + "\" option of -exportmodel"); - String optVal = opt.substring(sOpt.length() + 1); - if (optVal.equals("true")) - exportOptions.setShowRewards(true); - else if (optVal.equals("false")) - exportOptions.setShowRewards(false); - else - throw new PrismException("Unknown value \"" + optVal + "\" provided for \"" + sOpt + "\" option of -exportmodel"); - } - else if (opt.startsWith(sOpt = "states")) { - if (!opt.startsWith(sOpt + "=")) - throw new PrismException("No value provided for \"" + sOpt + "\" option of -exportmodel"); - String optVal = opt.substring(sOpt.length() + 1); - if (optVal.equals("true")) - exportOptions.setShowStates(true); - else if (optVal.equals("false")) - exportOptions.setShowStates(false); - else - throw new PrismException("Unknown value \"" + optVal + "\" provided for \"" + sOpt + "\" option of -exportmodel"); - } - else if (opt.startsWith(sOpt = "obs")) { - if (!opt.startsWith(sOpt + "=")) - throw new PrismException("No value provided for \"" + sOpt + "\" option of -exportmodel"); - String optVal = opt.substring(sOpt.length() + 1); - if (optVal.equals("true")) - exportOptions.setShowObservations(true); - else if (optVal.equals("false")) - exportOptions.setShowObservations(false); - else - throw new PrismException("Unknown value \"" + optVal + "\" provided for \"" + sOpt + "\" option of -exportmodel"); - } - else if (opt.startsWith(sOpt = "actions")) { - if (!opt.startsWith(sOpt + "=")) - throw new PrismException("No value provided for \"" + sOpt + "\" option of -exportmodel"); - String optVal = opt.substring(sOpt.length() + 1); - if (optVal.equals("true")) { - exportOptions.setShowActions(true); - } else if (optVal.equals("false")) { - exportOptions.setShowActions(false); - } - else { - throw new PrismException("Unknown value \"" + optVal + "\" provided for \"" + sOpt + "\" option of -exportmodel"); - } - } - else if (opt.startsWith(sOpt = "headers")) { - if (!opt.startsWith(sOpt + "=")) - throw new PrismException("No value provided for \"" + sOpt + "\" option of -exportmodel"); - String optVal = opt.substring(sOpt.length() + 1); - if (optVal.equals("true")) { - exportOptions.setPrintHeaders(true); - } else if (optVal.equals("false")) { - exportOptions.setPrintHeaders(false); - } - else { - throw new PrismException("Unknown value \"" + optVal + "\" provided for \"" + sOpt + "\" option of -exportmodel"); - } - } - else if (opt.startsWith(sOpt = "precision")) { - if (!opt.startsWith(sOpt + "=")) - throw new PrismException("No value provided for \"" + sOpt + "\" option of -exportmodel"); - String optVal = opt.substring(sOpt.length() + 1); + new OptionParser() + .flag("matlab", () -> { exportOptions.setFormat(ModelExportFormat.MATLAB); exportType = Prism.EXPORT_MATLAB; }) + .flag("rows", () -> { exportOptions.setExplicitRows(true); exportType = Prism.EXPORT_ROWS; }) + .flag("text", () -> exportOptions.setBinaryAsText(true)) + .flag("proplabels", () -> { + for (ModelExportTask t : newModelExportTasks) + if (t.getEntity() == ModelExportTask.ModelExportEntity.LABELS) + t.setLabelExportSet(ModelExportTask.LabelExportSet.ALL); + }) + .choice("format", new OptionParser.Choice() + .when("explicit", () -> exportOptions.setFormat(ModelExportFormat.EXPLICIT)) + .when("matlab", () -> exportOptions.setFormat(ModelExportFormat.MATLAB)) + .when("dot", () -> exportOptions.setFormat(ModelExportFormat.DOT)) + .when("drn", () -> exportOptions.setFormat(ModelExportFormat.DRN)) + .when("umb", () -> exportOptions.setFormat(ModelExportFormat.UMB))) + .bool("labels", v -> exportOptions.setShowLabels(v)) + .bool("rewards", v -> exportOptions.setShowRewards(v)) + .bool("states", v -> exportOptions.setShowStates(v)) + .bool("obs", v -> exportOptions.setShowObservations(v)) + .bool("actions", v -> exportOptions.setShowActions(v)) + .bool("headers", v -> exportOptions.setPrintHeaders(v)) + .string("precision", v -> { try { - int precision = Integer.parseInt(optVal); - if (!RANGE_EXPORT_DOUBLE_PRECISION.contains(precision)) { - throw new NumberFormatException(""); - } - exportOptions.setModelPrecision(precision); + int n = Integer.parseInt(v); + if (!RANGE_EXPORT_DOUBLE_PRECISION.contains(n)) throw new NumberFormatException(); + exportOptions.setModelPrecision(n); } catch (NumberFormatException e) { - throw new PrismException("Invalid value \"" + optVal + "\" provided for \"" + sOpt + "\" option of -exportmodel"); - } - } - else if (opt.startsWith(sOpt = "zip")) { - if (!opt.startsWith(sOpt + "=")) - throw new PrismException("No value provided for \"" + sOpt + "\" option of -exportmodel"); - String optVal = opt.substring(sOpt.length() + 1); - if (optVal.equals("true")) { - exportOptions.setZipped(true); - } else if (optVal.equals("false")) { - exportOptions.setZipped(false); - } else if (optVal.equals("gzip") || optVal.equals("gz")) { - exportOptions.setZipped(true).setCompressionFormat(ModelExportOptions.CompressionFormat.GZIP); - } else if (optVal.equals("xz")) { - exportOptions.setZipped(true).setCompressionFormat(ModelExportOptions.CompressionFormat.XZ); - } - else { - throw new PrismException("Unknown value \"" + optVal + "\" provided for \"" + sOpt + "\" option of -exportmodel"); - } - } - // Unknown option - else { - throw new PrismException("Unknown option \"" + opt + "\" for -exportmodel switch"); - } - } + throw new PrismException("Invalid value \"" + v + "\" for \"precision\" option of -exportmodel"); + } + }) + .choice("zip", new OptionParser.Choice() + .when("true", () -> exportOptions.setZipped(true)) + .when("false", () -> exportOptions.setZipped(false)) + .when("gzip", "gz", () -> exportOptions.setZipped(true).setCompressionFormat(ModelExportOptions.CompressionFormat.GZIP)) + .when("xz", () -> exportOptions.setZipped(true).setCompressionFormat(ModelExportOptions.CompressionFormat.XZ))) + .parse(optionsString, "exportmodel"); // Apply options from this switch to each export task for (ModelExportTask exportTask : newModelExportTasks) { exportTask.getExportOptions().apply(exportOptions); @@ -2347,75 +1683,19 @@ private void processExportStratSwitch(String filesOptionsString) throws PrismExc exportStratOptions.setType(StrategyExportOptions.StrategyExportType.ACTIONS); } // Process options - String options[] = optionsString.split(","); - for (String opt : options) { - // Ignore "" - if (opt.equals("")) { - } - else if (opt.startsWith("type")) { - if (!opt.startsWith("type=")) - throw new PrismException("No value provided for \"type\" option of -exportstrat"); - String optVal = opt.substring(5); - if (optVal.equals("actions")) - exportStratOptions.setType(StrategyExportOptions.StrategyExportType.ACTIONS); - else if (optVal.equals("indices")) - exportStratOptions.setType(StrategyExportOptions.StrategyExportType.INDICES); - else if (optVal.equals("model") || optVal.equals("induced")) - exportStratOptions.setType(StrategyExportOptions.StrategyExportType.INDUCED_MODEL); - else if (optVal.equals("dot")) - exportStratOptions.setType(StrategyExportOptions.StrategyExportType.DOT_FILE); - else - throw new PrismException("Unknown value \"" + optVal + "\" provided for \"type\" option of -exportstrat"); - } - else if (opt.startsWith("mode")) { - if (!opt.startsWith("mode=")) - throw new PrismException("No value provided for \"mode\" option of -exportstrat"); - String optVal = opt.substring(5); - if (optVal.equals("restrict")) - exportStratOptions.setMode(StrategyExportOptions.InducedModelMode.RESTRICT); - else if (optVal.equals("reduce")) - exportStratOptions.setMode(StrategyExportOptions.InducedModelMode.REDUCE); - else - throw new PrismException("Unknown value \"" + optVal + "\" provided for \"mode\" option of -exportstrat"); - } - else if (opt.startsWith("reach")) { - if (!opt.startsWith("reach=")) - throw new PrismException("No value provided for \"reach\" option of -exportstrat"); - String optVal = opt.substring(6); - if (optVal.equals("true")) - exportStratOptions.setReachOnly(true); - else if (optVal.equals("false")) - exportStratOptions.setReachOnly(false); - else - throw new PrismException("Unknown value \"" + optVal + "\" provided for \"reach\" option of -exportstrat"); - } - else if (opt.startsWith("states")) { - if (!opt.startsWith("states=")) - throw new PrismException("No value provided for \"states\" option of -exportstrat"); - String optVal = opt.substring(7); - if (optVal.equals("true")) - exportStratOptions.setShowStates(true); - else if (optVal.equals("false")) - exportStratOptions.setShowStates(false); - else - throw new PrismException("Unknown value \"" + optVal + "\" provided for \"reach\" option of -exportstrat"); - } - else if (opt.startsWith("obs")) { - if (!opt.startsWith("obs=")) - throw new PrismException("No value provided for \"obs\" option of -exportstrat"); - String optVal = opt.substring(4); - if (optVal.equals("true")) - exportStratOptions.setMergeObservations(true); - else if (optVal.equals("false")) - exportStratOptions.setMergeObservations(false); - else - throw new PrismException("Unknown value \"" + optVal + "\" provided for \"reach\" option of -exportstrat"); - } - // Unknown option - else { - throw new PrismException("Unknown option \"" + opt + "\" for -exportstrat switch"); - } - } + new OptionParser() + .choice("type", new OptionParser.Choice() + .when("actions", () -> exportStratOptions.setType(StrategyExportOptions.StrategyExportType.ACTIONS)) + .when("indices", () -> exportStratOptions.setType(StrategyExportOptions.StrategyExportType.INDICES)) + .when("model", "induced", () -> exportStratOptions.setType(StrategyExportOptions.StrategyExportType.INDUCED_MODEL)) + .when("dot", () -> exportStratOptions.setType(StrategyExportOptions.StrategyExportType.DOT_FILE))) + .choice("mode", new OptionParser.Choice() + .when("restrict", () -> exportStratOptions.setMode(StrategyExportOptions.InducedModelMode.RESTRICT)) + .when("reduce", () -> exportStratOptions.setMode(StrategyExportOptions.InducedModelMode.REDUCE))) + .bool("reach", v -> exportStratOptions.setReachOnly(v)) + .bool("states", v -> exportStratOptions.setShowStates(v)) + .bool("obs", v -> exportStratOptions.setMergeObservations(v)) + .parse(optionsString, "exportstrat"); } /** @@ -2428,19 +1708,13 @@ private void processMainLogSwitch(String filesOptionsString) throws PrismExcepti String filename = halves[0]; String optionsString = halves[1]; // Process options - boolean append = false; - for (String opt : optionsString.split(",")) { - if (opt.equals("")) { - // ignore empty - } else if (opt.equals("append")) { - append = true; - } else { - throw new PrismException("Unknown option \"" + opt + "\" for -mainlog switch"); - } - } + boolean[] append = { false }; + new OptionParser() + .flag("append", () -> append[0] = true) + .parse(optionsString, "mainlog"); // Open the log try { - mainLog = new PrismFileLog(filename, append); + mainLog = new PrismFileLog(filename, append[0]); prism.setMainLog(mainLog); } catch (PrismException e) { errorAndExit("Couldn't open log file \"" + filename + "\""); diff --git a/prism/src/prism/PrismSettings.java b/prism/src/prism/PrismSettings.java index d3f099f00f..f27f8cb947 100644 --- a/prism/src/prism/PrismSettings.java +++ b/prism/src/prism/PrismSettings.java @@ -2,10 +2,8 @@ // // Copyright (c) 2002- // Authors: -// * Dave Parker (University of Oxford, formerly University of Birmingham) -// * Andrew Hinton (University of Birmingham) -// * Vincent Nimal (University of Oxford) -// +// * Dave Parker (University of Oxford) +// //------------------------------------------------------------------------------ // // This file is part of PRISM. @@ -949,7 +947,10 @@ public synchronized void loadDefaults() protected boolean exportPropAut = false; protected String exportPropAutType = "txt"; protected String exportPropAutFilename = "da.txt"; - + + // CLI switch handler map (populated lazily by initSwitchHandlers) + private Map settingsSwitchHandlers; + public void setExportPropAut(boolean b) throws PrismException { exportPropAut = b; @@ -981,795 +982,326 @@ public String getExportPropAutFilename() } /** - * Set an option by parsing one or more command-line arguments. - * Reads the ith argument (assumed to be in the form "-switch") - * and also any subsequent arguments required as parameters. - * Return the index of the next argument to be read. - * @param args Full list of arguments - * @param i Index of first argument to read + * Handle a command-line switch by dispatching to the registered settings handler. + * @param sw Switch name (without leading {@code -}, colon sub-options already in {@code consumer}) + * @param consumer Argument consumer positioned at the switch token */ - public synchronized int setFromCommandLineSwitch(String args[], int i) throws PrismException + public synchronized void setFromCommandLineSwitch(String sw, ArgConsumer consumer) throws PrismException { - String s; - int j; - double d; - - // Process string (remove - and extract any options) - Pair pair = splitSwitch(args[i]); - String sw = pair.first; - String optionsString = pair.second; - Map options = splitOptionsString(optionsString); - - // Note: the order of these switches should match the -help output (just to help keep track of things). - - // ENGINES/METHODS: - - // Main model checking engine - if (sw.equals("mtbdd") || sw.equals("m")) { - set(PRISM_ENGINE, "MTBDD"); - } - else if (sw.equals("sparse") || sw.equals("s")) { - set(PRISM_ENGINE, "Sparse"); - } - else if (sw.equals("hybrid") || sw.equals("h")) { - set(PRISM_ENGINE, "Hybrid"); - } - else if (sw.equals("explicit") || sw.equals("ex")) { - set(PRISM_ENGINE, "Explicit"); - } - // Exact model checking - else if (sw.equals("exact")) { - set(PRISM_EXACT_ENABLED, true); - } - // PTA model checking methods - else if (sw.equals("ptamethod")) { - if (i < args.length - 1) { - s = args[++i]; - if (s.equals("digital")) - set(PRISM_PTA_METHOD, "Digital clocks"); - else if (s.equals("games")) - set(PRISM_PTA_METHOD, "Stochastic games"); - else if (s.equals("backwards") || s.equals("bw")) - set(PRISM_PTA_METHOD, "Backwards reachability"); - else - throw new PrismException("Unrecognised option for -" + sw + " switch (options are: digital, games, backwards)"); - } else { - throw new PrismException("No parameter specified for -" + sw + " switch"); - } - } - // Transient methods - else if (sw.equals("transientmethod")) { - if (i < args.length - 1) { - s = args[++i]; - if (s.equals("unif")) - set(PRISM_TRANSIENT_METHOD, "Uniformisation"); - else if (s.equals("fau")) - set(PRISM_TRANSIENT_METHOD, "Fast adaptive uniformisation"); - else - throw new PrismException("Unrecognised option for -" + sw + " switch (options are: unif, fau)"); - } else { - throw new PrismException("No parameter specified for -" + sw + " switch"); - } - } - // Heuristic modes - else if (sw.equals("heuristic")) { - if (i < args.length - 1) { - s = args[++i]; - if (s.equals("none")) - set(PRISM_HEURISTIC, "None"); - else if (s.equals("speed")) - set(PRISM_HEURISTIC, "Speed"); - else if (s.equals("memory")) - set(PRISM_HEURISTIC, "Memory"); - else - throw new PrismException("Unrecognised option for -" + sw + " switch (options are: none, speed, memory)"); - } else { - throw new PrismException("No parameter specified for -" + sw + " switch"); - } + // Lazy init (method is synchronized, so no race condition) + if (settingsSwitchHandlers == null) initSwitchHandlers(); + + SwitchHandler handler = settingsSwitchHandlers.get(sw); + if (handler != null) { + handler.handle(sw, consumer); + return; } + throw new PrismException("Invalid switch -" + sw + " (type \"prism -help\" for full list)"); + } + + /** Populate {@link #settingsSwitchHandlers} with a handler for every switch recognised here. */ + private void initSwitchHandlers() + { + settingsSwitchHandlers = new LinkedHashMap<>(); + + // ENGINES/METHODS: + addSwitch("mtbdd", "m", new FlagSwitch(() -> set(PRISM_ENGINE, "MTBDD"))); + addSwitch("sparse", "s", new FlagSwitch(() -> set(PRISM_ENGINE, "Sparse"))); + addSwitch("hybrid", "h", new FlagSwitch(() -> set(PRISM_ENGINE, "Hybrid"))); + addSwitch("explicit", "ex", new FlagSwitch(() -> set(PRISM_ENGINE, "Explicit"))); + addSwitch("exact", new FlagSwitch(() -> set(PRISM_EXACT_ENABLED, true))); + addSwitch("ptamethod", new EnumSwitch() + .when("digital", () -> set(PRISM_PTA_METHOD, "Digital clocks")) + .when("games", () -> set(PRISM_PTA_METHOD, "Stochastic games")) + .when("backwards", "bw", () -> set(PRISM_PTA_METHOD, "Backwards reachability"))); + addSwitch("transientmethod", new EnumSwitch() + .when("unif", () -> set(PRISM_TRANSIENT_METHOD, "Uniformisation")) + .when("fau", () -> set(PRISM_TRANSIENT_METHOD, "Fast adaptive uniformisation"))); + addSwitch("heuristic", new EnumSwitch() + .when("none", () -> set(PRISM_HEURISTIC, "None")) + .when("speed", () -> set(PRISM_HEURISTIC, "Speed")) + .when("memory", () -> set(PRISM_HEURISTIC, "Memory"))); + // NUMERICAL SOLUTION OPTIONS: - - // Linear equation solver + MDP soln method - else if (sw.equals("power") || sw.equals("pow") || sw.equals("pwr")) { - set(PRISM_LIN_EQ_METHOD, "Power"); - } else if (sw.equals("jacobi") || sw.equals("jac")) { - set(PRISM_LIN_EQ_METHOD, "Jacobi"); - } else if (sw.equals("gaussseidel") || sw.equals("gs")) { + addSwitch("power", "pow", "pwr", new FlagSwitch(() -> set(PRISM_LIN_EQ_METHOD, "Power"))); + addSwitch("jacobi", "jac", new FlagSwitch(() -> set(PRISM_LIN_EQ_METHOD, "Jacobi"))); + addSwitch("gaussseidel", "gs", new FlagSwitch(() -> { set(PRISM_LIN_EQ_METHOD, "Gauss-Seidel"); set(PRISM_MDP_SOLN_METHOD, "Gauss-Seidel"); set(PRISM_MDP_MULTI_SOLN_METHOD, "Gauss-Seidel"); set(PRISM_IMDP_SOLN_METHOD, "Gauss-Seidel"); - } else if (sw.equals("bgaussseidel") || sw.equals("bgs")) { - set(PRISM_LIN_EQ_METHOD, "Backwards Gauss-Seidel"); - } else if (sw.equals("pgaussseidel") || sw.equals("pgs")) { - set(PRISM_LIN_EQ_METHOD, "Pseudo-Gauss-Seidel"); - } else if (sw.equals("bpgaussseidel") || sw.equals("bpgs")) { - set(PRISM_LIN_EQ_METHOD, "Backwards Pseudo-Gauss-Seidel"); - } else if (sw.equals("jor")) { - set(PRISM_LIN_EQ_METHOD, "JOR"); - } else if (sw.equals("sor")) { - set(PRISM_LIN_EQ_METHOD, "SOR"); - } else if (sw.equals("bsor")) { - set(PRISM_LIN_EQ_METHOD, "Backwards SOR"); - } else if (sw.equals("psor")) { - set(PRISM_LIN_EQ_METHOD, "Pseudo-SOR"); - } else if (sw.equals("bpsor")) { - set(PRISM_LIN_EQ_METHOD, "Backwards Pseudo-SOR"); - } else if (sw.equals("valiter")) { + })); + addSwitch("bgaussseidel", "bgs", new FlagSwitch(() -> set(PRISM_LIN_EQ_METHOD, "Backwards Gauss-Seidel"))); + addSwitch("pgaussseidel", "pgs", new FlagSwitch(() -> set(PRISM_LIN_EQ_METHOD, "Pseudo-Gauss-Seidel"))); + addSwitch("bpgaussseidel", "bpgs", new FlagSwitch(() -> set(PRISM_LIN_EQ_METHOD, "Backwards Pseudo-Gauss-Seidel"))); + addSwitch("jor", new FlagSwitch(() -> set(PRISM_LIN_EQ_METHOD, "JOR"))); + addSwitch("sor", new FlagSwitch(() -> set(PRISM_LIN_EQ_METHOD, "SOR"))); + addSwitch("bsor", new FlagSwitch(() -> set(PRISM_LIN_EQ_METHOD, "Backwards SOR"))); + addSwitch("psor", new FlagSwitch(() -> set(PRISM_LIN_EQ_METHOD, "Pseudo-SOR"))); + addSwitch("bpsor", new FlagSwitch(() -> set(PRISM_LIN_EQ_METHOD, "Backwards Pseudo-SOR"))); + addSwitch("valiter", new FlagSwitch(() -> { set(PRISM_MDP_SOLN_METHOD, "Value iteration"); set(PRISM_MDP_MULTI_SOLN_METHOD, "Value iteration"); set(PRISM_IMDP_SOLN_METHOD, "Value iteration"); - } else if (sw.equals("politer")) { - set(PRISM_MDP_SOLN_METHOD, "Policy iteration"); - } else if (sw.equals("modpoliter")) { - set(PRISM_MDP_SOLN_METHOD, "Modified policy iteration"); - } else if (sw.equals("linprog") || sw.equals("lp")) { + })); + addSwitch("politer", new FlagSwitch(() -> set(PRISM_MDP_SOLN_METHOD, "Policy iteration"))); + addSwitch("modpoliter", new FlagSwitch(() -> set(PRISM_MDP_SOLN_METHOD, "Modified policy iteration"))); + addSwitch("linprog", "lp", new FlagSwitch(() -> { set(PRISM_MDP_SOLN_METHOD, "Linear programming"); set(PRISM_MDP_MULTI_SOLN_METHOD, "Linear programming"); - } - - // Interval iterations - else if (sw.equals("intervaliter") || - sw.equals("ii")) { + })); + addSwitch("intervaliter", "ii", (sw, a) -> { set(PRISM_INTERVAL_ITER, true); - - if (optionsString != null) { - optionsString = optionsString.trim(); + String opts = a.optionsString(); + if (opts != null) { + opts = opts.trim(); try { - OptionsIntervalIteration.validate(optionsString); + OptionsIntervalIteration.validate(opts); } catch (PrismException e) { throw new PrismException("In options for -" + sw + " switch: " + e.getMessage()); } - - // append options to existing ones - String iiOptions = getString(PRISM_INTERVAL_ITER_OPTIONS); - if ("".equals(iiOptions)) - iiOptions = optionsString; - else - iiOptions += "," + optionsString; - set(PRISM_INTERVAL_ITER_OPTIONS, iiOptions); - } - } - - // Pmax quotient - else if (sw.equals("pmaxquotient")) { - set(PRISM_PMAX_QUOTIENT, true); - } - - // Topological VI - else if (sw.equals("topological")) { - set(PRISM_TOPOLOGICAL_VI, true); - } - - // Linear equation solver over-relaxation parameter - else if (sw.equals("omega")) { - if (i < args.length - 1) { - try { - d = Double.parseDouble(args[++i]); - set(PRISM_LIN_EQ_METHOD_PARAM, d); - } catch (NumberFormatException e) { - throw new PrismException("Invalid value for -" + sw + " switch"); - } - } else { - throw new PrismException("No value specified for -" + sw + " switch"); - } - } - // Termination criterion (iterative methods) - else if (sw.equals("relative") || sw.equals("rel")) { - set(PRISM_TERM_CRIT, "Relative"); - } - else if (sw.equals("absolute") || sw.equals("abs")) { - set(PRISM_TERM_CRIT, "Absolute"); - } - // Termination criterion parameter - else if (sw.equals("epsilon") || sw.equals("e")) { - if (i < args.length - 1) { - try { - d = Double.parseDouble(args[++i]); - if (d < 0) - throw new NumberFormatException(""); - set(PRISM_TERM_CRIT_PARAM, d); - } catch (NumberFormatException e) { - throw new PrismException("Invalid value for -" + sw + " switch"); - } - } else { - throw new PrismException("No value specified for -" + sw + " switch"); - } - } - // Max iters - else if (sw.equals("maxiters")) { - if (i < args.length - 1) { - try { - j = Integer.parseInt(args[++i]); - if (j < 0) - throw new NumberFormatException(""); - set(PRISM_MAX_ITERS, j); - } catch (NumberFormatException e) { - throw new PrismException("Invalid value for -" + sw + " switch"); - } - } else { - throw new PrismException("No value specified for -" + sw + " switch"); - } - } - // export iterations - else if (sw.equals("exportiterations")) { - set(PRISM_EXPORT_ITERATIONS, true); - } - // fixed grid resolution - else if (sw.equals("gridresolution")) { - if (i < args.length - 1) { - try { - j = Integer.parseInt(args[++i]); - if (j < 0) - throw new NumberFormatException(""); - set(PRISM_GRID_RESOLUTION, j); - } catch (NumberFormatException e) { - throw new PrismException("Invalid value for -" + sw + " switch"); - } - } else { - throw new PrismException("No value specified for -" + sw + " switch"); + String existing = getString(PRISM_INTERVAL_ITER_OPTIONS); + set(PRISM_INTERVAL_ITER_OPTIONS, "".equals(existing) ? opts : existing + "," + opts); } - } - // export probabilities/rewards with up to n significant decimal places - else if (sw.equals("exportmodelprecision")) { - if (i < args.length - 1) { - try { - int precision = Integer.parseInt(args[++i]); - if (!RANGE_EXPORT_DOUBLE_PRECISION.contains(precision)) - throw new NumberFormatException(""); - set(PRISM_EXPORT_MODEL_PRECISION, precision); - } catch (NumberFormatException e) { - throw new PrismException("Invalid value for -" + sw + " switch"); - } - } else { - throw new PrismException("No value specified for -" + sw + " switch"); - } - } - // export headers off - else if (sw.equals("noexportheaders")) { - set(PRISM_EXPORT_MODEL_HEADERS, false); - } + }); + addSwitch("pmaxquotient", new FlagSwitch(() -> set(PRISM_PMAX_QUOTIENT, true))); + addSwitch("topological", new FlagSwitch(() -> set(PRISM_TOPOLOGICAL_VI, true))); + addSwitch("omega", new DoubleSwitch(d -> set(PRISM_LIN_EQ_METHOD_PARAM, d))); + addSwitch("relative", "rel", new FlagSwitch(() -> set(PRISM_TERM_CRIT, "Relative"))); + addSwitch("absolute", "abs", new FlagSwitch(() -> set(PRISM_TERM_CRIT, "Absolute"))); + addSwitch("epsilon", "e", (sw, a) -> { + double d = a.nextDouble(sw); + if (d < 0) throw new PrismException("Invalid value for -" + sw + " switch"); + set(PRISM_TERM_CRIT_PARAM, d); + }); + addSwitch("maxiters", (sw, a) -> { + int n = a.nextInt(sw); + if (n < 0) throw new PrismException("Invalid value for -" + sw + " switch"); + set(PRISM_MAX_ITERS, n); + }); + addSwitch("exportiterations", new FlagSwitch(() -> set(PRISM_EXPORT_ITERATIONS, true))); + addSwitch("gridresolution", (sw, a) -> { + int n = a.nextInt(sw); + if (n < 0) throw new PrismException("Invalid value for -" + sw + " switch"); + set(PRISM_GRID_RESOLUTION, n); + }); + addSwitch("exportmodelprecision", (sw, a) -> { + int n = a.nextInt(sw); + if (!RANGE_EXPORT_DOUBLE_PRECISION.contains(n)) + throw new PrismException("Invalid value for -" + sw + " switch"); + set(PRISM_EXPORT_MODEL_PRECISION, n); + }); + addSwitch("noexportheaders", new FlagSwitch(() -> set(PRISM_EXPORT_MODEL_HEADERS, false))); // MODEL CHECKING OPTIONS: - - // Precomputation algs off - else if (sw.equals("nopre")) { - set(PRISM_PRECOMPUTATION, false); - } - else if (sw.equals("noprob0")) { - set(PRISM_PROB0, false); - } - else if (sw.equals("noprob1")) { - set(PRISM_PROB1, false); - } - // Use predecessor relation? (e.g. for precomputation) - else if (sw.equals("noprerel")) { - set(PRISM_PRE_REL, false); - } - // Fix deadlocks on/off - else if (sw.equals("fixdl")) { - set(PRISM_FIX_DEADLOCKS, true); - } - else if (sw.equals("nofixdl")) { - set(PRISM_FIX_DEADLOCKS, false); - } - // Fairness on/off - else if (sw.equals("fair")) { - set(PRISM_FAIRNESS, true); - } - else if (sw.equals("nofair")) { - set(PRISM_FAIRNESS, false); - } - // Prob/rate checks off - else if (sw.equals("noprobchecks")) { - set(PRISM_DO_PROB_CHECKS, false); - } - // Sum round-off threshold - else if (sw.equals("sumroundoff")) { - if (i < args.length - 1) { - try { - d = Double.parseDouble(args[++i]); - if (d < 0) - throw new NumberFormatException(""); - set(PRISM_SUM_ROUND_OFF, d); - } catch (NumberFormatException e) { - throw new PrismException("Invalid value for -" + sw + " switch"); - } - } else { - throw new PrismException("No value specified for -" + sw + " switch"); - } - } - // No steady-state detection - else if (sw.equals("nossdetect")) { - set(PRISM_DO_SS_DETECTION, false); - } - // SCC computation algorithm - else if (sw.equals("sccmethod") || sw.equals("bsccmethod")) { - if (i < args.length - 1) { - s = args[++i]; - if (s.equals("xiebeerel")) - set(PRISM_SCC_METHOD, "Xie-Beerel"); - else if (s.equals("lockstep")) - set(PRISM_SCC_METHOD, "Lockstep"); - else if (s.equals("sccfind")) - set(PRISM_SCC_METHOD, "SCC-Find"); - else - throw new PrismException("Unrecognised option for -" + sw + " switch (options are: xiebeerel, lockstep, sccfind)"); - } else { - throw new PrismException("No parameter specified for -" + sw + " switch"); - } - } - // Enable symmetry reduction - else if (sw.equals("symm")) { - if (i < args.length - 2) { - set(PRISM_SYMM_RED_PARAMS, args[++i] + " " + args[++i]); - } else { - throw new PrismException("-symm switch requires two parameters (num. modules before/after symmetric ones)"); - } - } - // Abstraction-refinement engine options string (append if already partially specified) - else if (sw.equals("aroptions")) { - if (i < args.length - 1) { - String arOptions = getString(PRISM_AR_OPTIONS); - if ("".equals(arOptions)) - arOptions = args[++i].trim(); - else - arOptions += "," + args[++i].trim(); - set(PRISM_AR_OPTIONS, arOptions); - } else { - throw new PrismException("No parameter specified for -" + sw + " switch"); - } - } - // Handle all path formulas via automata constructions - else if (sw.equals("pathviaautomata")) { - set(PRISM_PATH_VIA_AUTOMATA, true); - } - // Don't simplify deterministic automata - else if (sw.equals("nodasimplify")) { - set(PRISM_NO_DA_SIMPLIFY, true); - } + addSwitch("nopre", new FlagSwitch(() -> set(PRISM_PRECOMPUTATION, false))); + addSwitch("noprob0", new FlagSwitch(() -> set(PRISM_PROB0, false))); + addSwitch("noprob1", new FlagSwitch(() -> set(PRISM_PROB1, false))); + addSwitch("noprerel", new FlagSwitch(() -> set(PRISM_PRE_REL, false))); + addSwitch("fixdl", new FlagSwitch(() -> set(PRISM_FIX_DEADLOCKS, true))); + addSwitch("nofixdl", new FlagSwitch(() -> set(PRISM_FIX_DEADLOCKS, false))); + addSwitch("fair", new FlagSwitch(() -> set(PRISM_FAIRNESS, true))); + addSwitch("nofair", new FlagSwitch(() -> set(PRISM_FAIRNESS, false))); + addSwitch("noprobchecks", new FlagSwitch(() -> set(PRISM_DO_PROB_CHECKS, false))); + addSwitch("sumroundoff", (sw, a) -> { + double d = a.nextDouble(sw); + if (d < 0) throw new PrismException("Invalid value for -" + sw + " switch"); + set(PRISM_SUM_ROUND_OFF, d); + }); + addSwitch("nossdetect", new FlagSwitch(() -> set(PRISM_DO_SS_DETECTION, false))); + addSwitch("sccmethod", "bsccmethod", new EnumSwitch() + .when("xiebeerel", () -> set(PRISM_SCC_METHOD, "Xie-Beerel")) + .when("lockstep", () -> set(PRISM_SCC_METHOD, "Lockstep")) + .when("sccfind", () -> set(PRISM_SCC_METHOD, "SCC-Find"))); + addSwitch("symm", (sw, a) -> { + String p1 = a.next(sw); + String p2 = a.next(sw); + set(PRISM_SYMM_RED_PARAMS, p1 + " " + p2); + }); + addSwitch("aroptions", (sw, a) -> { + String v = a.next(sw).trim(); + String existing = getString(PRISM_AR_OPTIONS); + set(PRISM_AR_OPTIONS, "".equals(existing) ? v : existing + "," + v); + }); + addSwitch("pathviaautomata", new FlagSwitch(() -> set(PRISM_PATH_VIA_AUTOMATA, true))); + addSwitch("nodasimplify", new FlagSwitch(() -> set(PRISM_NO_DA_SIMPLIFY, true))); - // MULTI-OBJECTIVE MODEL CHECKING OPTIONS: - - // Max different corner points that will be generated when performing - // target driven multi-obj verification. - else if (sw.equals("multimaxpoints")) { - if (i < args.length - 1) { - try { - j = Integer.parseInt(args[++i]); - if (j < 0) - throw new NumberFormatException(""); - set(PRISM_MULTI_MAX_POINTS, j); - } catch (NumberFormatException e) { - throw new PrismException("Invalid value for -" + sw + " switch"); - } - } else { - throw new PrismException("No value specified for -" + sw + " switch"); - } - } - // Threshold for approximate Pareto curve generation - else if (sw.equals("paretoepsilon")) { - if (i < args.length - 1) { - try { - d = Double.parseDouble(args[++i]); - if (d < 0) - throw new PrismException("Value for -" + sw + " switch must be non-negative"); - set(PRISM_PARETO_EPSILON, d); - } catch (NumberFormatException e) { - throw new PrismException("Invalid value for -" + sw + " switch"); - } - } else { - throw new PrismException("No value specified for -" + sw + " switch"); - } - } - else if (sw.equals("exportpareto")) { - if (i < args.length - 1) { - set(PRISM_EXPORT_PARETO_FILENAME, args[++i]); - } else { - throw new PrismException("No file specified for -" + sw + " switch"); - } - } - + addSwitch("multimaxpoints", (sw, a) -> { + int n = a.nextInt(sw); + if (n < 0) throw new PrismException("Invalid value for -" + sw + " switch"); + set(PRISM_MULTI_MAX_POINTS, n); + }); + addSwitch("paretoepsilon", (sw, a) -> { + double d = a.nextDouble(sw); + if (d < 0) throw new PrismException("Value for -" + sw + " switch must be non-negative"); + set(PRISM_PARETO_EPSILON, d); + }); + addSwitch("exportpareto", new StringSwitch(s -> set(PRISM_EXPORT_PARETO_FILENAME, s))); + // OUTPUT OPTIONS: - - // Verbosity - else if (sw.equals("verbose") || sw.equals("v")) { - set(PRISM_VERBOSE, true); - } - // Extra dd info on - else if (sw.equals("extraddinfo")) { - set(PRISM_EXTRA_DD_INFO, true); - } - // Extra reach info on - else if (sw.equals("extrareachinfo")) { - set(PRISM_EXTRA_REACH_INFO, true); - } - + addSwitch("verbose", "v", new FlagSwitch(() -> set(PRISM_VERBOSE, true))); + addSwitch("extraddinfo", new FlagSwitch(() -> set(PRISM_EXTRA_DD_INFO, true))); + addSwitch("extrareachinfo", new FlagSwitch(() -> set(PRISM_EXTRA_REACH_INFO, true))); + // SPARSE/HYBRID/MTBDD OPTIONS: - - // Turn off compact option for sparse matrix storage - else if (sw.equals("nocompact")) { - set(PRISM_COMPACT, false); - } - // Sparse bits info - else if (sw.equals("sbl")) { - if (i < args.length - 1) { - try { - j = Integer.parseInt(args[++i]); - if (j < -1) - throw new NumberFormatException(); - set(PRISM_NUM_SB_LEVELS, j); - } catch (NumberFormatException e) { - throw new PrismException("Invalid value for -" + sw + " switch"); - } - } else { - throw new PrismException("No value specified for -" + sw + " switch"); - } - } - else if (sw.equals("sbmax")) { - if (i < args.length - 1) { - try { - j = Integer.parseInt(args[++i]); - if (j < 0) - throw new NumberFormatException(); - set(PRISM_SB_MAX_MEM, j); - } catch (NumberFormatException e) { - throw new PrismException("Invalid value for -" + sw + " switch"); - } - } else { - throw new PrismException("No value specified for -" + sw + " switch"); - } - } - // Hybrid SOR info - else if (sw.equals("sorl") || sw.equals("gsl")) { - if (i < args.length - 1) { - try { - j = Integer.parseInt(args[++i]); - if (j < -1) - throw new NumberFormatException(); - set(PRISM_NUM_SOR_LEVELS, j); - } catch (NumberFormatException e) { - throw new PrismException("Invalid value for -" + sw + " switch"); - } - } else { - throw new PrismException("No value specified for -" + sw + " switch"); - } - } - else if (sw.equals("sormax") || sw.equals("gsmax")) { - if (i < args.length - 1) { - try { - j = Integer.parseInt(args[++i]); - if (j < 0) - throw new NumberFormatException(); - set(PRISM_SOR_MAX_MEM, j); - } catch (NumberFormatException e) { - throw new PrismException("Invalid value for -" + sw + " switch"); - } - } else { - throw new PrismException("No value specified for -" + sw + " switch"); - } - } - // CUDD settings - else if (sw.equals("cuddmaxmem")) { - if (i < args.length - 1) { - set(PRISM_CUDD_MAX_MEM, args[++i]); - } else { - throw new PrismException("No value specified for -" + sw + " switch"); - } - } - else if (sw.equals("cuddepsilon")) { - if (i < args.length - 1) { - try { - d = Double.parseDouble(args[++i]); - if (d < 0) - throw new NumberFormatException(""); - set(PRISM_CUDD_EPSILON, d); - } catch (NumberFormatException e) { - throw new PrismException("Invalid value for -" + sw + " switch"); - } - } else { - throw new PrismException("No value specified for -" + sw + " switch"); - } - } else if (sw.equals("ddextrastatevars")) { - if (i < args.length - 1) { - try { - int v = Integer.parseInt(args[++i]); - if (v < 0) - throw new NumberFormatException(""); - set(PRISM_DD_EXTRA_STATE_VARS, v); - } catch (NumberFormatException e) { - throw new PrismException("Invalid value for -" + sw + " switch"); - } - } else { - throw new PrismException("No value specified for -" + sw + " switch"); - } - } else if (sw.equals("ddextraactionvars")) { - if (i < args.length - 1) { - try { - int v = Integer.parseInt(args[++i]); - if (v < 0) - throw new NumberFormatException(""); - set(PRISM_DD_EXTRA_ACTION_VARS, v); - } catch (NumberFormatException e) { - throw new PrismException("Invalid value for -" + sw + " switch"); - } - } else { - throw new PrismException("No value specified for -" + sw + " switch"); - } - } - + addSwitch("nocompact", new FlagSwitch(() -> set(PRISM_COMPACT, false))); + addSwitch("sbl", (sw, a) -> { + int n = a.nextInt(sw); + if (n < -1) throw new PrismException("Invalid value for -" + sw + " switch"); + set(PRISM_NUM_SB_LEVELS, n); + }); + addSwitch("sbmax", (sw, a) -> { + int n = a.nextInt(sw); + if (n < 0) throw new PrismException("Invalid value for -" + sw + " switch"); + set(PRISM_SB_MAX_MEM, n); + }); + addSwitch("sorl", "gsl", (sw, a) -> { + int n = a.nextInt(sw); + if (n < -1) throw new PrismException("Invalid value for -" + sw + " switch"); + set(PRISM_NUM_SOR_LEVELS, n); + }); + addSwitch("sormax", "gsmax", (sw, a) -> { + int n = a.nextInt(sw); + if (n < 0) throw new PrismException("Invalid value for -" + sw + " switch"); + set(PRISM_SOR_MAX_MEM, n); + }); + addSwitch("cuddmaxmem", new StringSwitch(s -> set(PRISM_CUDD_MAX_MEM, s))); + addSwitch("cuddepsilon", (sw, a) -> { + double d = a.nextDouble(sw); + if (d < 0) throw new PrismException("Invalid value for -" + sw + " switch"); + set(PRISM_CUDD_EPSILON, d); + }); + addSwitch("ddextrastatevars", (sw, a) -> { + int n = a.nextInt(sw); + if (n < 0) throw new PrismException("Invalid value for -" + sw + " switch"); + set(PRISM_DD_EXTRA_STATE_VARS, n); + }); + addSwitch("ddextraactionvars", (sw, a) -> { + int n = a.nextInt(sw); + if (n < 0) throw new PrismException("Invalid value for -" + sw + " switch"); + set(PRISM_DD_EXTRA_ACTION_VARS, n); + }); + // ADVERSARIES/COUNTEREXAMPLES: - - // Export adversary to file - else if (sw.equals("exportadv")) { - if (i < args.length - 1) { - set(PRISM_EXPORT_ADV, "DTMC"); - set(PRISM_EXPORT_ADV_FILENAME, args[++i]); - } else { - throw new PrismException("No file specified for -" + sw + " switch"); - } - } - // Export adversary to file, as an MDP - else if (sw.equals("exportadvmdp")) { - if (i < args.length - 1) { - set(PRISM_EXPORT_ADV, "MDP"); - set(PRISM_EXPORT_ADV_FILENAME, args[++i]); - } else { - throw new PrismException("No file specified for -" + sw + " switch"); - } - } - - // LTL2DA TOOLS - - else if (sw.equals("ltl2datool")) { - if (i < args.length - 1) { - String filename = args[++i]; - set(PRISM_LTL2DA_TOOL, filename); - } else { - throw new PrismException("The -" + sw + " switch requires one argument (path to the executable)"); - } - } - else if (sw.equals("ltl2dasyntax")) { - if (i < args.length - 1) { - String syntax = args[++i]; - switch (syntax) { - case "lbt": - set(PRISM_LTL2DA_SYNTAX, "LBT"); - break; - case "spin": - set(PRISM_LTL2DA_SYNTAX, "Spin"); - break; - case "spot": - set(PRISM_LTL2DA_SYNTAX, "Spot"); - break; - case "rabinizer": - set(PRISM_LTL2DA_SYNTAX, "Rabinizer"); - break; - default: - throw new PrismException("Unrecognised option for -" + sw + " switch (options are: lbt, spin, spot, rabinizer)"); - } - } else { - throw new PrismException("The -" + sw + " switch requires one argument (options are: lbt, spin, spot, rabinizer)"); - } - } + addSwitch("exportadv", (sw, a) -> { + set(PRISM_EXPORT_ADV, "DTMC"); + set(PRISM_EXPORT_ADV_FILENAME, a.next(sw)); + }); + addSwitch("exportadvmdp", (sw, a) -> { + set(PRISM_EXPORT_ADV, "MDP"); + set(PRISM_EXPORT_ADV_FILENAME, a.next(sw)); + }); - // DEBUGGING / SANITY CHECKS - else if (sw.equals("ddsanity")) { - set(PRISM_JDD_SANITY_CHECKS, true); - } + // LTL2DA TOOLS: + addSwitch("ltl2datool", new StringSwitch(s -> set(PRISM_LTL2DA_TOOL, s))); + addSwitch("ltl2dasyntax", new EnumSwitch() + .when("lbt", () -> set(PRISM_LTL2DA_SYNTAX, "LBT")) + .when("spin", () -> set(PRISM_LTL2DA_SYNTAX, "Spin")) + .when("spot", () -> set(PRISM_LTL2DA_SYNTAX, "Spot")) + .when("rabinizer", () -> set(PRISM_LTL2DA_SYNTAX, "Rabinizer"))); + + // DEBUGGING / SANITY CHECKS: + addSwitch("ddsanity", new FlagSwitch(() -> set(PRISM_JDD_SANITY_CHECKS, true))); // PARAMETRIC MODEL CHECKING: - - else if (sw.equals("param")) { - set(PRISM_PARAM_ENABLED, true); - } - else if (sw.equals("paramprecision")) { - if (i < args.length - 1) { - set(PRISM_PARAM_PRECISION, args[++i]); - } else { - throw new PrismException("No value specified for -" + sw + " switch"); - } - } - else if (sw.equals("paramsplit")) { - if (i < args.length - 1) { - s = args[++i]; - if (s.equals("longest")) - set(PRISM_PARAM_SPLIT, "Longest"); - else if (s.equals("all")) - set(PRISM_PARAM_SPLIT, "All"); - else - throw new PrismException("Unrecognised option for -" + sw + " switch (options are: longest, all)"); - } else { - throw new PrismException("No value specified for -" + sw + " switch"); - } - } - else if (sw.equals("parambisim")) { - if (i < args.length - 1) { - s = args[++i]; - if (s.equals("strong")) - set(PRISM_PARAM_BISIM, "Strong"); - else if (s.equals("weak")) - set(PRISM_PARAM_BISIM, "Weak"); - else if (s.equals("none")) - set(PRISM_PARAM_BISIM, "None"); - else - throw new PrismException("Unrecognised option for -" + sw + " switch (options are: strong, weak, none)"); - } else { - throw new PrismException("No value specified for -" + sw + " switch"); - } - } - else if (sw.equals("paramfunction")) { - if (i < args.length - 1) { - s = args[++i]; - if (s.equals("jascached")) - set(PRISM_PARAM_FUNCTION, "JAS-cached"); - else if (s.equals("jas")) - set(PRISM_PARAM_FUNCTION, "JAS"); - else if (s.equals("dag")) - set(PRISM_PARAM_FUNCTION, "DAG"); - else - throw new PrismException("Unrecognised option for -" + sw + " switch (options are: jascached, jas, dag)"); - } else { - throw new PrismException("No value specified for -" + sw + " switch"); - } - } - else if (sw.equals("paramelimorder")) { - if (i < args.length - 1) { - s = args[++i]; - if (s.equals("arb")) - set(PRISM_PARAM_ELIM_ORDER, "Arbitrary"); - else if (s.equals("fw")) - set(PRISM_PARAM_ELIM_ORDER, "Forward"); - else if (s.equals("fwrev")) - set(PRISM_PARAM_ELIM_ORDER, "Forward-reversed"); - else if (s.equals("bw")) - set(PRISM_PARAM_ELIM_ORDER, "Backward"); - else if (s.equals("bwrev")) - set(PRISM_PARAM_ELIM_ORDER, "Backward-reversed"); - else if (s.equals("rand")) - set(PRISM_PARAM_ELIM_ORDER, "Random"); - else - throw new PrismException("Unrecognised option for -" + sw + " switch (options are: arb,fw,fwrev,bw,bwrev,rand)"); - } else { - throw new PrismException("No value specified for -" + sw + " switch"); - } - } - else if (sw.equals("paramrandompoints")) { - try { - j = Integer.parseInt(args[++i]); - if (j < 0) - throw new NumberFormatException(); - set(PRISM_PARAM_RANDOM_POINTS, j); - } catch (NumberFormatException e) { - throw new PrismException("Invalid value for -" + sw + " switch"); - } - } - else if (sw.equals("paramsubsumeregions")) { - boolean b = Boolean.parseBoolean(args[++i]); - set(PRISM_PARAM_SUBSUME_REGIONS, b); - } - else if (sw.equals("paramdagmaxerror")) { - try { - d = Double.parseDouble(args[++i]); - if (d < 0) - throw new NumberFormatException(); - set(PRISM_PARAM_DAG_MAX_ERROR, d); - } catch (NumberFormatException e) { - throw new PrismException("Invalid value for -" + sw + " switch"); - } - } - - // FAST ADAPTIVE UNIFORMISATION - - // Epsilon for fast adaptive uniformisation - else if (sw.equals("fauepsilon")) { - if (i < args.length - 1) { - try { - d = Double.parseDouble(args[++i]); - if (d < 0) - throw new NumberFormatException(""); - set(PRISM_FAU_EPSILON, d); - } catch (NumberFormatException e) { - throw new PrismException("Invalid value for -" + sw + " switch"); - } - } else { - throw new PrismException("No value specified for -" + sw + " switch"); - } - } - // Delta for fast adaptive uniformisation - else if (sw.equals("faudelta")) { - if (i < args.length - 1) { - try { - d = Double.parseDouble(args[++i]); - if (d < 0) - throw new NumberFormatException(""); - set(PRISM_FAU_DELTA, d); - } catch (NumberFormatException e) { - throw new PrismException("Invalid value for -" + sw + " switch"); - } - } else { - throw new PrismException("No value specified for -" + sw + " switch"); - } - } - // Array threshold for fast adaptive uniformisation - else if (sw.equals("fauarraythreshold")) { - if (i < args.length - 1) { - try { - j = Integer.parseInt(args[++i]); - if (j < 0) - throw new NumberFormatException(""); - set(PRISM_FAU_ARRAYTHRESHOLD, j); - } catch (NumberFormatException e) { - throw new PrismException("Invalid value for -" + sw + " switch"); - } - } else { - throw new PrismException("No value specified for -" + sw + " switch"); - } - } - // Number of intervals for fast adaptive uniformisation - else if (sw.equals("fauintervals")) { - if (i < args.length - 1) { - try { - j = Integer.parseInt(args[++i]); - if (j < 0) - throw new NumberFormatException(""); - set(PRISM_FAU_INTERVALS, j); - } catch (NumberFormatException e) { - throw new PrismException("Invalid value for -" + sw + " switch"); - } - } else { - throw new PrismException("No value specified for -" + sw + " switch"); - } - } - else if (sw.equals("fauinitival")) { - if (i < args.length - 1) { - try { - d = Double.parseDouble(args[++i]); - if (d < 0.0) - throw new NumberFormatException(""); - set(PRISM_FAU_INITIVAL, d); - } catch (NumberFormatException e) { - throw new PrismException("Invalid value for -" + sw + " switch"); - } - } else { - throw new PrismException("No value specified for -" + sw + " switch"); - } - } + addSwitch("param", new FlagSwitch(() -> set(PRISM_PARAM_ENABLED, true))); + addSwitch("paramprecision", new StringSwitch(s -> set(PRISM_PARAM_PRECISION, s))); + addSwitch("paramsplit", new EnumSwitch() + .when("longest", () -> set(PRISM_PARAM_SPLIT, "Longest")) + .when("all", () -> set(PRISM_PARAM_SPLIT, "All"))); + addSwitch("parambisim", new EnumSwitch() + .when("strong", () -> set(PRISM_PARAM_BISIM, "Strong")) + .when("weak", () -> set(PRISM_PARAM_BISIM, "Weak")) + .when("none", () -> set(PRISM_PARAM_BISIM, "None"))); + addSwitch("paramfunction", new EnumSwitch() + .when("jascached", () -> set(PRISM_PARAM_FUNCTION, "JAS-cached")) + .when("jas", () -> set(PRISM_PARAM_FUNCTION, "JAS")) + .when("dag", () -> set(PRISM_PARAM_FUNCTION, "DAG"))); + addSwitch("paramelimorder", new EnumSwitch() + .when("arb", () -> set(PRISM_PARAM_ELIM_ORDER, "Arbitrary")) + .when("fw", () -> set(PRISM_PARAM_ELIM_ORDER, "Forward")) + .when("fwrev", () -> set(PRISM_PARAM_ELIM_ORDER, "Forward-reversed")) + .when("bw", () -> set(PRISM_PARAM_ELIM_ORDER, "Backward")) + .when("bwrev", () -> set(PRISM_PARAM_ELIM_ORDER, "Backward-reversed")) + .when("rand", () -> set(PRISM_PARAM_ELIM_ORDER, "Random"))); + addSwitch("paramrandompoints", (sw, a) -> { + int n = a.nextInt(sw); + if (n < 0) throw new PrismException("Invalid value for -" + sw + " switch"); + set(PRISM_PARAM_RANDOM_POINTS, n); + }); + addSwitch("paramsubsumeregions", (sw, a) -> { + set(PRISM_PARAM_SUBSUME_REGIONS, Boolean.parseBoolean(a.next(sw))); + }); + addSwitch("paramdagmaxerror", (sw, a) -> { + double d = a.nextDouble(sw); + if (d < 0) throw new PrismException("Invalid value for -" + sw + " switch"); + set(PRISM_PARAM_DAG_MAX_ERROR, d); + }); - // HIDDEN OPTIONS - - // export property automaton to file (hidden option) - else if (sw.equals("exportpropaut")) { - if (i < args.length - 1) { - setExportPropAut(true); - setExportPropAutFilename(args[++i]); - setExportPropAutType("txt"); // default - for (Map.Entry option : options.entrySet()) { - if (option.getKey().equals("txt")) { - setExportPropAutType("txt"); - } else if (option.getKey().equals("dot")) { - setExportPropAutType("dot"); - } else if (option.getKey().equals("hoa")) { - setExportPropAutType("hoa"); - } else { - throw new PrismException("Unknown option \"" + option.getKey() + "\" for -" + sw + " switch"); - } + // FAST ADAPTIVE UNIFORMISATION: + addSwitch("fauepsilon", (sw, a) -> { + double d = a.nextDouble(sw); + if (d < 0) throw new PrismException("Invalid value for -" + sw + " switch"); + set(PRISM_FAU_EPSILON, d); + }); + addSwitch("faudelta", (sw, a) -> { + double d = a.nextDouble(sw); + if (d < 0) throw new PrismException("Invalid value for -" + sw + " switch"); + set(PRISM_FAU_DELTA, d); + }); + addSwitch("fauarraythreshold", (sw, a) -> { + int n = a.nextInt(sw); + if (n < 0) throw new PrismException("Invalid value for -" + sw + " switch"); + set(PRISM_FAU_ARRAYTHRESHOLD, n); + }); + addSwitch("fauintervals", (sw, a) -> { + int n = a.nextInt(sw); + if (n < 0) throw new PrismException("Invalid value for -" + sw + " switch"); + set(PRISM_FAU_INTERVALS, n); + }); + addSwitch("fauinitival", (sw, a) -> { + double d = a.nextDouble(sw); + if (d < 0.0) throw new PrismException("Invalid value for -" + sw + " switch"); + set(PRISM_FAU_INITIVAL, d); + }); + + addSwitch("exportpropaut", (sw, a) -> { + Map options = splitOptionsString(a.optionsString()); + setExportPropAut(true); + setExportPropAutFilename(a.next(sw)); + setExportPropAutType("txt"); // default + for (Map.Entry option : options.entrySet()) { + switch (option.getKey()) { + case "txt": setExportPropAutType("txt"); break; + case "dot": setExportPropAutType("dot"); break; + case "hoa": setExportPropAutType("hoa"); break; + default: throw new PrismException("Unknown option \"" + option.getKey() + "\" for -" + sw + " switch"); } - } else { - throw new PrismException("No file specified for -" + sw + " switch"); } - } - - // unknown switch - error - else { - throw new PrismException("Invalid switch -" + sw + " (type \"prism -help\" for full list)"); - } - - return i + 1; + }); } - + + private void addSwitch(String name, SwitchHandler h) + { + settingsSwitchHandlers.put(name, h); + } + + private void addSwitch(String n1, String n2, SwitchHandler h) + { + settingsSwitchHandlers.put(n1, h); + settingsSwitchHandlers.put(n2, h); + } + + private void addSwitch(String n1, String n2, String n3, SwitchHandler h) + { + settingsSwitchHandlers.put(n1, h); + settingsSwitchHandlers.put(n2, h); + settingsSwitchHandlers.put(n3, h); + } + + /** * Split a switch of the form -switch:options into parts. * The latter can be empty, in which case the : is optional. diff --git a/prism/src/prism/SwitchHandler.java b/prism/src/prism/SwitchHandler.java new file mode 100644 index 0000000000..fde6fc56b0 --- /dev/null +++ b/prism/src/prism/SwitchHandler.java @@ -0,0 +1,135 @@ +//============================================================================== +// +// Copyright (c) 2026- +// Authors: +// * Dave Parker (University of Oxford) +// +//------------------------------------------------------------------------------ +// +// This file is part of PRISM. +// +// PRISM is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// PRISM is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with PRISM; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +//============================================================================== + +package prism; + +import java.util.LinkedHashMap; + +/** + * Handler for a single CLI switch. + * Can be implemented as a lambda or via one of the typed concrete classes + * ({@link FlagSwitch}, {@link StringSwitch}, {@link IntSwitch}, + * {@link DoubleSwitch}, {@link EnumSwitch}) that encapsulate common + * argument-consumption patterns. + */ +@FunctionalInterface +interface SwitchHandler +{ + void handle(String sw, ArgConsumer args) throws PrismException; +} + +// ── Concrete implementations ───────────────────────────────────────────────── + +/** Switch with no argument: just runs an action when the switch is seen. */ +class FlagSwitch implements SwitchHandler +{ + @FunctionalInterface + interface Action { void run() throws PrismException; } + + private final Action action; + + FlagSwitch(Action action) { this.action = action; } + + @Override + public void handle(String sw, ArgConsumer a) throws PrismException { action.run(); } +} + +/** Switch that consumes the next argument as a raw string and passes it to an action. */ +class StringSwitch implements SwitchHandler +{ + @FunctionalInterface + interface Action { void accept(String s) throws PrismException; } + + private final Action action; + + StringSwitch(Action action) { this.action = action; } + + @Override + public void handle(String sw, ArgConsumer a) throws PrismException { action.accept(a.next(sw)); } +} + +/** Switch that consumes the next argument, parses it as an {@code int}, and passes it to an action. */ +class IntSwitch implements SwitchHandler +{ + @FunctionalInterface + interface Action { void accept(int n) throws PrismException; } + + private final Action action; + + IntSwitch(Action action) { this.action = action; } + + @Override + public void handle(String sw, ArgConsumer a) throws PrismException { action.accept(a.nextInt(sw)); } +} + +/** Switch that consumes the next argument, parses it as a {@code double}, and passes it to an action. */ +class DoubleSwitch implements SwitchHandler +{ + @FunctionalInterface + interface Action { void accept(double d) throws PrismException; } + + private final Action action; + + DoubleSwitch(Action action) { this.action = action; } + + @Override + public void handle(String sw, ArgConsumer a) throws PrismException { action.accept(a.nextDouble(sw)); } +} + +/** + * Switch that consumes the next argument and dispatches to one of a fixed set of named choices. + * Throws a descriptive error listing valid options on unknown input. + * Choices are registered via {@link #when(String, FlagSwitch.Action)} which supports chaining. + * Aliases (multiple keys for the same action) are supported by calling {@code when()} multiple times. + */ +class EnumSwitch implements SwitchHandler +{ + private final LinkedHashMap choices = new LinkedHashMap<>(); + + EnumSwitch when(String key, FlagSwitch.Action action) + { + choices.put(key, action); + return this; + } + + EnumSwitch when(String key1, String key2, FlagSwitch.Action action) + { + choices.put(key1, action); + choices.put(key2, action); + return this; + } + + @Override + public void handle(String sw, ArgConsumer a) throws PrismException + { + String v = a.next(sw); + FlagSwitch.Action action = choices.get(v); + if (action == null) + throw new PrismException("Unrecognised option \"" + v + "\" for -" + sw + + " (options are: " + String.join(", ", choices.keySet()) + ")"); + action.run(); + } +} From d41c02ad87c3d34c52a0c0c9b363f79dc948a3aa Mon Sep 17 00:00:00 2001 From: Dave Parker Date: Mon, 29 Jun 2026 22:45:36 +0100 Subject: [PATCH 02/15] CLI refactor: integrate help text directly into switch registration Each switch's short description (for -help) and detailed description (for -help ) are now declared at the addSwitch call site rather than maintained separately in printHelp/printHelpSwitch methods. Help output is generated from the ordered switch registry, so adding a switch requires only one place to edit. Key additions in SwitchHandler.java: - SwitchEntry bundles handler + short text + argHint + longDesc lambda - SwitchRegistry wraps the ordered map and tracks the current group header - StringPlusOptionsSwitch stores an OptionParser alongside its handler, splits :, and calls the action with (files, ParseCallback) so the process method triggers options parsing at the right moment; printOptions() on the switch lets the longDesc lambda list sub-options without referencing a separate field Key changes in PrismCL.java: - initSwitchHandlers() registers all ~110 switches with their descriptions using beginGroup()/addSwitch() overloads; short -help is derived from the registry, detailed -help from the longDesc lambdas - Six OptionParser fields replaced by local StringPlusOptionsSwitch variables; process methods receive (String files, ParseCallback parse) and call parse.run() instead of naming the parser explicitly - OptionParser combined overloads (flag/bool/string/choice with description + action) and printOptions() let sub-option help live alongside the handlers in each switch's OptionParser definition --- prism/src/prism/OptionParser.java | 190 +++++- prism/src/prism/PrismCL.java | 996 +++++++++++++--------------- prism/src/prism/PrismSettings.java | 579 +++++++--------- prism/src/prism/SwitchHandler.java | 111 ++++ prism/src/prism/SwitchRegistry.java | 143 ++++ 5 files changed, 1133 insertions(+), 886 deletions(-) create mode 100644 prism/src/prism/SwitchRegistry.java diff --git a/prism/src/prism/OptionParser.java b/prism/src/prism/OptionParser.java index b08e1888f4..145da5d308 100644 --- a/prism/src/prism/OptionParser.java +++ b/prism/src/prism/OptionParser.java @@ -26,14 +26,29 @@ package prism; +import java.util.ArrayList; import java.util.LinkedHashMap; +import java.util.List; /** * Builder for parsing a comma-separated list of sub-options within a CLI switch argument * (e.g. the {@code opt1,key=val,...} portion of {@code -switch files:opt1,key=val,...}). - * Handles three option styles: bare flags, boolean {@code key=true|false}, fixed-choice + * Handles four option styles: bare flags, boolean {@code key=true|false}, fixed-choice * {@code key=val}, and raw-string {@code key=anything}. - * All error messages include the option name and switch name automatically. + * + *

Each registration method has three variants: + *

    + *
  • Action-only — e.g. {@code flag(name, action)}: registers an execution handler + * but no help metadata; use for hidden/alias options. + *
  • Description-only — e.g. {@code flag(name, desc)}: records help metadata + * for {@link #printOptions} but registers no handler; use when building a + * description-only parser for {@code -help} output that is separate from the live parser. + *
  • Combined — e.g. {@code flag(name, desc, action)}: registers both handler + * and help metadata in one call; the preferred form when the same parser instance + * is used for both parsing and help output. + *
+ * + *

All error messages include the option name and switch name automatically. */ class OptionParser { @@ -48,45 +63,159 @@ private interface ValueHandler void accept(String key, String val, String switchName) throws PrismException; } + /** One entry in the human-readable option table printed by {@link #printOptions}. */ + private static class HelpEntry + { + final String type; // "flag", "bool", "string", "choice" + final String name; + final String argHint; // null for flag/bool; "" for string; "a/b/c" for choice + final String description; + + HelpEntry(String type, String name, String argHint, String description) + { + this.type = type; this.name = name; this.argHint = argHint; this.description = description; + } + } + private final LinkedHashMap flagHandlers = new LinkedHashMap<>(); private final LinkedHashMap valueHandlers = new LinkedHashMap<>(); + private final List helpEntries = new ArrayList<>(); - /** Register a bare flag option (no {@code =value}). */ + // ── Flag options ────────────────────────────────────────────────────────── + + /** Register a bare flag option (action only, no help metadata). */ OptionParser flag(String name, FlagAction action) { flagHandlers.put(name, action); return this; } - /** Register a {@code key=value} option; action receives the raw value string. */ + /** Record a bare flag option for help output only (no execution handler). */ + OptionParser flag(String name, String description) + { + helpEntries.add(new HelpEntry("flag", name, null, description)); + return this; + } + + /** Register a bare flag option with both execution handler and help metadata. */ + OptionParser flag(String name, String description, FlagAction action) + { + flagHandlers.put(name, action); + helpEntries.add(new HelpEntry("flag", name, null, description)); + return this; + } + + // ── String options ──────────────────────────────────────────────────────── + + /** Register a {@code key=value} option (action only; action receives the raw value string). */ OptionParser string(String name, StringAction action) { valueHandlers.put(name, (key, val, sw) -> action.accept(val)); return this; } - /** Register a {@code key=true|false} option. */ + /** Record a {@code key=} string option for help output only. */ + OptionParser string(String name, String argHint, String description) + { + helpEntries.add(new HelpEntry("string", name, argHint, description)); + return this; + } + + /** Register a {@code key=value} option with both execution handler and help metadata. */ + OptionParser string(String name, String argHint, String description, StringAction action) + { + valueHandlers.put(name, (key, val, sw) -> action.accept(val)); + helpEntries.add(new HelpEntry("string", name, argHint, description)); + return this; + } + + // ── Boolean options ─────────────────────────────────────────────────────── + + /** Register a {@code key=true|false} option (action only). */ OptionParser bool(String name, BoolAction action) { valueHandlers.put(name, (key, val, sw) -> { - if (val.equals("true")) - action.accept(true); - else if (val.equals("false")) - action.accept(false); - else - throw new PrismException("Unknown value \"" + val + "\" for \"" + key + "\" option of -" + sw - + " (expected true or false)"); + if (val.equals("true")) action.accept(true); + else if (val.equals("false")) action.accept(false); + else throw new PrismException("Unknown value \"" + val + "\" for \"" + key + "\" option of -" + sw + + " (expected true or false)"); }); return this; } - /** Register a {@code key=val} option where {@code val} must be one of a fixed set. */ + /** Record a {@code key=true|false} option for help output only. */ + OptionParser bool(String name, String description) + { + helpEntries.add(new HelpEntry("bool", name, null, description)); + return this; + } + + /** Register a {@code key=true|false} option with both execution handler and help metadata. */ + OptionParser bool(String name, String description, BoolAction action) + { + bool(name, action); + helpEntries.add(new HelpEntry("bool", name, null, description)); + return this; + } + + // ── Choice options ──────────────────────────────────────────────────────── + + /** Register a {@code key=val} option where {@code val} must be one of a fixed set (action only). */ OptionParser choice(String name, Choice c) { valueHandlers.put(name, c.toHandler()); return this; } + /** Record a choice option for help output only; {@code validValues} are the displayed accepted values. */ + OptionParser choice(String name, String description, String... validValues) + { + helpEntries.add(new HelpEntry("choice", name, String.join("/", validValues), description)); + return this; + } + + /** + * Register a {@code key=val} choice option with both execution handler and help metadata. + * The displayed valid values are derived from the primary key of each {@link Choice#when} entry + * (the first argument when multiple keys are given as aliases). + */ + OptionParser choice(String name, String description, Choice c) + { + valueHandlers.put(name, c.toHandler()); + helpEntries.add(new HelpEntry("choice", name, String.join("/", c.primaryKeys()), description)); + return this; + } + + // ── Help output ─────────────────────────────────────────────────────────── + + /** + * Print the registered help entries as a human-readable option list. + * Only entries added via the description-only or combined overloads appear. + * Typical output: + *

+	 *  * csv - Export results as comma-separated values
+	 *  * format (=explicit/matlab/dot) - model export format
+	 *  * precision (=<n>) - use n significant figures
+	 *  * rewards (=true/false) - whether to include rewards
+	 * 
+ */ + void printOptions(PrismLog log) + { + for (HelpEntry e : helpEntries) { + StringBuilder sb = new StringBuilder(" * ").append(e.name); + switch (e.type) { + case "flag": /* nothing after name */ break; + case "bool": sb.append(" (=true/false)"); break; + case "string": // fall-through + case "choice": sb.append(" (=").append(e.argHint).append(")"); break; + } + sb.append(" - ").append(e.description); + log.println(sb.toString()); + } + } + + // ── Parsing ─────────────────────────────────────────────────────────────── + /** * Parse a comma-separated options string, dispatching each token to the registered handler. * @param optionsString comma-separated options (may be empty or null) @@ -99,14 +228,12 @@ void parse(String optionsString, String switchName) throws PrismException if (opt.isEmpty()) continue; int eq = opt.indexOf('='); if (eq == -1) { - // Bare option — must be a registered flag FlagAction fa = flagHandlers.get(opt); if (fa != null) { fa.run(); continue; } if (valueHandlers.containsKey(opt)) throw new PrismException("No value provided for \"" + opt + "\" option of -" + switchName); throw new PrismException("Unknown option \"" + opt + "\" for -" + switchName + " switch"); } else { - // key=value option String key = opt.substring(0, eq); String val = opt.substring(eq + 1); ValueHandler vh = valueHandlers.get(key); @@ -118,38 +245,49 @@ void parse(String optionsString, String switchName) throws PrismException } } + // ── Choice builder ──────────────────────────────────────────────────────── + /** * Builder for a fixed set of enumerated values for a {@link #choice} option. - * Mirrors the fluent {@code .when()} style of {@link EnumSwitch}. + * The first key in each {@code when()} call is the "primary" key shown in help output + * via {@link #primaryKeys()}; additional keys in the same call are accepted aliases + * but are not displayed. */ static class Choice { - private final LinkedHashMap map = new LinkedHashMap<>(); + private final LinkedHashMap map = new LinkedHashMap<>(); + private final List primaryKeys = new ArrayList<>(); /** Register a single accepted value and its action. */ Choice when(String key, FlagAction action) { + primaryKeys.add(key); map.put(key, action); return this; } - /** Register two accepted values (aliases) that trigger the same action. */ - Choice when(String k1, String k2, FlagAction action) + /** Register a primary value and one alias; both trigger the same action. */ + Choice when(String primary, String alias, FlagAction action) { - map.put(k1, action); - map.put(k2, action); + primaryKeys.add(primary); + map.put(primary, action); + map.put(alias, action); return this; } - /** Register three accepted values (aliases) that trigger the same action. */ - Choice when(String k1, String k2, String k3, FlagAction action) + /** Register a primary value and two aliases; all three trigger the same action. */ + Choice when(String primary, String alias1, String alias2, FlagAction action) { - map.put(k1, action); - map.put(k2, action); - map.put(k3, action); + primaryKeys.add(primary); + map.put(primary, action); + map.put(alias1, action); + map.put(alias2, action); return this; } + /** Returns the primary key of each registered choice, in registration order. */ + List primaryKeys() { return primaryKeys; } + /** Build the {@link ValueHandler} that dispatches on the registered values. */ ValueHandler toHandler() { diff --git a/prism/src/prism/PrismCL.java b/prism/src/prism/PrismCL.java index ae33941bb3..051c5f6ee1 100644 --- a/prism/src/prism/PrismCL.java +++ b/prism/src/prism/PrismCL.java @@ -36,10 +36,14 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Objects; +import java.util.Set; +import java.util.function.Consumer; import common.StackTraceHelper; import csv.CsvFormatException; @@ -146,8 +150,9 @@ public class PrismCL implements PrismModelListener private String exportStratFilename = null; private String simpathFilename = null; - // CLI switch handler map (populated by initSwitchHandlers) - private Map switchHandlers; + // Unified CLI switch map (handler + help metadata), populated by initSwitchHandlers + private Map switchHandlers; + private SwitchRegistry registry; // logs private PrismLog mainLog = null; @@ -200,7 +205,18 @@ public class PrismCL implements PrismModelListener // strategy export info private StrategyExportOptions exportStratOptions = null; - + + // transient state used during option parsing for complex switches + private boolean exportResultsCsv; + private boolean exportResultsMatrix; + private ModelExportTask pendingLabelExportTask; + private ModelExportOptions pendingExportOptions; + private List pendingExportTasks; + + // option parsers for complex switches (initialised in initSwitchHandlers, capture 'this') + // switch stored as field so handleFilesOnly() can be called for the .all shorthand (line ~1563) + private StringPlusOptionsSwitch importModelSwitch; + // parametric analysis info private String[] paramLowerBounds = null; private String[] paramUpperBounds = null; @@ -1065,11 +1081,11 @@ private void parseArguments(String[] args) throws PrismException String arg = consumer.advance(); String sw = consumer.parseSwitch(arg); if (sw != null) { - SwitchHandler handler = switchHandlers.get(sw); - if (handler != null) - handler.handle(sw, consumer); + SwitchEntry entry = switchHandlers.get(sw); + if (entry != null) + entry.handler.handle(sw, consumer); else - prism.getSettings().setFromCommandLineSwitch(sw, consumer); + errorAndExit("Unknown switch -" + sw + " (type \"prism -help\" for full list)"); } else { // argument is assumed to be a (model/properties) filename filenameArgs.add(arg); @@ -1080,40 +1096,36 @@ private void parseArguments(String[] args) throws PrismException } /** - * Populate {@link #switchHandlers} with a handler for every switch recognised by PrismCL. - * Switches not registered here fall through to {@link PrismSettings#setFromCommandLineSwitch}. - * The insertion order matches the -help output ordering. + * Populate {@link #switchHandlers} with a handler and help metadata for every recognised switch. + * {@link PrismSettings#registerSwitchHandlers} contributes PrismSettings switches in the right + * position. Insertion order determines the {@code -help} output ordering. */ private void initSwitchHandlers() { switchHandlers = new LinkedHashMap<>(); + registry = new SwitchRegistry(switchHandlers); - // Help / version / meta - addSwitch("help", "?", (sw, a) -> { - // -help [switch] is one of the few cases where the value arg is optional + // ── General (no section header) ────────────────────────────────────── + SwitchHandler helpHandler = (sw, a) -> { if (a.hasNext()) printHelpSwitch(a.next(sw)); else printHelp(); exit(); - }); - addSwitch("javamaxmem", - (sw, a) -> a.next(sw)); // consumed before JVM launch; value discarded - addSwitch("javastack", - (sw, a) -> a.next(sw)); // consumed before JVM launch; value discarded - addSwitch("javaparams", - (sw, a) -> a.next(sw)); // consumed before JVM launch; value discarded - addSwitch("timeout", (sw, a) -> { - int t = PrismUtils.convertTimeStringtoSeconds(a.next(sw)); - if (t < 0) errorAndExit("Negative timeout value \"" + t + "\" for -" + sw + " switch"); - if (t > 0) setTimeout(t); - // t == 0 -> no timeout - }); - addSwitch("version", new FlagSwitch(() -> { printVersion(); exit(); })); - addSwitch("dir", new StringSwitch(Prism::setWorkingDirectory)); - addSwitch("settings", new StringSwitch(s -> settingsFilename = s.trim())); - addSwitch("keywords", new FlagSwitch(() -> { printListOfKeywords(); exit(); })); // hidden - - // Property / constant / parameter info - addSwitch("pf", "pctl", "csl", new StringSwitch(s -> propertyString = s)); - addSwitch("prop", "property", (sw, a) -> { + }; + registry.addSwitch("help", helpHandler, "", "Display this help message"); + registry.addSwitch("?", helpHandler); // hidden alias + registry.addSwitch("version", new FlagSwitch(() -> { printVersion(); exit(); }), + "", "Display PRISM version info"); + registry.addSwitch("javaversion", new FlagSwitch(() -> {}), // handled by launch script + "", "Display Java version info"); + registry.addSwitch("dir", new StringSwitch(Prism::setWorkingDirectory), + "", "Set current working directory"); + registry.addSwitch("settings", new StringSwitch(s -> settingsFilename = s.trim()), + "", "Load settings from "); + + registry.addBlankLine(); + + registry.addSwitch("pf", "pctl", "csl", new StringSwitch(s -> propertyString = s), + "", "Model check properties "); + SwitchHandler propHandler = (sw, a) -> { String[] props = a.next(sw).trim().split(","); propertyIndices = new ArrayList<>(); for (String p : props) { @@ -1122,37 +1134,69 @@ private void initSwitchHandlers() catch (NumberFormatException e) { propertyIndices.add(p); } } } - }); - addSwitch("const", (sw, a) -> { + }; + registry.addSwitch("property", "prop", propHandler, + "", "Only model check properties included in list of indices/names"); + registry.addSwitch("const", (sw, a) -> { String v = a.next(sw).trim(); if ("".equals(constSwitch)) constSwitch = v; else constSwitch += "," + v; - }); - addSwitch("param", (sw, a) -> { - param = true; - String v = a.next(sw).trim(); - if ("".equals(paramSwitch)) paramSwitch = v; else paramSwitch += "," + v; - }); - - // Computation modes - addSwitch("steadystate", "ss", new FlagSwitch(() -> steadystate = true)); - addSwitch("transient", "tr", (sw, a) -> { dotransient = true; transientTime = a.next(sw); }); - addSwitch("simpath", (sw, a) -> { + }, "", "Define constant values as (e.g. for experiments)", + log -> { + log.println("Switch: -const \n"); + log.println(" is a comma-separated list of values or value ranges for undefined constants"); + log.println("in the model or properties (i.e. those declared without values, such as \"const int a;\")."); + log.println("You can either specify a single value (a=1), a range (a=1:10) or a range with a step (a=1:2:50)."); + log.println("For convenience, constant definutions can also be split across multiple -const switches."); + log.println("\nExamples:"); + log.println(" -const a=1,b=5.6,c=true"); + log.println(" -const a=1:10,b=5.6"); + log.println(" -const a=1:2:50,b=5.6"); + log.println(" -const a=1:2:50 -const b=5.6"); + }); + registry.addSwitch("steadystate", "ss", new FlagSwitch(() -> steadystate = true), + "", "Compute steady-state probabilities (D/CTMCs only)"); + registry.addSwitch("transient", "tr", (sw, a) -> { dotransient = true; transientTime = a.next(sw); }, + "", "Compute transient probabilities for time (or time range) (D/CTMCs only)"); + registry.addSwitch("simpath", (sw, a) -> { simpath = true; simpathDetails = a.next(sw); simpathFilename = a.next(sw); - }); - addSwitch("nobuild", new FlagSwitch(() -> nobuild = true)); - addSwitch("test", new FlagSwitch(() -> test = true)); - addSwitch("testall", new FlagSwitch(() -> { test = true; testExitsOnFail = false; })); - addSwitch("test:umb", new FlagSwitch(() -> prism.setTestUMB(true))); - - // DD debugging (hidden) - addSwitch("dddebug", new FlagSwitch(() -> jdd.DebugJDD.enable())); - addSwitch("ddtraceall", new FlagSwitch(() -> jdd.DebugJDD.traceAll = true)); - addSwitch("ddtracefollowcopies", new FlagSwitch(() -> jdd.DebugJDD.traceFollowCopies = true)); - addSwitch("dddebugwarnfatal", new FlagSwitch(() -> jdd.DebugJDD.warningsAreFatal = true)); - addSwitch("dddebugwarnoff", new FlagSwitch(() -> jdd.DebugJDD.warningsOff = true)); - addSwitch("ddtrace", (sw, a) -> { + }, " ", "Generate a random path with the simulator", + log -> { + log.println("Switch: -simpath \n"); + log.println("Generate a random path with the simulator and export it to (or to the screen if =\"stdout\")."); + log.println(" is a comma-separated list of options taken from:"); + GenerateSimulationPath.printOptions(log); + }); + registry.addSwitch("nobuild", new FlagSwitch(() -> nobuild = true), + "", "Skip model construction (just do parse/export)"); + registry.addSwitch("test", new FlagSwitch(() -> test = true), + "", "Enable \"test\" mode"); + registry.addSwitch("testall", new FlagSwitch(() -> { test = true; testExitsOnFail = false; }), + "", "Enable \"test\" mode, but don't exit on error"); + registry.addSwitch("javamaxmem", (sw, a) -> a.next(sw), // consumed before JVM launch + "", "Set the maximum heap size for Java, e.g. 500m, 4g [default: 1g]"); + registry.addSwitch("javastack", (sw, a) -> a.next(sw), // consumed before JVM launch + "", "Set the Java stack size [default: 4m]"); + registry.addSwitch("javaparams", (sw, a) -> a.next(sw), // consumed before JVM launch + "", "Pass additional command-line arguments to Java"); + registry.addSwitch("timeout", (sw, a) -> { + int t = PrismUtils.convertTimeStringtoSeconds(a.next(sw)); + if (t < 0) errorAndExit("Negative timeout value \"" + t + "\" for -" + sw + " switch"); + if (t > 0) setTimeout(t); + }, "", "Exit after a time-out of seconds if not already terminated"); + registry.addSwitch("ng", new FlagSwitch(() -> {}), // handled in main() before go() + "", "Run PRISM in Nailgun server mode; subsequent calls are then made via \"ngprism\""); + + // Hidden general switches + registry.addSwitch("keywords", new FlagSwitch(() -> { printListOfKeywords(); exit(); })); + registry.addSwitch("test:umb", new FlagSwitch(() -> prism.setTestUMB(true))); + registry.addSwitch("dddebug", new FlagSwitch(() -> jdd.DebugJDD.enable())); + registry.addSwitch("ddtraceall", new FlagSwitch(() -> jdd.DebugJDD.traceAll = true)); + registry.addSwitch("ddtracefollowcopies", new FlagSwitch(() -> jdd.DebugJDD.traceFollowCopies = true)); + registry.addSwitch("dddebugwarnfatal", new FlagSwitch(() -> jdd.DebugJDD.warningsAreFatal = true)); + registry.addSwitch("dddebugwarnoff", new FlagSwitch(() -> jdd.DebugJDD.warningsOff = true)); + registry.addSwitch("ddtrace", (sw, a) -> { String idStr = a.next(sw); try { jdd.DebugJDD.enableTracingForID(Integer.parseInt(idStr)); @@ -1161,196 +1205,348 @@ private void initSwitchHandlers() } }); - // IMPORT OPTIONS: - addSwitch("importpepa", new FlagSwitch(() -> importpepa = true)); - addSwitch("importprismpp", (sw, a) -> { importprismpp = true; prismppParams = a.next(sw); }); // hidden - addSwitch("importmodel", new StringSwitch(this::processImportModelSwitch)); - addSwitch("importtrans", (sw, a) -> { - // Recall model name in case needed as basename for model exports - modelFilename = a.next(sw); + // ── IMPORTS ────────────────────────────────────────────────────────── + registry.beginGroup("IMPORTS"); + registry.addSwitch("importpepa", new FlagSwitch(() -> importpepa = true), + "", "Model description is in PEPA, not the PRISM language"); + registry.addSwitch("importprismpp", (sw, a) -> { importprismpp = true; prismppParams = a.next(sw); }); // hidden + importModelSwitch = new StringPlusOptionsSwitch( + new OptionParser() + .choice("format", "model import format", new OptionParser.Choice() + .when("explicit", () -> { for (ModelImportSource s : modelImportSources) s.format = ModelExportFormat.EXPLICIT; }) + .when("umb", () -> { for (ModelImportSource s : modelImportSources) s.format = ModelExportFormat.UMB; })), + this::processImportModelSwitch); + registry.addSwitch("importmodel", importModelSwitch, + "", "Import the model directly from file(s)", + log -> { + log.println("Switch: -importmodel [:options]\n"); + log.println("Import the model directly from one or more file(s)."); + log.println("Use a list of file extensions to indicate which files should be read, e.g.:"); + log.println("\n -importmodel in.tra,sta\n"); + log.println("Possible extensions are: .tra, .sta, .obs, .lab, .srew, .trew, .umb"); + log.println("Use extension .all to import all explicit files (.tra/sta/obs/lab/srew/trew), e.g.:"); + log.println("\n -importmodel in.all\n"); + log.println("If provided, is a comma-separated list of options taken from:"); + importModelSwitch.printOptions(log); + }); + registry.addSwitch("importtrans", (sw, a) -> { + modelFilename = a.next(sw); // recall for use as basename in model exports modelImportSources.add(new ModelImportSource(ModelExportTask.ModelExportEntity.MODEL, ModelExportFormat.EXPLICIT, new File(modelFilename))); - }); - addSwitch("importstates", new StringSwitch(s -> modelImportSources.add( - new ModelImportSource(ModelExportTask.ModelExportEntity.STATES, ModelExportFormat.EXPLICIT, new File(s))))); - addSwitch("importobs", new StringSwitch(s -> modelImportSources.add( - new ModelImportSource(ModelExportTask.ModelExportEntity.OBSERVATIONS, ModelExportFormat.EXPLICIT, new File(s))))); - addSwitch("importlabels", new StringSwitch(s -> modelImportSources.add( - new ModelImportSource(ModelExportTask.ModelExportEntity.LABELS, ModelExportFormat.EXPLICIT, new File(s))))); - addSwitch("importstaterewards", new StringSwitch(s -> modelImportSources.add( - new ModelImportSource(ModelExportTask.ModelExportEntity.STATE_REWARDS, ModelExportFormat.EXPLICIT, new File(s))))); - addSwitch("importtransrewards", new StringSwitch(s -> modelImportSources.add( - new ModelImportSource(ModelExportTask.ModelExportEntity.TRANSITION_REWARDS, ModelExportFormat.EXPLICIT, new File(s))))); - addSwitch("importinitdist", (sw, a) -> { importinitdist = true; importInitDistFilename = a.next(sw); }); - addSwitch("importresults", (sw, a) -> { + }, "", "Import the transition matrix directly from a text file"); + registry.addSwitch("importstates", new StringSwitch(s -> modelImportSources.add( + new ModelImportSource(ModelExportTask.ModelExportEntity.STATES, ModelExportFormat.EXPLICIT, new File(s)))), + "", "Import the list of states directly from a text file"); + registry.addSwitch("importobs", new StringSwitch(s -> modelImportSources.add( + new ModelImportSource(ModelExportTask.ModelExportEntity.OBSERVATIONS, ModelExportFormat.EXPLICIT, new File(s)))), + "", "Import the list of observations directly from a text file"); + registry.addSwitch("importlabels", new StringSwitch(s -> modelImportSources.add( + new ModelImportSource(ModelExportTask.ModelExportEntity.LABELS, ModelExportFormat.EXPLICIT, new File(s)))), + "", "Import the list of labels directly from a text file"); + registry.addSwitch("importstaterewards", new StringSwitch(s -> modelImportSources.add( + new ModelImportSource(ModelExportTask.ModelExportEntity.STATE_REWARDS, ModelExportFormat.EXPLICIT, new File(s)))), + "", "Import the state rewards directly from a text file"); + registry.addSwitch("importtransrewards", new StringSwitch(s -> modelImportSources.add( + new ModelImportSource(ModelExportTask.ModelExportEntity.TRANSITION_REWARDS, ModelExportFormat.EXPLICIT, new File(s)))), + "", "Import the transition rewards directly from a text file"); + registry.addSwitch("importinitdist", (sw, a) -> { importinitdist = true; importInitDistFilename = a.next(sw); }, + "", "Specify initial probability distribution for transient/steady-state analysis"); + registry.addSwitch("dtmc", new FlagSwitch(() -> typeOverride = ModelType.DTMC), + "", "Force imported/built model to be a DTMC"); + registry.addSwitch("ctmc", new FlagSwitch(() -> typeOverride = ModelType.CTMC), + "", "Force imported/built model to be a CTMC"); + registry.addSwitch("mdp", new FlagSwitch(() -> typeOverride = ModelType.MDP), + "", "Force imported/built model to be an MDP"); + registry.addSwitch("importresults", (sw, a) -> { importresults = true; modelFilename = "no-model-file.prism"; importResultsFilename = a.next(sw); - }); - addSwitch("dtmc", new FlagSwitch(() -> typeOverride = ModelType.DTMC)); - addSwitch("mdp", new FlagSwitch(() -> typeOverride = ModelType.MDP)); - addSwitch("ctmc", new FlagSwitch(() -> typeOverride = ModelType.CTMC)); - - // EXPORT OPTIONS: - addSwitch("exportprism", (sw, a) -> { exportprism = true; exportPrismFilename = a.next(sw); }); - addSwitch("exportprismconst", (sw, a) -> { exportprismconst = true; exportPrismConstFilename = a.next(sw); }); - addSwitch("exportresults", new StringSwitch(this::processExportResultsSwitch)); - addSwitch("exportvector", (sw, a) -> { + }, "", "Import results from a data frame stored in CSV file", + log -> { + log.println("Switch: -importresults \n"); + log.println("Import results from a data frame stored as comma-separated values in ."); + }); + + // ── EXPORTS ────────────────────────────────────────────────────────── + registry.beginGroup("EXPORTS"); + StringPlusOptionsSwitch exportResultsSwitch = new StringPlusOptionsSwitch( + new OptionParser() + .flag("csv", "Export results as comma-separated values", () -> exportResultsCsv = true) + .flag("matrix", "Export results as one or more 2D matrices (e.g. for surface plots)", () -> exportResultsMatrix = true) + .flag("dataframe", "Export results as dataframe in comma-separated values", () -> exportShape = ResultsExportShape.DATA_FRAME) + .flag("comment", "Export results in comment format for regression testing",() -> exportShape = ResultsExportShape.COMMENT), + this::processExportResultsSwitch); + registry.addSwitch("exportresults", exportResultsSwitch, + "", "Export the results of model checking to a file", + log -> { + log.println("Switch: -exportresults \n"); + log.println("Exports the results of model checking to (or to the screen if =\"stdout\")."); + log.println("The default behaviour is to export a list of results in text form, using tabs to separate items."); + log.println("If provided, is a comma-separated list of options taken from:"); + exportResultsSwitch.printOptions(log); + }); + registry.addSwitch("exportvector", (sw, a) -> { exportvector = true; exportVectorFilename = a.next(sw); prism.setStoreVector(true); - }); - addSwitch("exportmodel", new StringSwitch(this::processExportModelSwitch)); - // process -exportmodelprecision in PrismSettings - addSwitch("exporttrans", new StringSwitch(s -> modelExportTasks.add( - new ModelExportTask(ModelExportTask.ModelExportEntity.MODEL, s)))); - addSwitch("exportstaterewards", new StringSwitch(s -> modelExportTasks.add( - new ModelExportTask(ModelExportTask.ModelExportEntity.STATE_REWARDS, s)))); - addSwitch("exporttransrewards", new StringSwitch(s -> modelExportTasks.add( - new ModelExportTask(ModelExportTask.ModelExportEntity.TRANSITION_REWARDS, s)))); - addSwitch("exportrewards", (sw, a) -> { + }, "", "Export results of model checking for all states to a file"); + StringPlusOptionsSwitch exportModelSwitch = new StringPlusOptionsSwitch( + new OptionParser() + .choice("format", "model export format", new OptionParser.Choice() + .when("explicit", () -> pendingExportOptions.setFormat(ModelExportFormat.EXPLICIT)) + .when("matlab", () -> pendingExportOptions.setFormat(ModelExportFormat.MATLAB)) + .when("dot", () -> pendingExportOptions.setFormat(ModelExportFormat.DOT)) + .when("drn", () -> pendingExportOptions.setFormat(ModelExportFormat.DRN)) + .when("umb", () -> pendingExportOptions.setFormat(ModelExportFormat.UMB))) + .bool("rewards", "whether to include rewards", v -> pendingExportOptions.setShowRewards(v)) + .bool("labels", "whether to include labels", v -> pendingExportOptions.setShowLabels(v)) + .bool("states", "whether to include state definitions", v -> pendingExportOptions.setShowStates(v)) + .bool("obs", "whether to include observation definitions", v -> pendingExportOptions.setShowObservations(v)) + .bool("actions", "whether to include actions on choices/transitions", v -> pendingExportOptions.setShowActions(v)) + .string("precision","", "use significant figures for floating point values (in text)", v -> { + try { + int n = Integer.parseInt(v); + if (!RANGE_EXPORT_DOUBLE_PRECISION.contains(n)) throw new NumberFormatException(); + pendingExportOptions.setModelPrecision(n); + } catch (NumberFormatException e) { + throw new PrismException("Invalid value \"" + v + "\" for \"precision\" option of -exportmodel"); + } + }) + .choice("zip", "whether to zip UMB files", new OptionParser.Choice() + .when("true", () -> pendingExportOptions.setZipped(true)) + .when("false", () -> pendingExportOptions.setZipped(false)) + .when("gzip", "gz", () -> pendingExportOptions.setZipped(true).setCompressionFormat(ModelExportOptions.CompressionFormat.GZIP)) + .when("xz", () -> pendingExportOptions.setZipped(true).setCompressionFormat(ModelExportOptions.CompressionFormat.XZ))) + .flag("text", "show binary formats in textual form", () -> pendingExportOptions.setBinaryAsText(true)) + .flag("matlab", "same as format=matlab", () -> { pendingExportOptions.setFormat(ModelExportFormat.MATLAB); exportType = Prism.EXPORT_MATLAB; }) + .flag("rows", "export matrices with one row/distribution on each line", () -> { pendingExportOptions.setExplicitRows(true); exportType = Prism.EXPORT_ROWS; }) + .flag("proplabels", "also export labels from a properties file into the same file, too", () -> { + for (ModelExportTask t : pendingExportTasks) + if (t.getEntity() == ModelExportTask.ModelExportEntity.LABELS) + t.setLabelExportSet(ModelExportTask.LabelExportSet.ALL); + }) + .bool("headers", "include headers in explicit (reward) files", v -> pendingExportOptions.setPrintHeaders(v)), + this::processExportModelSwitch); + registry.addSwitch("exportmodel", exportModelSwitch, + "", "Export the built model to file(s)", + log -> { + log.println("Switch: -exportmodel \n"); + log.println("Export the built model to file(s) (or to the screen if =\"stdout\")."); + log.println("Use a list of file extensions to indicate which files should be generated, e.g.:"); + log.println("\n -exportmodel out.tra,sta\n"); + log.println("\n -exportmodel out.umb\n"); + log.println("Possible extensions are: .tra, .srew, .trew, .lab, .sta, .obs, .dot, .umb, .drn"); + log.println("Use extension .all to export all explicit files (.tra/srew/trew/lab/sta/obs), e.g.:"); + log.println("\n -exportmodel out.all\n"); + log.println("Omit the file basename to use the basename of the model file, e.g.:"); + log.println("\n -exportmodel .all\n"); + log.println("Use extension .rew to export both .srew/.trew files"); + log.println(); + log.println("If provided, is a comma-separated list of options taken from:"); + exportModelSwitch.printOptions(log); + }); + registry.addSwitch("exporttrans", new StringSwitch(s -> modelExportTasks.add( + new ModelExportTask(ModelExportTask.ModelExportEntity.MODEL, s))), + "", "Export the transition matrix to a file"); + registry.addSwitch("exportstaterewards", new StringSwitch(s -> modelExportTasks.add( + new ModelExportTask(ModelExportTask.ModelExportEntity.STATE_REWARDS, s))), + "", "Export the state rewards vector to a file"); + registry.addSwitch("exporttransrewards", new StringSwitch(s -> modelExportTasks.add( + new ModelExportTask(ModelExportTask.ModelExportEntity.TRANSITION_REWARDS, s))), + "", "Export the transition rewards matrix to a file"); + registry.addSwitch("exportrewards", (sw, a) -> { modelExportTasks.add(new ModelExportTask(ModelExportTask.ModelExportEntity.STATE_REWARDS, a.next(sw))); modelExportTasks.add(new ModelExportTask(ModelExportTask.ModelExportEntity.TRANSITION_REWARDS, a.next(sw))); - }); - addSwitch("exportstates", new StringSwitch(s -> modelExportTasks.add( - new ModelExportTask(ModelExportTask.ModelExportEntity.STATES, s)))); - addSwitch("exportobs", new StringSwitch(s -> modelExportTasks.add( - new ModelExportTask(ModelExportTask.ModelExportEntity.OBSERVATIONS, s)))); - addSwitch("exportlabels", new StringSwitch(this::processExportLabelsSwitch)); - addSwitch("exportproplabels", new StringSwitch(this::processExportPropLabelsSwitch)); - addSwitch("exportmatlab", new FlagSwitch(() -> { + }, " ", "Export state/transition rewards to files 1/2"); + registry.addSwitch("exportstates", new StringSwitch(s -> modelExportTasks.add( + new ModelExportTask(ModelExportTask.ModelExportEntity.STATES, s))), + "", "Export the list of reachable states to a file"); + registry.addSwitch("exportobs", new StringSwitch(s -> modelExportTasks.add( + new ModelExportTask(ModelExportTask.ModelExportEntity.OBSERVATIONS, s))), + "", "Export the list of observations to a file"); + StringPlusOptionsSwitch exportLabelsSwitch = new StringPlusOptionsSwitch( + new OptionParser() + .flag("matlab", "export data in Matlab format", () -> pendingLabelExportTask.getExportOptions().setFormat(ModelExportFormat.MATLAB)) + .flag("proplabels", "export labels from a properties file into the same file, too",() -> pendingLabelExportTask.setLabelExportSet(ModelExportTask.LabelExportSet.ALL)), + this::processExportLabelsSwitch); + registry.addSwitch("exportlabels", exportLabelsSwitch, + "", "Export the list of labels and satisfying states to a file", + log -> { + log.println("Switch: -exportlabels \n"); + log.println("Export the list of labels and satisfying states to a file (or to the screen if =\"stdout\")."); + log.println(); + log.println("If provided, is a comma-separated list of options taken from:"); + exportLabelsSwitch.printOptions(log); + }); + StringPlusOptionsSwitch exportPropLabelsSwitch = new StringPlusOptionsSwitch( + new OptionParser() + .flag("matlab", "export data in Matlab format", () -> pendingLabelExportTask.getExportOptions().setFormat(ModelExportFormat.MATLAB)), + this::processExportPropLabelsSwitch); + registry.addSwitch("exportproplabels", exportPropLabelsSwitch, + "", "Export the list of labels and satisfying states from the properties file to a file", + log -> { + log.println("Switch: -exportproplabels \n"); + log.println("Export the list of labels and satisfying states from the properties file to a file (or to the screen if =\"stdout\")."); + log.println(); + log.println("If provided, is a comma-separated list of options taken from:"); + exportPropLabelsSwitch.printOptions(log); + }); + StringPlusOptionsSwitch exportStratSwitch = new StringPlusOptionsSwitch( + new OptionParser() + .choice("type", "type of strategy export", new OptionParser.Choice() + .when("actions", () -> exportStratOptions.setType(StrategyExportOptions.StrategyExportType.ACTIONS)) + .when("indices", () -> exportStratOptions.setType(StrategyExportOptions.StrategyExportType.INDICES)) + .when("induced", "model", () -> exportStratOptions.setType(StrategyExportOptions.StrategyExportType.INDUCED_MODEL)) + .when("dot", () -> exportStratOptions.setType(StrategyExportOptions.StrategyExportType.DOT_FILE))) + .choice("mode", "mode to use for building induced model (or Dot file)", new OptionParser.Choice() + .when("restrict", () -> exportStratOptions.setMode(StrategyExportOptions.InducedModelMode.RESTRICT)) + .when("reduce", () -> exportStratOptions.setMode(StrategyExportOptions.InducedModelMode.REDUCE))) + .bool("reach", "whether to restrict the strategy to its reachable states", v -> exportStratOptions.setReachOnly(v)) + .bool("states", "whether to show states, rather than state indices, for actions lists or Dot files", v -> exportStratOptions.setShowStates(v)) + .bool("obs", "for partially observable models, whether to merge observationally equivalent states", v -> exportStratOptions.setMergeObservations(v)), + this::processExportStratSwitch); + registry.addSwitch("exportstrat", exportStratSwitch, + "", "Generate and export a strategy to a file", + log -> { + log.println("Switch: -exportstrat \n"); + log.println("Generate and export a strategy to a file (or to the screen if =\"stdout\")."); + log.println("Use file extension .tra or .dot to export as an induced model or Dot file, respectively."); + log.println("If provided, is a comma-separated list of options taken from:"); + exportStratSwitch.printOptions(log); + }); + registry.addSwitch("exportmatlab", new FlagSwitch(() -> { exportType = Prism.EXPORT_MATLAB; modelExportOptionsGlobal.setFormat(ModelExportFormat.MATLAB); - })); - addSwitch("exportmrmc", new FlagSwitch(() -> errorAndExit("Export to MRMC format no longer supported"))); - addSwitch("exportrows", new FlagSwitch(() -> { + }), "", "When exporting matrices/vectors/labels/etc., use Matlab format"); + registry.addSwitch("exportrows", new FlagSwitch(() -> { exportType = Prism.EXPORT_ROWS; modelExportOptionsGlobal.setExplicitRows(true); - })); - addSwitch("exportordered", "ordered", new FlagSwitch(() -> {})); // always done now, no-op - addSwitch("exportunordered", "unordered", new FlagSwitch(() -> errorAndExit("Switch -exportunordered is no longer supported"))); - addSwitch("exporttransdot", new StringSwitch(s -> { + }), "", "When exporting matrices, put a whole row on one line"); + registry.addSwitch("exporttransdot", new StringSwitch(s -> { ModelExportOptions exportOptions = new ModelExportOptions().setFormat(ModelExportFormat.DOT).setShowStates(false).setShowObservations(false); modelExportTasks.add(new ModelExportTask(ModelExportTask.ModelExportEntity.MODEL, s, exportOptions)); - })); - addSwitch("exporttransdotstates", new StringSwitch(s -> { + }), "", "Export the transition matrix graph to a dot file"); + registry.addSwitch("exporttransdotstates", new StringSwitch(s -> { ModelExportOptions exportOptions = new ModelExportOptions().setFormat(ModelExportFormat.DOT).setShowStates(true).setShowObservations(true); modelExportTasks.add(new ModelExportTask(ModelExportTask.ModelExportEntity.MODEL, s, exportOptions)); - })); - addSwitch("exportdot", new StringSwitch(s -> { + }), "", "Export the transition matrix graph to a dot file, with state info"); + registry.addSwitch("exportdot", new StringSwitch(s -> { ModelExportOptions exportOptions = new ModelExportOptions().setFormat(ModelExportFormat.DD_DOT); modelExportTasks.add(new ModelExportTask(ModelExportTask.ModelExportEntity.MODEL, s, exportOptions)); - })); - addSwitch("exportsccs", (sw, a) -> { exportsccs = true; exportSCCsFilename = a.next(sw); }); - addSwitch("exportbsccs", (sw, a) -> { exportbsccs = true; exportBSCCsFilename = a.next(sw); }); - addSwitch("exportmecs", (sw, a) -> { exportmecs = true; exportMECsFilename = a.next(sw); }); - addSwitch("exportsteadystate", "exportss", (sw, a) -> { + }), "", "Export the transition matrix MTBDD to a dot file"); + registry.addSwitch("exportsccs", (sw, a) -> { exportsccs = true; exportSCCsFilename = a.next(sw); }, + "", "Compute and export all SCCs of the model"); + registry.addSwitch("exportbsccs", (sw, a) -> { exportbsccs = true; exportBSCCsFilename = a.next(sw); }, + "", "Compute and export all BSCCs of the model"); + registry.addSwitch("exportmecs", (sw, a) -> { exportmecs = true; exportMECsFilename = a.next(sw); }, + "", "Compute and export all maximal end components (MDPs only)"); + SwitchHandler exportSteadyStateHandler = (sw, a) -> { exportSteadyStateFilename = a.next(sw); steadystate = true; // compute if asked to export - }); - addSwitch("exporttransient", "exporttr", new StringSwitch(s -> exportTransientFilename = s)); - addSwitch("exportstrat", new StringSwitch(this::processExportStratSwitch)); - addSwitch("exportdigital", new StringSwitch(s -> { + }; + registry.addSwitch("exportsteadystate", exportSteadyStateHandler, + "", "Export steady-state probabilities to a file"); + registry.addSwitch("exportss", exportSteadyStateHandler); // hidden alias + SwitchHandler exportTransientHandler = new StringSwitch(s -> exportTransientFilename = s); + registry.addSwitch("exporttransient", exportTransientHandler, + "", "Export transient probabilities to a file"); + registry.addSwitch("exporttr", exportTransientHandler); // hidden alias + registry.addSwitch("exportprism", (sw, a) -> { exportprism = true; exportPrismFilename = a.next(sw); }, + "", "Export final PRISM model to a file"); + registry.addSwitch("exportprismconst", (sw, a) -> { exportprismconst = true; exportPrismConstFilename = a.next(sw); }, + "", "Export final PRISM model with expanded constants to a file"); + + // Hidden export switches + registry.addSwitch("exportmrmc", new FlagSwitch(() -> errorAndExit("Export to MRMC format no longer supported"))); + registry.addSwitch("exportordered", "ordered", new FlagSwitch(() -> {})); // always done now, no-op + registry.addSwitch("exportunordered", "unordered", new FlagSwitch(() -> errorAndExit("Switch -exportunordered is no longer supported"))); + registry.addSwitch("exportdigital", new StringSwitch(s -> { File f = s.equals("stdout") ? null : new File(s); prism.setExportDigital(true); prism.setExportDigitalFile(f); })); - addSwitch("exporttarget", new StringSwitch(s -> { // hidden + registry.addSwitch("exporttarget", new StringSwitch(s -> { prism.setExportTarget(true); prism.setExportTargetFilename(s); })); - addSwitch("exportprodtrans", new StringSwitch(s -> { // hidden + registry.addSwitch("exportprodtrans", new StringSwitch(s -> { prism.setExportProductTrans(true); prism.setExportProductTransFilename(s); })); - addSwitch("exportprodstates", new StringSwitch(s -> { // hidden + registry.addSwitch("exportprodstates", new StringSwitch(s -> { prism.setExportProductStates(true); prism.setExportProductStatesFilename(s); })); - addSwitch("exportprodvector", new StringSwitch(s -> { // hidden + registry.addSwitch("exportprodvector", new StringSwitch(s -> { prism.setExportProductVector(true); prism.setExportProductVectorFilename(s); })); - // NB: Following the ordering of the -help text, more options go here, - // but these are processed in the PrismSettings class; see below. - - // SIMULATION OPTIONS: - addSwitch("sim", new FlagSwitch(() -> simulate = true)); - addSwitch("simmethod", new EnumSwitch() + // ── PrismSettings sections (EXPORT OPTIONS through FAU OPTIONS) ─────── + // The -param handler captures PrismCL state so is passed in explicitly. + SwitchHandler paramHandler = (sw, a) -> { + param = true; + String v = a.next(sw).trim(); + if ("".equals(paramSwitch)) paramSwitch = v; else paramSwitch += "," + v; + }; + prism.getSettings().registerSwitchHandlers(registry, prism, paramHandler); + + // ── SIMULATION OPTIONS ──────────────────────────────────────────────── + registry.beginGroup("SIMULATION OPTIONS"); + registry.addSwitch("sim", new FlagSwitch(() -> simulate = true), + "", "Use the PRISM simulator to approximate results of model checking"); + registry.addSwitch("simmethod", new EnumSwitch() .when("ci", () -> simMethodName = "ci") .when("aci", () -> simMethodName = "aci") .when("apmc", () -> simMethodName = "apmc") - .when("sprt", () -> simMethodName = "sprt")); - addSwitch("simsamples", (sw, a) -> { + .when("sprt", () -> simMethodName = "sprt"), + "", "Specify the method for approximate model checking (ci, aci, apmc, sprt)"); + registry.addSwitch("simsamples", (sw, a) -> { int n = a.nextInt(sw); if (n <= 0) errorAndExit("Invalid value for -" + sw + " switch"); simNumSamples = n; simNumSamplesGiven = true; - }); - addSwitch("simconf", (sw, a) -> { + }, "", "Set the number of samples for the simulator (CI/ACI/APMC methods)"); + registry.addSwitch("simconf", (sw, a) -> { double d = a.nextDouble(sw); if (d <= 0 || d >= 1) errorAndExit("Invalid value for -" + sw + " switch"); simConfidence = d; simConfidenceGiven = true; - }); - addSwitch("simwidth", (sw, a) -> { + }, "", "Set the confidence parameter for the simulator (CI/ACI/APMC methods)"); + registry.addSwitch("simwidth", (sw, a) -> { double d = a.nextDouble(sw); if (d <= 0) errorAndExit("Invalid value for -" + sw + " switch"); simWidth = d; simWidthGiven = true; - }); - addSwitch("simapprox", (sw, a) -> { + }, "", "Set the interval width for the simulator (CI/ACI methods)"); + registry.addSwitch("simapprox", (sw, a) -> { double d = a.nextDouble(sw); if (d <= 0) errorAndExit("Invalid value for -" + sw + " switch"); simApprox = d; simApproxGiven = true; - }); - addSwitch("simmanual", new FlagSwitch(() -> simManual = true)); - addSwitch("simvar", (sw, a) -> { + }, "", "Set the approximation parameter for the simulator (APMC method)"); + registry.addSwitch("simmanual", new FlagSwitch(() -> simManual = true), + "", "Do not use the automated way of deciding whether the variance is null or not"); + registry.addSwitch("simvar", (sw, a) -> { int n = a.nextInt(sw); if (n <= 0) errorAndExit("Invalid value for -" + sw + " switch"); reqIterToConclude = n; reqIterToConcludeGiven = true; - }); - addSwitch("simmaxrwd", (sw, a) -> { + }, "", "Set the minimum number of samples to know the variance is null or not"); + registry.addSwitch("simmaxrwd", (sw, a) -> { double d = a.nextDouble(sw); if (d <= 0.0) errorAndExit("Invalid value for -" + sw + " switch"); simMaxReward = d; simMaxRewardGiven = true; - }); - addSwitch("simpathlen", (sw, a) -> { + }, "", "Set the maximum reward -- useful to display the CI/ACI methods progress"); + registry.addSwitch("simpathlen", (sw, a) -> { long n = a.nextLong(sw); if (n <= 0) errorAndExit("Invalid value for -" + sw + " switch"); simMaxPath = n; simMaxPathGiven = true; - }); - - // FURTHER OPTIONS: - addSwitch("zerorewardcheck", new FlagSwitch(() -> prism.setCheckZeroLoops(true))); - addSwitch("explicitbuild", new FlagSwitch(() -> explicitbuild = true)); - addSwitch("explicitbuildtest", new FlagSwitch(() -> explicitbuildtest = true)); // hidden - - // MISCELLANEOUS HIDDEN OPTIONS: - addSwitch("mainlog", new StringSwitch(this::processMainLogSwitch)); - addSwitch("exportmodeldotview", new FlagSwitch(() -> exportmodeldotview = true)); - addSwitch("c1", new FlagSwitch(() -> prism.setConstruction(1))); - addSwitch("c2", new FlagSwitch(() -> prism.setConstruction(2))); - addSwitch("c3", new FlagSwitch(() -> prism.setConstruction(3))); - addSwitch("o1", new FlagSwitch(() -> { prism.setOrdering(1); orderingOverride = true; })); - addSwitch("o2", new FlagSwitch(() -> { prism.setOrdering(2); orderingOverride = true; })); - addSwitch("noreach", new FlagSwitch(() -> prism.setDoReach(false))); - addSwitch("nobscc", new FlagSwitch(() -> prism.setBSCCComp(false))); - addSwitch("frontier", new FlagSwitch(() -> prism.setReachMethod(Prism.REACH_FRONTIER))); - addSwitch("bfs", new FlagSwitch(() -> prism.setReachMethod(Prism.REACH_BFS))); - addSwitch("bisim", new FlagSwitch(() -> prism.setDoBisim(true))); - } - - /** Register a switch handler under a single name. */ - private void addSwitch(String name, SwitchHandler h) - { - switchHandlers.put(name, h); - } - - /** Register a switch handler under two names (primary + alias). */ - private void addSwitch(String n1, String n2, SwitchHandler h) - { - switchHandlers.put(n1, h); - switchHandlers.put(n2, h); - } - - /** Register a switch handler under three names (primary + two aliases). */ - private void addSwitch(String n1, String n2, String n3, SwitchHandler h) - { - switchHandlers.put(n1, h); - switchHandlers.put(n2, h); - switchHandlers.put(n3, h); + }, "", "Set the maximum path length for the simulator"); + + // Hidden miscellaneous switches + registry.addSwitch("explicitbuild", new FlagSwitch(() -> explicitbuild = true)); + registry.addSwitch("explicitbuildtest", new FlagSwitch(() -> explicitbuildtest = true)); + registry.addSwitch("mainlog", new StringSwitch(this::processMainLogSwitch)); + registry.addSwitch("exportmodeldotview", new FlagSwitch(() -> exportmodeldotview = true)); + registry.addSwitch("c1", new FlagSwitch(() -> prism.setConstruction(1))); + registry.addSwitch("c2", new FlagSwitch(() -> prism.setConstruction(2))); + registry.addSwitch("c3", new FlagSwitch(() -> prism.setConstruction(3))); + registry.addSwitch("o1", new FlagSwitch(() -> { prism.setOrdering(1); orderingOverride = true; })); + registry.addSwitch("o2", new FlagSwitch(() -> { prism.setOrdering(2); orderingOverride = true; })); + registry.addSwitch("noreach", new FlagSwitch(() -> prism.setDoReach(false))); + registry.addSwitch("nobscc", new FlagSwitch(() -> prism.setBSCCComp(false))); + registry.addSwitch("frontier", new FlagSwitch(() -> prism.setReachMethod(Prism.REACH_FRONTIER))); + registry.addSwitch("bfs", new FlagSwitch(() -> prism.setReachMethod(Prism.REACH_BFS))); + registry.addSwitch("bisim", new FlagSwitch(() -> prism.setDoBisim(true))); } /** @@ -1372,7 +1568,7 @@ private void processFileNames(List filenameArgs) throws PrismException if (filenameArgs.size() > 0) { modelFilename = filenameArgs.get(0); if (modelFilename.endsWith(".all")) { - processImportModelSwitch(modelFilename); + importModelSwitch.handleFilesOnly("importmodel", modelFilename); } } if (filenameArgs.size() > 1) { @@ -1382,28 +1578,25 @@ private void processFileNames(List filenameArgs) throws PrismException } /** Process the argument to the -exportresults switch. */ - private void processExportResultsSwitch(String filesOptionsString) throws PrismException + private void processExportResultsSwitch(String files, StringPlusOptionsSwitch.ParseCallback parse) throws PrismException { exportresults = true; - // Split into filename/options; also accept , as a legacy separator if : is absent - String[] halves = splitFilesAndOptions(filesOptionsString); - if (halves[1].isEmpty() && halves[0].indexOf(',') > -1) { - int comma = halves[0].indexOf(','); - halves[1] = halves[0].substring(comma + 1); - halves[0] = halves[0].substring(0, comma); - } - exportResultsFilename = halves[0]; + // Also accept , as a legacy separator if : is absent + String optionsStr = parse.options(); + String filesStr = files; + if (optionsStr.isEmpty() && files.indexOf(',') > -1) { + int comma = files.indexOf(','); + filesStr = files.substring(0, comma); + optionsStr = files.substring(comma + 1); + } + exportResultsFilename = filesStr; exportShape = ResultsExportShape.LIST_PLAIN; - boolean[] isCsv = {false}, isMatrix = {false}; - new OptionParser() - .flag("csv", () -> isCsv[0] = true) - .flag("matrix", () -> isMatrix[0] = true) - .flag("dataframe", () -> exportShape = ResultsExportShape.DATA_FRAME) - .flag("comment", () -> exportShape = ResultsExportShape.COMMENT) - .parse(halves[1], "exportresults"); + exportResultsCsv = false; + exportResultsMatrix = false; + parse.run(optionsStr); if (exportShape == ResultsExportShape.LIST_PLAIN) - exportShape = isCsv[0] ? (isMatrix[0] ? ResultsExportShape.MATRIX_CSV : ResultsExportShape.LIST_CSV) - : (isMatrix[0] ? ResultsExportShape.MATRIX_PLAIN : ResultsExportShape.LIST_PLAIN); + exportShape = exportResultsCsv ? (exportResultsMatrix ? ResultsExportShape.MATRIX_CSV : ResultsExportShape.LIST_CSV) + : (exportResultsMatrix ? ResultsExportShape.MATRIX_PLAIN : ResultsExportShape.LIST_PLAIN); } /** @@ -1412,12 +1605,8 @@ private void processExportResultsSwitch(String filesOptionsString) throws PrismE * because other individual switches (e.g. -importXXX) can later override * parts of the configurations set up here. */ - private void processImportModelSwitch(String filesOptionsString) throws PrismException + private void processImportModelSwitch(String filesString, StringPlusOptionsSwitch.ParseCallback parse) throws PrismException { - // Split into files/options (on :) - String halves[] = splitFilesAndOptions(filesOptionsString); - String filesString = halves[0]; - String optionsString = halves[1]; // Split files into basename/extensions int i = filesString.lastIndexOf('.'); String basename = i == -1 ? filesString : filesString.substring(0, i); @@ -1459,11 +1648,7 @@ private void processImportModelSwitch(String filesOptionsString) throws PrismExc } } // Process options - new OptionParser() - .choice("format", new OptionParser.Choice() - .when("explicit", () -> { for (ModelImportSource s : modelImportSources) s.format = ModelExportFormat.EXPLICIT; }) - .when("umb", () -> { for (ModelImportSource s : modelImportSources) s.format = ModelExportFormat.UMB; })) - .parse(optionsString, "importmodel"); + parse.run(); } /** @@ -1546,31 +1731,22 @@ private void addTransitionRewardImports(String basename, boolean assumeExists) /** * Process the arguments (file, options) to the -exportlabels switch. */ - private void processExportLabelsSwitch(String filesOptionsString) throws PrismException + private void processExportLabelsSwitch(String file, StringPlusOptionsSwitch.ParseCallback parse) throws PrismException { - // Split into files/options (on :) - String pair[] = splitFilesAndOptions(filesOptionsString); - ModelExportTask newExportTask = new ModelExportTask(ModelExportTask.ModelExportEntity.LABELS, pair[0]); - new OptionParser() - .flag("matlab", () -> newExportTask.getExportOptions().setFormat(ModelExportFormat.MATLAB)) - .flag("proplabels", () -> newExportTask.setLabelExportSet(ModelExportTask.LabelExportSet.ALL)) - .parse(pair[1], "exportlabels"); - modelExportTasks.add(newExportTask); + pendingLabelExportTask = new ModelExportTask(ModelExportTask.ModelExportEntity.LABELS, file); + parse.run(); + modelExportTasks.add(pendingLabelExportTask); } /** * Process the arguments (file, options) to the -exportproplabels switch. */ - private void processExportPropLabelsSwitch(String filesOptionsString) throws PrismException + private void processExportPropLabelsSwitch(String file, StringPlusOptionsSwitch.ParseCallback parse) throws PrismException { - // Split into files/options (on :) - String pair[] = splitFilesAndOptions(filesOptionsString); - ModelExportTask newExportTask = new ModelExportTask(ModelExportTask.ModelExportEntity.LABELS, pair[0]); - newExportTask.setLabelExportSet(ModelExportTask.LabelExportSet.EXTRA); - new OptionParser() - .flag("matlab", () -> newExportTask.getExportOptions().setFormat(ModelExportFormat.MATLAB)) - .parse(pair[1], "exportproplabels"); - modelExportTasks.add(newExportTask); + pendingLabelExportTask = new ModelExportTask(ModelExportTask.ModelExportEntity.LABELS, file); + pendingLabelExportTask.setLabelExportSet(ModelExportTask.LabelExportSet.EXTRA); + parse.run(); + modelExportTasks.add(pendingLabelExportTask); } /** @@ -1579,12 +1755,8 @@ private void processExportPropLabelsSwitch(String filesOptionsString) throws Pri * because other individual switches (e.g. -exportmatlab) can later override * parts of the configurations set up here. */ - private void processExportModelSwitch(String filesOptionsString) throws PrismException + private void processExportModelSwitch(String filesString, StringPlusOptionsSwitch.ParseCallback parse) throws PrismException { - // Split into files/options (on :) - String halves[] = splitFilesAndOptions(filesOptionsString); - String filesString = halves[0]; - String optionsString = halves[1]; // Split files into basename/extensions int i = filesString.lastIndexOf('.'); String basename = i == -1 ? filesString : filesString.substring(0, i); @@ -1611,46 +1783,12 @@ private void processExportModelSwitch(String filesOptionsString) throws PrismExc } } // Process options - ModelExportOptions exportOptions = new ModelExportOptions(); - new OptionParser() - .flag("matlab", () -> { exportOptions.setFormat(ModelExportFormat.MATLAB); exportType = Prism.EXPORT_MATLAB; }) - .flag("rows", () -> { exportOptions.setExplicitRows(true); exportType = Prism.EXPORT_ROWS; }) - .flag("text", () -> exportOptions.setBinaryAsText(true)) - .flag("proplabels", () -> { - for (ModelExportTask t : newModelExportTasks) - if (t.getEntity() == ModelExportTask.ModelExportEntity.LABELS) - t.setLabelExportSet(ModelExportTask.LabelExportSet.ALL); - }) - .choice("format", new OptionParser.Choice() - .when("explicit", () -> exportOptions.setFormat(ModelExportFormat.EXPLICIT)) - .when("matlab", () -> exportOptions.setFormat(ModelExportFormat.MATLAB)) - .when("dot", () -> exportOptions.setFormat(ModelExportFormat.DOT)) - .when("drn", () -> exportOptions.setFormat(ModelExportFormat.DRN)) - .when("umb", () -> exportOptions.setFormat(ModelExportFormat.UMB))) - .bool("labels", v -> exportOptions.setShowLabels(v)) - .bool("rewards", v -> exportOptions.setShowRewards(v)) - .bool("states", v -> exportOptions.setShowStates(v)) - .bool("obs", v -> exportOptions.setShowObservations(v)) - .bool("actions", v -> exportOptions.setShowActions(v)) - .bool("headers", v -> exportOptions.setPrintHeaders(v)) - .string("precision", v -> { - try { - int n = Integer.parseInt(v); - if (!RANGE_EXPORT_DOUBLE_PRECISION.contains(n)) throw new NumberFormatException(); - exportOptions.setModelPrecision(n); - } catch (NumberFormatException e) { - throw new PrismException("Invalid value \"" + v + "\" for \"precision\" option of -exportmodel"); - } - }) - .choice("zip", new OptionParser.Choice() - .when("true", () -> exportOptions.setZipped(true)) - .when("false", () -> exportOptions.setZipped(false)) - .when("gzip", "gz", () -> exportOptions.setZipped(true).setCompressionFormat(ModelExportOptions.CompressionFormat.GZIP)) - .when("xz", () -> exportOptions.setZipped(true).setCompressionFormat(ModelExportOptions.CompressionFormat.XZ))) - .parse(optionsString, "exportmodel"); + pendingExportOptions = new ModelExportOptions(); + pendingExportTasks = newModelExportTasks; + parse.run(); // Apply options from this switch to each export task for (ModelExportTask exportTask : newModelExportTasks) { - exportTask.getExportOptions().apply(exportOptions); + exportTask.getExportOptions().apply(pendingExportOptions); } // Add export tasks to the main list modelExportTasks.addAll(newModelExportTasks); @@ -1659,12 +1797,8 @@ private void processExportModelSwitch(String filesOptionsString) throws PrismExc /** * Process the arguments (files, options) to the -exportstrat switch */ - private void processExportStratSwitch(String filesOptionsString) throws PrismException + private void processExportStratSwitch(String fileString, StringPlusOptionsSwitch.ParseCallback parse) throws PrismException { - // Split into files/options (on :) - String halves[] = splitFilesAndOptions(filesOptionsString); - String fileString = halves[0]; - String optionsString = halves[1]; // Split file into basename/extension int i = fileString.lastIndexOf('.'); String basename = i == -1 ? fileString : fileString.substring(0, i); @@ -1683,19 +1817,7 @@ private void processExportStratSwitch(String filesOptionsString) throws PrismExc exportStratOptions.setType(StrategyExportOptions.StrategyExportType.ACTIONS); } // Process options - new OptionParser() - .choice("type", new OptionParser.Choice() - .when("actions", () -> exportStratOptions.setType(StrategyExportOptions.StrategyExportType.ACTIONS)) - .when("indices", () -> exportStratOptions.setType(StrategyExportOptions.StrategyExportType.INDICES)) - .when("model", "induced", () -> exportStratOptions.setType(StrategyExportOptions.StrategyExportType.INDUCED_MODEL)) - .when("dot", () -> exportStratOptions.setType(StrategyExportOptions.StrategyExportType.DOT_FILE))) - .choice("mode", new OptionParser.Choice() - .when("restrict", () -> exportStratOptions.setMode(StrategyExportOptions.InducedModelMode.RESTRICT)) - .when("reduce", () -> exportStratOptions.setMode(StrategyExportOptions.InducedModelMode.REDUCE))) - .bool("reach", v -> exportStratOptions.setReachOnly(v)) - .bool("states", v -> exportStratOptions.setShowStates(v)) - .bool("obs", v -> exportStratOptions.setMergeObservations(v)) - .parse(optionsString, "exportstrat"); + parse.run(); } /** @@ -1704,7 +1826,7 @@ private void processExportStratSwitch(String filesOptionsString) throws PrismExc private void processMainLogSwitch(String filesOptionsString) throws PrismException { // Split into file/options (on :) - String halves[] = splitFilesAndOptions(filesOptionsString); + String halves[] = StringPlusOptionsSwitch.splitFilesAndOptions(filesOptionsString); String filename = halves[0]; String optionsString = halves[1]; // Process options @@ -1721,30 +1843,6 @@ private void processMainLogSwitch(String filesOptionsString) throws PrismExcepti } } - /** - * Split a string of the form : into its two parts. - * The latter can be empty, in which case the : is optional. - * Instances of :\ are ignored (not treated as :) in case there is a Windows filename. - * @return the two parts as an array of two strings. - */ - private static String[] splitFilesAndOptions(String filesOptionsString) - { - String res[] = new String[2]; - // Split into files/options (on :) - int i = filesOptionsString.indexOf(':'); - while (filesOptionsString.length() > i + 1 && filesOptionsString.charAt(i + 1) == '\\') { - i = filesOptionsString.indexOf(':', i + 1); - } - if (i != -1) { - res[0] = filesOptionsString.substring(0, i); - res[1] = filesOptionsString.substring(i + 1); - } else { - res[0] = filesOptionsString; - res[1] = ""; - } - return res; - } - // print command line arguments public void printArguments(String[] args) @@ -1997,6 +2095,23 @@ else if (simMethodName.equals("sprt")) { /** * Print a -help message, i.e. a list of the command-line switches. */ + /** Build the left column of a {@code -help} line: {@code -name (or -alias, ...)}. */ + private static String buildHelpLeft(SwitchEntry entry) + { + StringBuilder left = new StringBuilder("-").append(entry.primaryName); + if (!entry.argHint.isEmpty()) + left.append(" ").append(entry.argHint); + if (entry.shownAliases.length > 0) { + left.append(" (or "); + for (int i = 0; i < entry.shownAliases.length; i++) { + if (i > 0) left.append(", "); + left.append("-").append(entry.shownAliases[i]); + } + left.append(")"); + } + return left.toString(); + } + private void printHelp() { mainLog.println("Usage: " + Prism.getCommandLineName() + " [options] [] [more-options]"); @@ -2004,211 +2119,46 @@ private void printHelp() mainLog.println("Options:"); mainLog.println("========"); mainLog.println(); - mainLog.println("-help .......................... Display this help message"); - mainLog.println("-version ....................... Display PRISM version info"); - mainLog.println("-javaversion ................... Display Java version info"); - mainLog.println("-dir ..................... Set current working directory"); - mainLog.println("-settings ................ Load settings from "); - mainLog.println(); - mainLog.println("-pf (or -pctl or -csl) . Model check properties "); - mainLog.println("-property (or -prop) .... Only model check properties included in list of indices/names"); - mainLog.println("-const .................. Define constant values as (e.g. for experiments)"); - mainLog.println("-steadystate (or -ss) .......... Compute steady-state probabilities (D/CTMCs only)"); - mainLog.println("-transient (or -tr ) .... Compute transient probabilities for time (or time range) (D/CTMCs only)"); - mainLog.println("-simpath ....... Generate a random path with the simulator"); - mainLog.println("-nobuild ....................... Skip model construction (just do parse/export)"); - mainLog.println("-test .......................... Enable \"test\" mode"); - mainLog.println("-testall ....................... Enable \"test\" mode, but don't exit on error"); - mainLog.println("-javamaxmem ................. Set the maximum heap size for Java, e.g. 500m, 4g [default: 1g]"); - mainLog.println("-javastack ................. Set the Java stack size [default: 4m]"); - mainLog.println("-javaparams ................. Pass additional command-line arguments to Java"); - mainLog.println("-timeout ................... Exit after a time-out of seconds if not already terminated"); - mainLog.println("-ng ............................ Run PRISM in Nailgun server mode; subsequent calls are then made via \"ngprism\""); - mainLog.println(); - mainLog.println("IMPORTS:"); - mainLog.println("-importpepa .................... Model description is in PEPA, not the PRISM language"); - mainLog.println("-importmodel ........... Import the model directly from text file(s)"); - mainLog.println("-importtrans ............ Import the transition matrix directly from a text file"); - mainLog.println("-importstates ............ Import the list of states directly from a text file"); - mainLog.println("-importobs ............... Import the list of observations directly from a text file"); - mainLog.println("-importlabels ............ Import the list of labels directly from a text file"); - mainLog.println("-importstaterewards ...... Import the state rewards directly from a text file"); - mainLog.println("-importtransrewards ...... Import the transition rewards directly from a text file"); - mainLog.println("-importinitdist .......... Specify initial probability distribution for transient/steady-state analysis"); - mainLog.println("-dtmc .......................... Force imported/built model to be a DTMC"); - mainLog.println("-ctmc .......................... Force imported/built model to be a CTMC"); - mainLog.println("-mdp ........................... Force imported/built model to be an MDP"); - mainLog.println("-importresults .......... Import results from a data frame stored in CSV file"); - mainLog.println(); - mainLog.println("EXPORTS:"); - mainLog.println("-exportresults Export the results of model checking to a file"); - mainLog.println("-exportvector .......... Export results of model checking for all states to a file"); - mainLog.println("-exportmodel . Export the built model to file(s)"); - mainLog.println("-exporttrans ............ Export the transition matrix to a file"); - mainLog.println("-exportstaterewards ..... Export the state rewards vector to a file"); - mainLog.println("-exporttransrewards ..... Export the transition rewards matrix to a file"); - mainLog.println("-exportrewards .. Export state/transition rewards to files 1/2"); - mainLog.println("-exportstates ........... Export the list of reachable states to a file"); - mainLog.println("-exportobs .............. Export the list of observations to a file"); - mainLog.println("-exportlabels . Export the list of labels and satisfying states to a file"); - mainLog.println("-exportproplabels . Export the list of labels and satisfying states from the properties file to a file"); - mainLog.println("-exportstrat .. Generate and export a strategy to a file"); - mainLog.println("-exportmatlab .................. When exporting matrices/vectors/labels/etc., use Matlab format"); - mainLog.println("-exportrows .................... When exporting matrices, put a whole row on one line"); - mainLog.println("-exporttransdot ......... Export the transition matrix graph to a dot file"); - mainLog.println("-exporttransdotstates ... Export the transition matrix graph to a dot file, with state info"); - mainLog.println("-exportdot .............. Export the transition matrix MTBDD to a dot file"); - mainLog.println("-exportsccs ............. Compute and export all SCCs of the model"); - mainLog.println("-exportbsccs ............ Compute and export all BSCCs of the model"); - mainLog.println("-exportmecs ............. Compute and export all maximal end components (MDPs only)"); - mainLog.println("-exportsteadystate ...... Export steady-state probabilities to a file"); - mainLog.println("-exporttransient ........ Export transient probabilities to a file"); - mainLog.println("-exportprism ............ Export final PRISM model to a file"); - mainLog.println("-exportprismconst ....... Export final PRISM model with expanded constants to a file"); - - PrismSettings.printHelp(mainLog); - - mainLog.println(); - mainLog.println("SIMULATION OPTIONS:"); - mainLog.println("-sim ........................... Use the PRISM simulator to approximate results of model checking"); - mainLog.println("-simmethod .............. Specify the method for approximate model checking (ci, aci, apmc, sprt)"); - mainLog.println("-simsamples ................ Set the number of samples for the simulator (CI/ACI/APMC methods)"); - mainLog.println("-simconf ................... Set the confidence parameter for the simulator (CI/ACI/APMC methods)"); - mainLog.println("-simwidth .................. Set the interval width for the simulator (CI/ACI methods)"); - mainLog.println("-simapprox ................. Set the approximation parameter for the simulator (APMC method)"); - mainLog.println("-simmanual ..................... Do not use the automated way of deciding whether the variance is null or not"); - mainLog.println("-simvar .................... Set the minimum number of samples to know the variance is null or not"); - mainLog.println("-simmaxrwd ................. Set the maximum reward -- useful to display the CI/ACI methods progress"); - mainLog.println("-simpathlen ................ Set the maximum path length for the simulator"); - + // First pass: find the longest left column across all visible entries. + int maxLeft = 0; + Set counted = new HashSet<>(); + for (SwitchEntry entry : switchHandlers.values()) { + if (!counted.add(entry)) continue; + if (entry.primaryName == null || entry.argHint == null) continue; + maxLeft = Math.max(maxLeft, buildHelpLeft(entry).length()); + } + // Second pass: print with dot-padding aligned to the longest entry (minimum 1 dot). + String lastGroup = ""; // sentinel: different from null so first group change fires + Set seen = new HashSet<>(); + for (Map.Entry e : switchHandlers.entrySet()) { + SwitchEntry entry = e.getValue(); + if (!seen.add(entry)) continue; + if (entry.primaryName == null) { mainLog.println(); continue; } // blank-line sentinel + if (entry.argHint == null) continue; // hidden + if (!Objects.equals(entry.group, lastGroup)) { + if (entry.group != null) { + mainLog.println(); + mainLog.println(entry.group + ":"); + } + lastGroup = entry.group; + } + String left = buildHelpLeft(entry); + mainLog.println(left + " " + ".".repeat(maxLeft - left.length() + 1) + " " + entry.shortText); + } mainLog.println(); mainLog.println("You can also use \"prism -help xxx\" for help on some switches -xxx with non-obvious syntax."); } - /** - * Print a -help xxx message, i.e. display help on a specific switch - */ + /** Print a {@code -help } message: display detailed help on a specific switch. */ private void printHelpSwitch(String sw) { - // Remove "-" from start of switch, in case present (it shouldn't be really) if (sw.charAt(0) == '-') sw = sw.substring(1); - - // -const - if (sw.equals("const")) { - mainLog.println("Switch: -const \n"); - mainLog.println(" is a comma-separated list of values or value ranges for undefined constants"); - mainLog.println("in the model or properties (i.e. those declared without values, such as \"const int a;\")."); - mainLog.println("You can either specify a single value (a=1), a range (a=1:10) or a range with a step (a=1:2:50)."); - mainLog.println("For convenience, constant definutions can also be split across multiple -const switches."); - mainLog.println("\nExamples:"); - mainLog.println(" -const a=1,b=5.6,c=true"); - mainLog.println(" -const a=1:10,b=5.6"); - mainLog.println(" -const a=1:2:50,b=5.6"); - mainLog.println(" -const a=1:2:50 -const b=5.6"); - } - // -simpath - else if (sw.equals("simpath")) { - mainLog.println("Switch: -simpath \n"); - mainLog.println("Generate a random path with the simulator and export it to (or to the screen if =\"stdout\")."); - mainLog.println(" is a comma-separated list of options taken from:"); - GenerateSimulationPath.printOptions(mainLog); - } - // -importmodel - else if (sw.equals("importmodel")) { - mainLog.println("Switch: -importmodel [:options]\n"); - mainLog.println("Import the model directly from one or more file(s)."); - mainLog.println("Use a list of file extensions to indicate which files should be read, e.g.:"); - mainLog.println("\n -importmodel in.tra,sta\n"); - mainLog.println("Possible extensions are: .tra, .sta, .obs, .lab, .srew, .trew, .umb"); - mainLog.println("Use extension .all to import all explicit files (.tra/sta/obs/lab/srew/trew), e.g.:"); - mainLog.println("\n -importmodel in.all\n"); - mainLog.println("If provided, is a comma-separated list of options taken from:"); - mainLog.println(" * format (=explicit/umb) - model import format"); - } - // -importresults - else if (sw.equals("importresults")) { - mainLog.println("Switch: -importresults \n"); - mainLog.println("Import results from a data frame stored as comma-separated values in ."); - } - // -exportresults - else if (sw.equals("exportresults")) { - mainLog.println("Switch: -exportresults \n"); - mainLog.println("Exports the results of model checking to (or to the screen if =\"stdout\")."); - mainLog.println("The default behaviour is to export a list of results in text form, using tabs to separate items."); - mainLog.println("If provided, is a comma-separated list of options taken from:"); - mainLog.println(" * csv - Export results as comma-separated values"); - mainLog.println(" * matrix - Export results as one or more 2D matrices (e.g. for surface plots)"); - mainLog.println(" * dataframe - Export results as dataframe in comma-separated values)"); - mainLog.println(" * comment - Export results in comment format for regression testing)"); - } - // -exportlabels - else if (sw.equals("exportlabels")) { - mainLog.println("Switch: -exportlabels \n"); - mainLog.println("Export the list of labels and satisfying states to a file (or to the screen if =\"stdout\")."); - mainLog.println(); - mainLog.println("If provided, is a comma-separated list of options taken from:"); - mainLog.println(" * matlab - export data in Matlab format"); - mainLog.println(" * proplabels - export labels from a properties file into the same file, too"); - } - // -exportproplabels - else if (sw.equals("exportproplabels")) { - mainLog.println("Switch: -exportproplabels \n"); - mainLog.println("Export the list of labels and satisfying states from the properties file to a file (or to the screen if =\"stdout\")."); - mainLog.println(); - mainLog.println("If provided, is a comma-separated list of options taken from:"); - mainLog.println(" * matlab - export data in Matlab format"); - } - // -exportmodel - else if (sw.equals("exportmodel")) { - mainLog.println("Switch: -exportmodel \n"); - mainLog.println("Export the built model to file(s) (or to the screen if =\"stdout\")."); - mainLog.println("Use a list of file extensions to indicate which files should be generated, e.g.:"); - mainLog.println("\n -exportmodel out.tra,sta\n"); - mainLog.println("\n -exportmodel out.umb\n"); - mainLog.println("Possible extensions are: .tra, .srew, .trew, .lab, .sta, .obs, .dot, .umb, .drn"); - mainLog.println("Use extension .all to export all explicit files (.tra/srew/trew/lab/sta/obs), e.g.:"); - mainLog.println("\n -exportmodel out.all\n"); - mainLog.println("Omit the file basename to use the basename of the model file, e.g.:"); - mainLog.println("\n -exportmodel .all\n"); - mainLog.println("Use extension .rew to export both .srew/.trew files"); - mainLog.println(); - mainLog.println("If provided, is a comma-separated list of options taken from:"); - mainLog.println(" * format (=explicit/matlab/dot/drn/umb) - model export format"); - mainLog.println(" * rewards (=true/false) - whether to include rewards"); - mainLog.println(" * labels (=true/false) - whether to include labels"); - mainLog.println(" * states (=true/false) - whether to include state definitions"); - mainLog.println(" * obs (=true/false) - whether to include observation definitions"); - mainLog.println(" * actions (=true/false) - whether to include actions on choices/transitions"); - mainLog.println(" * precision (=) - use significant figures for floating point values (in text)"); - mainLog.println(" * zip (=true/false) - whether to zip UMB files"); - mainLog.println(" * text - show binary formats in textual form "); - mainLog.println(" * matlab - same as format=matlab"); - mainLog.println("For the explicit files format:"); - mainLog.println(" * rows - export matrices with one row/distribution on each line"); - mainLog.println(" * proplabels - also export labels from a properties file into the same file, too"); - mainLog.println(" * headers (=true/false) - include headers in explicit (reward) files"); - } - // -exportstrat - else if (sw.equals("exportstrat")) { - mainLog.println("Switch: -exportstrat \n"); - mainLog.println("Generate and export a strategy to a file (or to the screen if =\"stdout\")."); - mainLog.println("Use file extension .tra or .dot to export as an induced model or Dot file, respectively."); - mainLog.println("If provided, is a comma-separated list of options taken from:"); - mainLog.println(" * type (=actions/induced/dot) - type of strategy export"); - mainLog.println(" * mode (=restrict/reduce) - mode to use for building induced model (or Dot file)"); - mainLog.println(" * reach (=true/false) - whether to restrict the strategy to its reachable states"); - mainLog.println(" * states (=true/false) - whether to show states, rather than state indices, for actions lists or Dot files"); - mainLog.println(" * obs (=true/false) - for partially observable models, whether to merge observationally equivalent states"); - } - // Try PrismSettings - else if (PrismSettings.printHelpSwitch(mainLog, sw)) { - return; - } - // Unknown - else { + SwitchEntry entry = switchHandlers.get(sw); + if (entry != null && entry.longDesc != null) + entry.longDesc.accept(mainLog); + else mainLog.println("Sorry - no help available for switch -" + sw); - } } // print version diff --git a/prism/src/prism/PrismSettings.java b/prism/src/prism/PrismSettings.java index f27f8cb947..b7fac7fe03 100644 --- a/prism/src/prism/PrismSettings.java +++ b/prism/src/prism/PrismSettings.java @@ -948,9 +948,6 @@ public synchronized void loadDefaults() protected String exportPropAutType = "txt"; protected String exportPropAutFilename = "da.txt"; - // CLI switch handler map (populated lazily by initSwitchHandlers) - private Map settingsSwitchHandlers; - public void setExportPropAut(boolean b) throws PrismException { exportPropAut = b; @@ -982,76 +979,98 @@ public String getExportPropAutFilename() } /** - * Handle a command-line switch by dispatching to the registered settings handler. - * @param sw Switch name (without leading {@code -}, colon sub-options already in {@code consumer}) - * @param consumer Argument consumer positioned at the switch token + * Register all PrismSettings switch handlers with the unified CLI switch registry. + * Called from {@link PrismCL#initSwitchHandlers()} to contribute PrismSettings switches + * in the correct help-output position (between EXPORTS and SIMULATION OPTIONS). + * + * @param reg the shared switch registry + * @param prism the Prism instance (for switches that set Prism-level state) + * @param paramHandler PrismCL-owned handler for {@code -param}; registered here so that + * {@code -param} appears in the PARAMETRIC section of {@code -help} */ - public synchronized void setFromCommandLineSwitch(String sw, ArgConsumer consumer) throws PrismException - { - // Lazy init (method is synchronized, so no race condition) - if (settingsSwitchHandlers == null) initSwitchHandlers(); - - SwitchHandler handler = settingsSwitchHandlers.get(sw); - if (handler != null) { - handler.handle(sw, consumer); - return; - } - - throw new PrismException("Invalid switch -" + sw + " (type \"prism -help\" for full list)"); - } - - /** Populate {@link #settingsSwitchHandlers} with a handler for every switch recognised here. */ - private void initSwitchHandlers() + void registerSwitchHandlers(SwitchRegistry reg, Prism prism, SwitchHandler paramHandler) { - settingsSwitchHandlers = new LinkedHashMap<>(); + // ── EXPORT OPTIONS ─────────────────────────────────────────────────── + reg.beginGroup("EXPORT OPTIONS"); + reg.addSwitch("exportmodelprecision", (sw, a) -> { + int n = a.nextInt(sw); + if (!RANGE_EXPORT_DOUBLE_PRECISION.contains(n)) + throw new PrismException("Invalid value for -" + sw + " switch"); + set(PRISM_EXPORT_MODEL_PRECISION, n); + }, "", "Export probabilities/rewards with n significant decimal places"); + reg.addSwitch("noexportheaders", new FlagSwitch(() -> set(PRISM_EXPORT_MODEL_HEADERS, false)), + "", "Don't include headers when exporting rewards"); - // ENGINES/METHODS: - addSwitch("mtbdd", "m", new FlagSwitch(() -> set(PRISM_ENGINE, "MTBDD"))); - addSwitch("sparse", "s", new FlagSwitch(() -> set(PRISM_ENGINE, "Sparse"))); - addSwitch("hybrid", "h", new FlagSwitch(() -> set(PRISM_ENGINE, "Hybrid"))); - addSwitch("explicit", "ex", new FlagSwitch(() -> set(PRISM_ENGINE, "Explicit"))); - addSwitch("exact", new FlagSwitch(() -> set(PRISM_EXACT_ENABLED, true))); - addSwitch("ptamethod", new EnumSwitch() + // ── ENGINES/METHODS ────────────────────────────────────────────────── + reg.beginGroup("ENGINES/METHODS"); + reg.addSwitch("mtbdd", "m", new FlagSwitch(() -> set(PRISM_ENGINE, "MTBDD")), + "", "Use the MTBDD engine"); + reg.addSwitch("sparse", "s", new FlagSwitch(() -> set(PRISM_ENGINE, "Sparse")), + "", "Use the Sparse engine"); + reg.addSwitch("hybrid", "h", new FlagSwitch(() -> set(PRISM_ENGINE, "Hybrid")), + "", "Use the Hybrid engine [default]"); + reg.addSwitch("explicit", "ex", new FlagSwitch(() -> set(PRISM_ENGINE, "Explicit")), + "", "Use the explicit engine"); + reg.addSwitch("exact", new FlagSwitch(() -> set(PRISM_EXACT_ENABLED, true)), + "", "Perform exact (arbitrary precision) model checking"); + reg.addSwitch("ptamethod", new EnumSwitch() .when("digital", () -> set(PRISM_PTA_METHOD, "Digital clocks")) .when("games", () -> set(PRISM_PTA_METHOD, "Stochastic games")) - .when("backwards", "bw", () -> set(PRISM_PTA_METHOD, "Backwards reachability"))); - addSwitch("transientmethod", new EnumSwitch() + .when("backwards", "bw", () -> set(PRISM_PTA_METHOD, "Backwards reachability")), + "", "Specify PTA engine (games, digital, backwards) [default: games]"); + reg.addSwitch("transientmethod", new EnumSwitch() .when("unif", () -> set(PRISM_TRANSIENT_METHOD, "Uniformisation")) - .when("fau", () -> set(PRISM_TRANSIENT_METHOD, "Fast adaptive uniformisation"))); - addSwitch("heuristic", new EnumSwitch() + .when("fau", () -> set(PRISM_TRANSIENT_METHOD, "Fast adaptive uniformisation")), + "", "CTMC transient analysis method (unif, fau) [default: unif]"); + reg.addSwitch("heuristic", new EnumSwitch() .when("none", () -> set(PRISM_HEURISTIC, "None")) .when("speed", () -> set(PRISM_HEURISTIC, "Speed")) - .when("memory", () -> set(PRISM_HEURISTIC, "Memory"))); + .when("memory", () -> set(PRISM_HEURISTIC, "Memory")), + "", "Automatic choice of engines/settings (none, speed, memory) [default: none]"); - // NUMERICAL SOLUTION OPTIONS: - addSwitch("power", "pow", "pwr", new FlagSwitch(() -> set(PRISM_LIN_EQ_METHOD, "Power"))); - addSwitch("jacobi", "jac", new FlagSwitch(() -> set(PRISM_LIN_EQ_METHOD, "Jacobi"))); - addSwitch("gaussseidel", "gs", new FlagSwitch(() -> { + // ── SOLUTION METHODS (LINEAR EQUATIONS) ────────────────────────────── + reg.beginGroup("SOLUTION METHODS (LINEAR EQUATIONS)"); + reg.addSwitch("power", "pow", "pwr", new FlagSwitch(() -> set(PRISM_LIN_EQ_METHOD, "Power")), + "", "Use the Power method for numerical computation"); + reg.addSwitch("jacobi", "jac", new FlagSwitch(() -> set(PRISM_LIN_EQ_METHOD, "Jacobi")), + "", "Use Jacobi for numerical computation [default]"); + reg.addSwitch("gaussseidel", "gs", new FlagSwitch(() -> { set(PRISM_LIN_EQ_METHOD, "Gauss-Seidel"); set(PRISM_MDP_SOLN_METHOD, "Gauss-Seidel"); set(PRISM_MDP_MULTI_SOLN_METHOD, "Gauss-Seidel"); set(PRISM_IMDP_SOLN_METHOD, "Gauss-Seidel"); - })); - addSwitch("bgaussseidel", "bgs", new FlagSwitch(() -> set(PRISM_LIN_EQ_METHOD, "Backwards Gauss-Seidel"))); - addSwitch("pgaussseidel", "pgs", new FlagSwitch(() -> set(PRISM_LIN_EQ_METHOD, "Pseudo-Gauss-Seidel"))); - addSwitch("bpgaussseidel", "bpgs", new FlagSwitch(() -> set(PRISM_LIN_EQ_METHOD, "Backwards Pseudo-Gauss-Seidel"))); - addSwitch("jor", new FlagSwitch(() -> set(PRISM_LIN_EQ_METHOD, "JOR"))); - addSwitch("sor", new FlagSwitch(() -> set(PRISM_LIN_EQ_METHOD, "SOR"))); - addSwitch("bsor", new FlagSwitch(() -> set(PRISM_LIN_EQ_METHOD, "Backwards SOR"))); - addSwitch("psor", new FlagSwitch(() -> set(PRISM_LIN_EQ_METHOD, "Pseudo-SOR"))); - addSwitch("bpsor", new FlagSwitch(() -> set(PRISM_LIN_EQ_METHOD, "Backwards Pseudo-SOR"))); - addSwitch("valiter", new FlagSwitch(() -> { + }), "", "Use Gauss-Seidel for numerical computation"); + reg.addSwitch("bgaussseidel", "bgs", new FlagSwitch(() -> set(PRISM_LIN_EQ_METHOD, "Backwards Gauss-Seidel")), + "", "Use Backwards Gauss-Seidel for numerical computation"); + reg.addSwitch("pgaussseidel", "pgs", new FlagSwitch(() -> set(PRISM_LIN_EQ_METHOD, "Pseudo-Gauss-Seidel")), + "", "Use Pseudo Gauss-Seidel for numerical computation"); + reg.addSwitch("bpgaussseidel", "bpgs", new FlagSwitch(() -> set(PRISM_LIN_EQ_METHOD, "Backwards Pseudo-Gauss-Seidel")), + "", "Use Backwards Pseudo Gauss-Seidel for numerical computation"); + reg.addSwitch("jor", new FlagSwitch(() -> set(PRISM_LIN_EQ_METHOD, "JOR")), + "", "Use JOR for numerical computation"); + reg.addSwitch("sor", new FlagSwitch(() -> set(PRISM_LIN_EQ_METHOD, "SOR")), + "", "Use SOR for numerical computation"); + reg.addSwitch("bsor", new FlagSwitch(() -> set(PRISM_LIN_EQ_METHOD, "Backwards SOR")), + "", "Use Backwards SOR for numerical computation"); + reg.addSwitch("psor", new FlagSwitch(() -> set(PRISM_LIN_EQ_METHOD, "Pseudo-SOR")), + "", "Use Pseudo SOR for numerical computation"); + reg.addSwitch("bpsor", new FlagSwitch(() -> set(PRISM_LIN_EQ_METHOD, "Backwards Pseudo-SOR")), + "", "Use Backwards Pseudo SOR for numerical computation"); + reg.addSwitch("omega", new DoubleSwitch(d -> set(PRISM_LIN_EQ_METHOD_PARAM, d)), + "", "Set over-relaxation parameter (for JOR/SOR/...) [default: 0.9]"); + + // ── SOLUTION METHODS (MDPS) ─────────────────────────────────────────── + reg.beginGroup("SOLUTION METHODS (MDPS)"); + reg.addSwitch("valiter", new FlagSwitch(() -> { set(PRISM_MDP_SOLN_METHOD, "Value iteration"); set(PRISM_MDP_MULTI_SOLN_METHOD, "Value iteration"); set(PRISM_IMDP_SOLN_METHOD, "Value iteration"); - })); - addSwitch("politer", new FlagSwitch(() -> set(PRISM_MDP_SOLN_METHOD, "Policy iteration"))); - addSwitch("modpoliter", new FlagSwitch(() -> set(PRISM_MDP_SOLN_METHOD, "Modified policy iteration"))); - addSwitch("linprog", "lp", new FlagSwitch(() -> { - set(PRISM_MDP_SOLN_METHOD, "Linear programming"); - set(PRISM_MDP_MULTI_SOLN_METHOD, "Linear programming"); - })); - addSwitch("intervaliter", "ii", (sw, a) -> { + }), "", "Use value iteration for solving MDPs [default]"); + reg.addSwitch("politer", new FlagSwitch(() -> set(PRISM_MDP_SOLN_METHOD, "Policy iteration")), + "", "Use policy iteration for solving MDPs"); + reg.addSwitch("modpoliter", new FlagSwitch(() -> set(PRISM_MDP_SOLN_METHOD, "Modified policy iteration")), + "", "Use modified policy iteration for solving MDPs"); + reg.addSwitch("intervaliter", "ii", (sw, a) -> { set(PRISM_INTERVAL_ITER, true); String opts = a.optionsString(); if (opts != null) { @@ -1064,210 +1083,258 @@ private void initSwitchHandlers() String existing = getString(PRISM_INTERVAL_ITER_OPTIONS); set(PRISM_INTERVAL_ITER_OPTIONS, "".equals(existing) ? opts : existing + "," + opts); } - }); - addSwitch("pmaxquotient", new FlagSwitch(() -> set(PRISM_PMAX_QUOTIENT, true))); - addSwitch("topological", new FlagSwitch(() -> set(PRISM_TOPOLOGICAL_VI, true))); - addSwitch("omega", new DoubleSwitch(d -> set(PRISM_LIN_EQ_METHOD_PARAM, d))); - addSwitch("relative", "rel", new FlagSwitch(() -> set(PRISM_TERM_CRIT, "Relative"))); - addSwitch("absolute", "abs", new FlagSwitch(() -> set(PRISM_TERM_CRIT, "Absolute"))); - addSwitch("epsilon", "e", (sw, a) -> { + }, "", "Use interval iteration to solve MDPs/MCs (see -help -ii)", + log -> { + log.println("Switch: -intervaliter (or -ii) optionally takes a comma-separated list of options:\n"); + log.println(" -intervaliter:option1,option2,...\n"); + log.println("where the options are one of the following:\n"); + log.println(OptionsIntervalIteration.getOptionsDescription()); + }); + reg.addSwitch("topological", new FlagSwitch(() -> set(PRISM_TOPOLOGICAL_VI, true)), + "", "Use topological value iteration"); + + // ── SOLUTION METHOD SETTINGS ────────────────────────────────────────── + reg.beginGroup("SOLUTION METHOD SETTINGS"); + reg.addSwitch("relative", "rel", new FlagSwitch(() -> set(PRISM_TERM_CRIT, "Relative")), + "", "Use relative error for detecting convergence [default]"); + reg.addSwitch("absolute", "abs", new FlagSwitch(() -> set(PRISM_TERM_CRIT, "Absolute")), + "", "Use absolute error for detecting convergence"); + reg.addSwitch("epsilon", "e", (sw, a) -> { double d = a.nextDouble(sw); if (d < 0) throw new PrismException("Invalid value for -" + sw + " switch"); set(PRISM_TERM_CRIT_PARAM, d); - }); - addSwitch("maxiters", (sw, a) -> { + }, "", "Set value of epsilon (for convergence check) [default: 1e-6]"); + reg.addSwitch("maxiters", (sw, a) -> { int n = a.nextInt(sw); if (n < 0) throw new PrismException("Invalid value for -" + sw + " switch"); set(PRISM_MAX_ITERS, n); - }); - addSwitch("exportiterations", new FlagSwitch(() -> set(PRISM_EXPORT_ITERATIONS, true))); - addSwitch("gridresolution", (sw, a) -> { + }, "", "Set max number of iterations [default: 10000]"); + reg.addSwitch("gridresolution", (sw, a) -> { int n = a.nextInt(sw); if (n < 0) throw new PrismException("Invalid value for -" + sw + " switch"); set(PRISM_GRID_RESOLUTION, n); - }); - addSwitch("exportmodelprecision", (sw, a) -> { - int n = a.nextInt(sw); - if (!RANGE_EXPORT_DOUBLE_PRECISION.contains(n)) - throw new PrismException("Invalid value for -" + sw + " switch"); - set(PRISM_EXPORT_MODEL_PRECISION, n); - }); - addSwitch("noexportheaders", new FlagSwitch(() -> set(PRISM_EXPORT_MODEL_HEADERS, false))); + }, "", "Set resolution for fixed grid approximation (POMDP) [default: 10]"); - // MODEL CHECKING OPTIONS: - addSwitch("nopre", new FlagSwitch(() -> set(PRISM_PRECOMPUTATION, false))); - addSwitch("noprob0", new FlagSwitch(() -> set(PRISM_PROB0, false))); - addSwitch("noprob1", new FlagSwitch(() -> set(PRISM_PROB1, false))); - addSwitch("noprerel", new FlagSwitch(() -> set(PRISM_PRE_REL, false))); - addSwitch("fixdl", new FlagSwitch(() -> set(PRISM_FIX_DEADLOCKS, true))); - addSwitch("nofixdl", new FlagSwitch(() -> set(PRISM_FIX_DEADLOCKS, false))); - addSwitch("fair", new FlagSwitch(() -> set(PRISM_FAIRNESS, true))); - addSwitch("nofair", new FlagSwitch(() -> set(PRISM_FAIRNESS, false))); - addSwitch("noprobchecks", new FlagSwitch(() -> set(PRISM_DO_PROB_CHECKS, false))); - addSwitch("sumroundoff", (sw, a) -> { + // ── MODEL CHECKING OPTIONS ──────────────────────────────────────────── + reg.beginGroup("MODEL CHECKING OPTIONS"); + reg.addSwitch("nopre", new FlagSwitch(() -> set(PRISM_PRECOMPUTATION, false)), + "", "Skip precomputation algorithms (where optional)"); + reg.addSwitch("noprob0", new FlagSwitch(() -> set(PRISM_PROB0, false)), + "", "Skip precomputation algorithm Prob0 (where optional)"); + reg.addSwitch("noprob1", new FlagSwitch(() -> set(PRISM_PROB1, false)), + "", "Skip precomputation algorithm Prob1 (where optional)"); + reg.addSwitch("noprerel", new FlagSwitch(() -> set(PRISM_PRE_REL, false)), + "", "Do not pre-compute/use predecessor relation, e.g. for precomputation"); + reg.addSwitch("fair", new FlagSwitch(() -> set(PRISM_FAIRNESS, true)), + "", "Use fairness (for model checking of MDPs)"); + reg.addSwitch("nofair", new FlagSwitch(() -> set(PRISM_FAIRNESS, false)), + "", "Don't use fairness (for model checking of MDPs) [default]"); + reg.addSwitch("fixdl", new FlagSwitch(() -> set(PRISM_FIX_DEADLOCKS, true)), + "", "Automatically put self-loops in deadlock states [default]"); + reg.addSwitch("nofixdl", new FlagSwitch(() -> set(PRISM_FIX_DEADLOCKS, false)), + "", "Do not automatically put self-loops in deadlock states"); + reg.addSwitch("noprobchecks", new FlagSwitch(() -> set(PRISM_DO_PROB_CHECKS, false)), + "", "Disable checks on model probabilities/rates"); + reg.addSwitch("sumroundoff", (sw, a) -> { double d = a.nextDouble(sw); if (d < 0) throw new PrismException("Invalid value for -" + sw + " switch"); set(PRISM_SUM_ROUND_OFF, d); - }); - addSwitch("nossdetect", new FlagSwitch(() -> set(PRISM_DO_SS_DETECTION, false))); - addSwitch("sccmethod", "bsccmethod", new EnumSwitch() + }, "", "Set probability sum threshold [default: 1-e5]"); + reg.addSwitch("zerorewardcheck", new FlagSwitch(() -> prism.setCheckZeroLoops(true)), + "", "Check for absence of zero-reward loops"); + reg.addSwitch("nossdetect", new FlagSwitch(() -> set(PRISM_DO_SS_DETECTION, false)), + "", "Disable steady-state detection for CTMC transient computations"); + SwitchHandler sccMethodHandler = new EnumSwitch() .when("xiebeerel", () -> set(PRISM_SCC_METHOD, "Xie-Beerel")) .when("lockstep", () -> set(PRISM_SCC_METHOD, "Lockstep")) - .when("sccfind", () -> set(PRISM_SCC_METHOD, "SCC-Find"))); - addSwitch("symm", (sw, a) -> { + .when("sccfind", () -> set(PRISM_SCC_METHOD, "SCC-Find")); + reg.addSwitch("sccmethod", sccMethodHandler, + "", "Specify (symbolic) SCC computation method (xiebeerel, lockstep, sccfind)"); + reg.addSwitch("bsccmethod", sccMethodHandler); // hidden alias + reg.addSwitch("symm", (sw, a) -> { String p1 = a.next(sw); String p2 = a.next(sw); set(PRISM_SYMM_RED_PARAMS, p1 + " " + p2); - }); - addSwitch("aroptions", (sw, a) -> { + }, "", "Symmetry reduction options string"); + reg.addSwitch("aroptions", (sw, a) -> { String v = a.next(sw).trim(); String existing = getString(PRISM_AR_OPTIONS); set(PRISM_AR_OPTIONS, "".equals(existing) ? v : existing + "," + v); - }); - addSwitch("pathviaautomata", new FlagSwitch(() -> set(PRISM_PATH_VIA_AUTOMATA, true))); - addSwitch("nodasimplify", new FlagSwitch(() -> set(PRISM_NO_DA_SIMPLIFY, true))); + }, "", "Abstraction-refinement engine options string", + log -> { + log.println("Switch: -aroptions \n"); + log.println(" is a comma-separated list of options regarding abstraction-refinement:"); + QuantAbstractRefine.printOptions(log); + }); + reg.addSwitch("pathviaautomata", new FlagSwitch(() -> set(PRISM_PATH_VIA_AUTOMATA, true)), + "", "Handle all path formulas via automata constructions"); + reg.addSwitch("nodasimplify", new FlagSwitch(() -> set(PRISM_NO_DA_SIMPLIFY, true)), + "", "Do not attempt to simplify deterministic automata, acceptance conditions"); + reg.addSwitch("exportadv", (sw, a) -> { + set(PRISM_EXPORT_ADV, "DTMC"); + set(PRISM_EXPORT_ADV_FILENAME, a.next(sw)); + }, "", "Export an adversary from MDP model checking (as a DTMC)"); + reg.addSwitch("exportadvmdp", (sw, a) -> { + set(PRISM_EXPORT_ADV, "MDP"); + set(PRISM_EXPORT_ADV_FILENAME, a.next(sw)); + }, "", "Export an adversary from MDP model checking (as an MDP)"); + reg.addSwitch("ltl2datool", new StringSwitch(s -> set(PRISM_LTL2DA_TOOL, s)), + "", "Run executable to convert LTL formulas to deterministic automata"); + reg.addSwitch("ltl2dasyntax", new EnumSwitch() + .when("lbt", () -> set(PRISM_LTL2DA_SYNTAX, "LBT")) + .when("spin", () -> set(PRISM_LTL2DA_SYNTAX, "Spin")) + .when("spot", () -> set(PRISM_LTL2DA_SYNTAX, "Spot")) + .when("rabinizer", () -> set(PRISM_LTL2DA_SYNTAX, "Rabinizer")), + "", "Specify output format for -ltl2datool switch (lbt, spin, spot, rabinizer)"); + reg.addSwitch("exportiterations", new FlagSwitch(() -> set(PRISM_EXPORT_ITERATIONS, true)), + "", "Export vectors for iteration algorithms to file"); + reg.addSwitch("pmaxquotient", new FlagSwitch(() -> set(PRISM_PMAX_QUOTIENT, true)), + "", "For Pmax computations in MDPs, compute in the MEC quotient"); - // MULTI-OBJECTIVE MODEL CHECKING OPTIONS: - addSwitch("multimaxpoints", (sw, a) -> { + // ── MULTI-OBJECTIVE MODEL CHECKING ─────────────────────────────────── + reg.beginGroup("MULTI-OBJECTIVE MODEL CHECKING"); + reg.addSwitch("linprog", "lp", new FlagSwitch(() -> { + set(PRISM_MDP_SOLN_METHOD, "Linear programming"); + set(PRISM_MDP_MULTI_SOLN_METHOD, "Linear programming"); + }), "", "Use linear programming for multi-objective model checking"); + reg.addSwitch("multimaxpoints", (sw, a) -> { int n = a.nextInt(sw); if (n < 0) throw new PrismException("Invalid value for -" + sw + " switch"); set(PRISM_MULTI_MAX_POINTS, n); - }); - addSwitch("paretoepsilon", (sw, a) -> { + }, "", "Maximal number of corner points for (valiter-based) multi-objective"); + reg.addSwitch("paretoepsilon", (sw, a) -> { double d = a.nextDouble(sw); if (d < 0) throw new PrismException("Value for -" + sw + " switch must be non-negative"); set(PRISM_PARETO_EPSILON, d); - }); - addSwitch("exportpareto", new StringSwitch(s -> set(PRISM_EXPORT_PARETO_FILENAME, s))); + }, "", "Threshold for Pareto curve approximation"); + reg.addSwitch("exportpareto", new StringSwitch(s -> set(PRISM_EXPORT_PARETO_FILENAME, s)), + "", "When computing Pareto curves, export points to a file"); - // OUTPUT OPTIONS: - addSwitch("verbose", "v", new FlagSwitch(() -> set(PRISM_VERBOSE, true))); - addSwitch("extraddinfo", new FlagSwitch(() -> set(PRISM_EXTRA_DD_INFO, true))); - addSwitch("extrareachinfo", new FlagSwitch(() -> set(PRISM_EXTRA_REACH_INFO, true))); + // ── OUTPUT OPTIONS ──────────────────────────────────────────────────── + reg.beginGroup("OUTPUT OPTIONS"); + reg.addSwitch("verbose", "v", new FlagSwitch(() -> set(PRISM_VERBOSE, true)), + "", "Verbose mode: print out state lists and probability vectors"); + reg.addSwitch("extraddinfo", new FlagSwitch(() -> set(PRISM_EXTRA_DD_INFO, true)), + "", "Display extra info about some (MT)BDDs"); + reg.addSwitch("extrareachinfo", new FlagSwitch(() -> set(PRISM_EXTRA_REACH_INFO, true)), + "", "Display extra info about progress of reachability"); - // SPARSE/HYBRID/MTBDD OPTIONS: - addSwitch("nocompact", new FlagSwitch(() -> set(PRISM_COMPACT, false))); - addSwitch("sbl", (sw, a) -> { + // ── SPARSE/HYBRID/MTBDD OPTIONS ────────────────────────────────────── + reg.beginGroup("SPARSE/HYBRID/MTBDD OPTIONS"); + reg.addSwitch("nocompact", new FlagSwitch(() -> set(PRISM_COMPACT, false)), + "", "Switch off \"compact\" sparse storage schemes"); + reg.addSwitch("sbl", (sw, a) -> { int n = a.nextInt(sw); if (n < -1) throw new PrismException("Invalid value for -" + sw + " switch"); set(PRISM_NUM_SB_LEVELS, n); - }); - addSwitch("sbmax", (sw, a) -> { + }, "", "Set number of levels (for hybrid engine) [default: -1]"); + reg.addSwitch("sbmax", (sw, a) -> { int n = a.nextInt(sw); if (n < 0) throw new PrismException("Invalid value for -" + sw + " switch"); set(PRISM_SB_MAX_MEM, n); - }); - addSwitch("sorl", "gsl", (sw, a) -> { + }, "", "Set memory limit (KB) (for hybrid engine) [default: 1024]"); + reg.addSwitch("gsl", "sorl", (sw, a) -> { int n = a.nextInt(sw); if (n < -1) throw new PrismException("Invalid value for -" + sw + " switch"); set(PRISM_NUM_SOR_LEVELS, n); - }); - addSwitch("sormax", "gsmax", (sw, a) -> { + }, "", "Set number of levels for hybrid GS/SOR [default: -1]"); + reg.addSwitch("gsmax", "sormax", (sw, a) -> { int n = a.nextInt(sw); if (n < 0) throw new PrismException("Invalid value for -" + sw + " switch"); set(PRISM_SOR_MAX_MEM, n); - }); - addSwitch("cuddmaxmem", new StringSwitch(s -> set(PRISM_CUDD_MAX_MEM, s))); - addSwitch("cuddepsilon", (sw, a) -> { + }, "", "Set memory limit (KB) for hybrid GS/SOR [default: 1024]"); + reg.addSwitch("cuddmaxmem", new StringSwitch(s -> set(PRISM_CUDD_MAX_MEM, s)), + "", "Set max memory for CUDD package, e.g. 125k, 50m, 4g [default: 1g]"); + reg.addSwitch("cuddepsilon", (sw, a) -> { double d = a.nextDouble(sw); if (d < 0) throw new PrismException("Invalid value for -" + sw + " switch"); set(PRISM_CUDD_EPSILON, d); - }); - addSwitch("ddextrastatevars", (sw, a) -> { + }, "", "Set epsilon value for CUDD package [default: 1e-15]"); + reg.addSwitch("ddsanity", new FlagSwitch(() -> set(PRISM_JDD_SANITY_CHECKS, true)), + "", "Enable internal sanity checks (causes slow-down)"); + reg.addSwitch("ddextrastatevars", (sw, a) -> { int n = a.nextInt(sw); if (n < 0) throw new PrismException("Invalid value for -" + sw + " switch"); set(PRISM_DD_EXTRA_STATE_VARS, n); - }); - addSwitch("ddextraactionvars", (sw, a) -> { + }, "", "Set the number of preallocated state vars [default: 20]"); + reg.addSwitch("ddextraactionvars", (sw, a) -> { int n = a.nextInt(sw); if (n < 0) throw new PrismException("Invalid value for -" + sw + " switch"); set(PRISM_DD_EXTRA_ACTION_VARS, n); - }); + }, "", "Set the number of preallocated action vars [default: 20]"); - // ADVERSARIES/COUNTEREXAMPLES: - addSwitch("exportadv", (sw, a) -> { - set(PRISM_EXPORT_ADV, "DTMC"); - set(PRISM_EXPORT_ADV_FILENAME, a.next(sw)); - }); - addSwitch("exportadvmdp", (sw, a) -> { - set(PRISM_EXPORT_ADV, "MDP"); - set(PRISM_EXPORT_ADV_FILENAME, a.next(sw)); - }); - - // LTL2DA TOOLS: - addSwitch("ltl2datool", new StringSwitch(s -> set(PRISM_LTL2DA_TOOL, s))); - addSwitch("ltl2dasyntax", new EnumSwitch() - .when("lbt", () -> set(PRISM_LTL2DA_SYNTAX, "LBT")) - .when("spin", () -> set(PRISM_LTL2DA_SYNTAX, "Spin")) - .when("spot", () -> set(PRISM_LTL2DA_SYNTAX, "Spot")) - .when("rabinizer", () -> set(PRISM_LTL2DA_SYNTAX, "Rabinizer"))); - - // DEBUGGING / SANITY CHECKS: - addSwitch("ddsanity", new FlagSwitch(() -> set(PRISM_JDD_SANITY_CHECKS, true))); - - // PARAMETRIC MODEL CHECKING: - addSwitch("param", new FlagSwitch(() -> set(PRISM_PARAM_ENABLED, true))); - addSwitch("paramprecision", new StringSwitch(s -> set(PRISM_PARAM_PRECISION, s))); - addSwitch("paramsplit", new EnumSwitch() + // ── PARAMETRIC MODEL CHECKING OPTIONS ──────────────────────────────── + reg.beginGroup("PARAMETRIC MODEL CHECKING OPTIONS"); + reg.addSwitch("param", paramHandler, + "", "Do parametric model checking with parameters (and ranges) "); + reg.addSwitch("paramprecision", new StringSwitch(s -> set(PRISM_PARAM_PRECISION, s)), + "", "Set max undecided region for parameter synthesis [default: 5/100]"); + reg.addSwitch("paramsplit", new EnumSwitch() .when("longest", () -> set(PRISM_PARAM_SPLIT, "Longest")) - .when("all", () -> set(PRISM_PARAM_SPLIT, "All"))); - addSwitch("parambisim", new EnumSwitch() + .when("all", () -> set(PRISM_PARAM_SPLIT, "All")), + "", "Set method to split parameter regions (longest,all) [default: longest]"); + reg.addSwitch("parambisim", new EnumSwitch() .when("strong", () -> set(PRISM_PARAM_BISIM, "Strong")) .when("weak", () -> set(PRISM_PARAM_BISIM, "Weak")) - .when("none", () -> set(PRISM_PARAM_BISIM, "None"))); - addSwitch("paramfunction", new EnumSwitch() + .when("none", () -> set(PRISM_PARAM_BISIM, "None")), + "", "Set bisimulation minimisation for parameter synthesis (weak,strong,none) [default: weak]"); + reg.addSwitch("paramfunction", new EnumSwitch() .when("jascached", () -> set(PRISM_PARAM_FUNCTION, "JAS-cached")) .when("jas", () -> set(PRISM_PARAM_FUNCTION, "JAS")) - .when("dag", () -> set(PRISM_PARAM_FUNCTION, "DAG"))); - addSwitch("paramelimorder", new EnumSwitch() + .when("dag", () -> set(PRISM_PARAM_FUNCTION, "DAG")), + "", "Set function representation for parameter synthesis (jascached,jas) [default: jascached]"); + reg.addSwitch("paramelimorder", new EnumSwitch() .when("arb", () -> set(PRISM_PARAM_ELIM_ORDER, "Arbitrary")) .when("fw", () -> set(PRISM_PARAM_ELIM_ORDER, "Forward")) .when("fwrev", () -> set(PRISM_PARAM_ELIM_ORDER, "Forward-reversed")) .when("bw", () -> set(PRISM_PARAM_ELIM_ORDER, "Backward")) .when("bwrev", () -> set(PRISM_PARAM_ELIM_ORDER, "Backward-reversed")) - .when("rand", () -> set(PRISM_PARAM_ELIM_ORDER, "Random"))); - addSwitch("paramrandompoints", (sw, a) -> { + .when("rand", () -> set(PRISM_PARAM_ELIM_ORDER, "Random")), + "", "Set elimination order for parameter synthesis (arb,fw,fwrev,bw,bwrev,rand) [default: bw]"); + reg.addSwitch("paramrandompoints", (sw, a) -> { int n = a.nextInt(sw); if (n < 0) throw new PrismException("Invalid value for -" + sw + " switch"); set(PRISM_PARAM_RANDOM_POINTS, n); - }); - addSwitch("paramsubsumeregions", (sw, a) -> { + }, "", "Set number of random points to evaluate per region [default: 5]"); + reg.addSwitch("paramsubsumeregions", (sw, a) -> { set(PRISM_PARAM_SUBSUME_REGIONS, Boolean.parseBoolean(a.next(sw))); - }); - addSwitch("paramdagmaxerror", (sw, a) -> { + }, "", "Subsume adjacent regions during analysis [default: true]"); + reg.addSwitch("paramdagmaxerror", (sw, a) -> { double d = a.nextDouble(sw); if (d < 0) throw new PrismException("Invalid value for -" + sw + " switch"); set(PRISM_PARAM_DAG_MAX_ERROR, d); - }); + }, "", "Maximal error probability allowed for DAG function representation [default: 1E-100]"); - // FAST ADAPTIVE UNIFORMISATION: - addSwitch("fauepsilon", (sw, a) -> { + // ── FAST ADAPTIVE UNIFORMISATION (FAU) OPTIONS ─────────────────────── + reg.beginGroup("FAST ADAPTIVE UNIFORMISATION (FAU) OPTIONS"); + reg.addSwitch("fauepsilon", (sw, a) -> { double d = a.nextDouble(sw); if (d < 0) throw new PrismException("Invalid value for -" + sw + " switch"); set(PRISM_FAU_EPSILON, d); - }); - addSwitch("faudelta", (sw, a) -> { + }, "", "Set probability threshold of birth process in FAU [default: 1e-6]"); + reg.addSwitch("faudelta", (sw, a) -> { double d = a.nextDouble(sw); if (d < 0) throw new PrismException("Invalid value for -" + sw + " switch"); set(PRISM_FAU_DELTA, d); - }); - addSwitch("fauarraythreshold", (sw, a) -> { + }, "", "Set probability threshold for irrelevant states in FAU [default: 1e-12]"); + reg.addSwitch("fauarraythreshold", (sw, a) -> { int n = a.nextInt(sw); if (n < 0) throw new PrismException("Invalid value for -" + sw + " switch"); set(PRISM_FAU_ARRAYTHRESHOLD, n); - }); - addSwitch("fauintervals", (sw, a) -> { + }, "", "Set threshold when to switch to sparse matrix in FAU [default: 100]"); + reg.addSwitch("fauintervals", (sw, a) -> { int n = a.nextInt(sw); if (n < 0) throw new PrismException("Invalid value for -" + sw + " switch"); set(PRISM_FAU_INTERVALS, n); - }); - addSwitch("fauinitival", (sw, a) -> { + }, "", "Set number of intervals to divide time intervals into for FAU [default: 1]"); + reg.addSwitch("fauinitival", (sw, a) -> { double d = a.nextDouble(sw); if (d < 0.0) throw new PrismException("Invalid value for -" + sw + " switch"); set(PRISM_FAU_INITIVAL, d); - }); + }, "", "Set length of additional initial time interval for FAU [default: 1.0]"); - addSwitch("exportpropaut", (sw, a) -> { + // Hidden switch + reg.addSwitch("exportpropaut", (sw, a) -> { Map options = splitOptionsString(a.optionsString()); setExportPropAut(true); setExportPropAutFilename(a.next(sw)); @@ -1283,25 +1350,6 @@ private void initSwitchHandlers() }); } - private void addSwitch(String name, SwitchHandler h) - { - settingsSwitchHandlers.put(name, h); - } - - private void addSwitch(String n1, String n2, SwitchHandler h) - { - settingsSwitchHandlers.put(n1, h); - settingsSwitchHandlers.put(n2, h); - } - - private void addSwitch(String n1, String n2, String n3, SwitchHandler h) - { - settingsSwitchHandlers.put(n1, h); - settingsSwitchHandlers.put(n2, h); - settingsSwitchHandlers.put(n3, h); - } - - /** * Split a switch of the form -switch:options into parts. * The latter can be empty, in which case the : is optional. @@ -1355,149 +1403,6 @@ private static Map splitOptionsString(String optionsString) return map; } - /** - * Print a fragment of the -help message, - * i.e. a list of the command-line switches handled by this class. - */ - public static void printHelp(PrismLog mainLog) - { - mainLog.println(); - mainLog.println("EXPORT OPTIONS:"); - mainLog.println("-exportmodelprecision ....... Export probabilities/rewards with n significant decimal places"); - mainLog.println("-noexportheaders ............... Don't include headers when exporting rewards"); - mainLog.println(); - mainLog.println("ENGINES/METHODS:"); - mainLog.println("-mtbdd (or -m) ................. Use the MTBDD engine"); - mainLog.println("-sparse (or -s) ................ Use the Sparse engine"); - mainLog.println("-hybrid (or -h) ................ Use the Hybrid engine [default]"); - mainLog.println("-explicit (or -ex) ............. Use the explicit engine"); - mainLog.println("-exact ......................... Perform exact (arbitrary precision) model checking"); - mainLog.println("-ptamethod .............. Specify PTA engine (games, digital, backwards) [default: games]"); - mainLog.println("-transientmethod ........ CTMC transient analysis method (unif, fau) [default: unif]"); - mainLog.println("-heuristic .............. Automatic choice of engines/settings (none, speed, memory) [default: none]"); - mainLog.println(); - mainLog.println("SOLUTION METHODS (LINEAR EQUATIONS):"); - mainLog.println("-power (or -pow, -pwr) ......... Use the Power method for numerical computation"); - mainLog.println("-jacobi (or -jac) .............. Use Jacobi for numerical computation [default]"); - mainLog.println("-gaussseidel (or -gs) .......... Use Gauss-Seidel for numerical computation"); - mainLog.println("-bgaussseidel (or -bgs) ........ Use Backwards Gauss-Seidel for numerical computation"); - mainLog.println("-pgaussseidel (or -pgs) ........ Use Pseudo Gauss-Seidel for numerical computation"); - mainLog.println("-bpgaussseidel (or -bpgs) ...... Use Backwards Pseudo Gauss-Seidel for numerical computation"); - mainLog.println("-jor ........................... Use JOR for numerical computation"); - mainLog.println("-sor ........................... Use SOR for numerical computation"); - mainLog.println("-bsor .......................... Use Backwards SOR for numerical computation"); - mainLog.println("-psor .......................... Use Pseudo SOR for numerical computation"); - mainLog.println("-bpsor ......................... Use Backwards Pseudo SOR for numerical computation"); - mainLog.println("-omega ..................... Set over-relaxation parameter (for JOR/SOR/...) [default: 0.9]"); - mainLog.println(); - mainLog.println("SOLUTION METHODS (MDPS):"); - mainLog.println("-valiter ....................... Use value iteration for solving MDPs [default]"); - mainLog.println("-gaussseidel (or -gs) .......... Use Gauss-Seidel value iteration for solving MDPs"); - mainLog.println("-politer ....................... Use policy iteration for solving MDPs"); - mainLog.println("-modpoliter .................... Use modified policy iteration for solving MDPs"); - mainLog.println("-intervaliter (or -ii) ......... Use interval iteration to solve MDPs/MCs (see -help -ii)"); - mainLog.println("-topological ................... Use topological value iteration"); - mainLog.println(); - mainLog.println("SOLUTION METHOD SETTINGS"); - mainLog.println("-relative (or -rel) ............ Use relative error for detecting convergence [default]"); - mainLog.println("-absolute (or -abs) ............ Use absolute error for detecting convergence"); - mainLog.println("-epsilon (or -e ) ....... Set value of epsilon (for convergence check) [default: 1e-6]"); - mainLog.println("-maxiters .................. Set max number of iterations [default: 10000]"); - mainLog.println("-gridresolution .............Set resolution for fixed grid approximation (POMDP) [default: 10]"); - - mainLog.println(); - mainLog.println("MODEL CHECKING OPTIONS:"); - mainLog.println("-nopre ......................... Skip precomputation algorithms (where optional)"); - mainLog.println("-noprob0 ....................... Skip precomputation algorithm Prob0 (where optional)"); - mainLog.println("-noprob1 ....................... Skip precomputation algorithm Prob1 (where optional)"); - mainLog.println("-noprerel ...................... Do not pre-compute/use predecessor relation, e.g. for precomputation"); - mainLog.println("-fair .......................... Use fairness (for model checking of MDPs)"); - mainLog.println("-nofair ........................ Don't use fairness (for model checking of MDPs) [default]"); - mainLog.println("-fixdl ......................... Automatically put self-loops in deadlock states [default]"); - mainLog.println("-nofixdl ....................... Do not automatically put self-loops in deadlock states"); - mainLog.println("-noprobchecks .................. Disable checks on model probabilities/rates"); - mainLog.println("-sumroundoff ............... Set probability sum threshold [default: 1-e5]"); - mainLog.println("-zerorewardcheck ............... Check for absence of zero-reward loops"); - mainLog.println("-nossdetect .................... Disable steady-state detection for CTMC transient computations"); - mainLog.println("-sccmethod .............. Specify (symbolic) SCC computation method (xiebeerel, lockstep, sccfind)"); - mainLog.println("-symm ................. Symmetry reduction options string"); - mainLog.println("-aroptions ............ Abstraction-refinement engine options string"); - mainLog.println("-pathviaautomata ............... Handle all path formulas via automata constructions"); - mainLog.println("-nodasimplify .................. Do not attempt to simplify deterministic automata, acceptance conditions"); - mainLog.println("-exportadv .............. Export an adversary from MDP model checking (as a DTMC)"); - mainLog.println("-exportadvmdp ........... Export an adversary from MDP model checking (as an MDP)"); - mainLog.println("-ltl2datool ............. Run executable to convert LTL formulas to deterministic automata"); - mainLog.println("-ltl2dasyntax .............. Specify output format for -ltl2datool switch (lbt, spin, spot, rabinizer)"); - mainLog.println("-exportiterations .............. Export vectors for iteration algorithms to file"); - mainLog.println("-pmaxquotient .................. For Pmax computations in MDPs, compute in the MEC quotient"); - - mainLog.println(); - mainLog.println("MULTI-OBJECTIVE MODEL CHECKING:"); - mainLog.println("-linprog (or -lp) .............. Use linear programming for multi-objective model checking"); - mainLog.println("-multimaxpoints ............ Maximal number of corner points for (valiter-based) multi-objective"); - mainLog.println("-paretoepsilon ............. Threshold for Pareto curve approximation"); - mainLog.println("-exportpareto ........... When computing Pareto curves, export points to a file"); - mainLog.println(); - mainLog.println("OUTPUT OPTIONS:"); - mainLog.println("-verbose (or -v) ............... Verbose mode: print out state lists and probability vectors"); - mainLog.println("-extraddinfo ................... Display extra info about some (MT)BDDs"); - mainLog.println("-extrareachinfo ................ Display extra info about progress of reachability"); - mainLog.println(); - mainLog.println("SPARSE/HYBRID/MTBDD OPTIONS:"); - mainLog.println("-nocompact ..................... Switch off \"compact\" sparse storage schemes"); - mainLog.println("-sbl ....................... Set number of levels (for hybrid engine) [default: -1]"); - mainLog.println("-sbmax ..................... Set memory limit (KB) (for hybrid engine) [default: 1024]"); - mainLog.println("-gsl (or sorl ) ......... Set number of levels for hybrid GS/SOR [default: -1]"); - mainLog.println("-gsmax (or sormax ) ..... Set memory limit (KB) for hybrid GS/SOR [default: 1024]"); - mainLog.println("-cuddmaxmem ................ Set max memory for CUDD package, e.g. 125k, 50m, 4g [default: 1g]"); - mainLog.println("-cuddepsilon ............... Set epsilon value for CUDD package [default: 1e-15]"); - mainLog.println("-ddsanity ...................... Enable internal sanity checks (causes slow-down)"); - mainLog.println("-ddextrastatevars .......... Set the number of preallocated state vars [default: 20]"); - mainLog.println("-ddextraactionvars ......... Set the number of preallocated action vars [default: 20]"); - mainLog.println(); - mainLog.println("PARAMETRIC MODEL CHECKING OPTIONS:"); - mainLog.println("-param .................. Do parametric model checking with parameters (and ranges) "); - mainLog.println("-paramprecision ............ Set max undecided region for parameter synthesis [default: 5/100]"); - mainLog.println("-paramsplit ............. Set method to split parameter regions (longest,all) [default: longest]"); - mainLog.println("-parambisim ............. Set bisimulation minimisation for parameter synthesis (weak,strong,none) [default: weak]"); - mainLog.println("-paramfunction .......... Set function representation for parameter synthesis (jascached,jas) [default: jascached]"); - mainLog.println("-paramelimorder ......... Set elimination order for parameter synthesis (arb,fw,fwrev,bw,bwrev,rand) [default: bw]"); - mainLog.println("-paramrandompoints ......... Set number of random points to evaluate per region [default: 5]"); - mainLog.println("-paramsubsumeregions ....... Subsume adjacent regions during analysis [default: true]"); - mainLog.println("-paramdagmaxerror .......... Maximal error probability allowed for DAG function representation [default: 1E-100]"); - mainLog.println(); - mainLog.println("FAST ADAPTIVE UNIFORMISATION (FAU) OPTIONS:"); - mainLog.println("-fauepsilon ................ Set probability threshold of birth process in FAU [default: 1e-6]"); - mainLog.println("-faudelta .................. Set probability threshold for irrelevant states in FAU [default: 1e-12]"); - mainLog.println("-fauarraythreshold ......... Set threshold when to switch to sparse matrix in FAU [default: 100]"); - mainLog.println("-fauintervals .............. Set number of intervals to divide time intervals into for FAU [default: 1]"); - mainLog.println("-fauinitival ............... Set length of additional initial time interval for FAU [default: 1.0]"); - } - - /** - * Print a -help xxx message, i.e. display help on a specific switch {@code sw}. - * Return true iff help was available for this switch. - */ - public static boolean printHelpSwitch(PrismLog mainLog, String sw) - { - // -aroptions - if (sw.equals("aroptions")) { - mainLog.println("Switch: -aroptions \n"); - mainLog.println(" is a comma-separated list of options regarding abstraction-refinement:"); - QuantAbstractRefine.printOptions(mainLog); - return true; - } - else if (sw.equals("ii") || sw.equals("intervaliter")) { - mainLog.println("Switch: -intervaliter (or -ii) optionally takes a comma-separated list of options:\n"); - mainLog.println(" -intervaliter:option1,option2,...\n"); - mainLog.println("where the options are one of the following:\n"); - mainLog.println(OptionsIntervalIteration.getOptionsDescription()); - return true; - } - - return false; - } - /** * Set the value for an option, with the option key given as a String, * and the value as an Object of appropriate type or a String to be parsed. diff --git a/prism/src/prism/SwitchHandler.java b/prism/src/prism/SwitchHandler.java index fde6fc56b0..db7b1d8432 100644 --- a/prism/src/prism/SwitchHandler.java +++ b/prism/src/prism/SwitchHandler.java @@ -27,6 +27,7 @@ package prism; import java.util.LinkedHashMap; +import java.util.function.Consumer; /** * Handler for a single CLI switch. @@ -99,6 +100,116 @@ interface Action { void accept(double d) throws PrismException; } public void handle(String sw, ArgConsumer a) throws PrismException { action.accept(a.nextDouble(sw)); } } +/** + * Switch that consumes the next argument, splits it into {@code :}, + * then calls an {@link Action} with the files portion and a {@link ParseCallback} that + * triggers the stored {@link OptionParser} at whatever point in the action is appropriate. + * The split respects Windows path separators ({@code :\}) by skipping them. + * + *

Call {@link #printOptions} to print sub-option help from the stored parser. + * Call {@link #handleFilesOnly} to process a files-only argument (no options suffix). + */ +class StringPlusOptionsSwitch implements SwitchHandler +{ + @FunctionalInterface + interface Action { void accept(String files, ParseCallback parse) throws PrismException; } + + /** + * Token passed to an {@link Action} so it can trigger options parsing at the right moment. + * The options string is the text after the first {@code :} in the switch argument (empty if none). + */ + static final class ParseCallback + { + private final OptionParser parser; + private final String sw; + private final String options; + + ParseCallback(OptionParser parser, String sw, String options) + { + this.parser = parser; this.sw = sw; this.options = options; + } + + /** The options string as split from the switch argument (may be empty). */ + String options() { return options; } + + /** Parse the options with the stored parser under the switch name. */ + void run() throws PrismException { parser.parse(options, sw); } + + /** Parse an alternative options string (e.g. for legacy separator handling). */ + void run(String overrideOptions) throws PrismException { parser.parse(overrideOptions, sw); } + } + + private final OptionParser parser; + private final Action action; + + StringPlusOptionsSwitch(OptionParser parser, Action action) + { + this.parser = parser; this.action = action; + } + + @Override + public void handle(String sw, ArgConsumer a) throws PrismException + { + String[] parts = splitFilesAndOptions(a.next(sw)); + action.accept(parts[0], new ParseCallback(parser, sw, parts[1])); + } + + /** Process a files-only argument (empty options). Equivalent to handle with no {@code :options} suffix. */ + void handleFilesOnly(String sw, String files) throws PrismException + { + action.accept(files, new ParseCallback(parser, sw, "")); + } + + /** Print the sub-options registered in the stored parser (for use in help blocks). */ + void printOptions(PrismLog log) { parser.printOptions(log); } + + /** + * Split a {@code :} argument on the first {@code :} that is not a + * Windows path separator ({@code :\}). Returns {@code {files, options}}; options is + * empty if no {@code :} is found. + */ + static String[] splitFilesAndOptions(String arg) + { + int i = arg.indexOf(':'); + while (arg.length() > i + 1 && arg.charAt(i + 1) == '\\') + i = arg.indexOf(':', i + 1); + if (i != -1) + return new String[]{arg.substring(0, i), arg.substring(i + 1)}; + else + return new String[]{arg, ""}; + } +} + +/** + * Bundles a {@link SwitchHandler} with its help metadata for the unified CLI switch map. + * Visible entries have a non-null {@code argHint}; hidden entries have {@code argHint == null}; + * blank-line sentinels have {@code primaryName == null}. {@code group} is the section header + * printed above the first entry in a group. {@code longDesc} is invoked by {@code -help }. + */ +class SwitchEntry +{ + final SwitchHandler handler; + final String group; + final String primaryName; // null = blank-line sentinel + final String[] shownAliases; // aliases displayed in -help output (subset of all registered names) + final String argHint; // null = hidden; "" = flag switch; e.g. "", "" + final String shortText; // description text; null for hidden/blank-line sentinels + final Consumer longDesc; + + SwitchEntry(SwitchHandler handler, String group, String primaryName, + String[] shownAliases, String argHint, String shortText, + Consumer longDesc) + { + this.handler = handler; + this.group = group; + this.primaryName = primaryName; + this.shownAliases = shownAliases; + this.argHint = argHint; + this.shortText = shortText; + this.longDesc = longDesc; + } +} + /** * Switch that consumes the next argument and dispatches to one of a fixed set of named choices. * Throws a descriptive error listing valid options on unknown input. diff --git a/prism/src/prism/SwitchRegistry.java b/prism/src/prism/SwitchRegistry.java new file mode 100644 index 0000000000..001dab27c1 --- /dev/null +++ b/prism/src/prism/SwitchRegistry.java @@ -0,0 +1,143 @@ +//============================================================================== +// +// Copyright (c) 2026- +// Authors: +// * Dave Parker (University of Oxford) +// +//------------------------------------------------------------------------------ +// +// This file is part of PRISM. +// +// PRISM is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// PRISM is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with PRISM; if not, write to the Free Software Foundation, +// Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +//============================================================================== + +package prism; + +import java.util.Map; +import java.util.function.Consumer; + +/** + * Accumulator for the unified CLI switch map, shared between {@link PrismCL} and + * {@link PrismSettings} during one-time initialisation of the switch registry. + * Tracks the current help section so callers need only call {@link #beginGroup} + * when the section changes. + * + *

The visible {@code addSwitch} overloads separate the argument hint (e.g. {@code ""}, + * {@code ""} for flags) from the description text. The primary name and any shown aliases are + * recorded in the {@link SwitchEntry} so {@code printHelp()} can generate the left column and + * dot-padding automatically. + */ +class SwitchRegistry +{ + private static final String[] NO_ALIASES = new String[0]; + + private final Map map; + private String currentGroup = null; + + SwitchRegistry(Map map) { this.map = map; } + + /** Begin a new help section. Subsequent visible entries are listed under this header. */ + void beginGroup(String group) { currentGroup = group; } + + // ── Hidden (not listed in -help) ───────────────────────────────────────── + + void addSwitch(String name, SwitchHandler h) + { + map.put(name, new SwitchEntry(h, currentGroup, name, NO_ALIASES, null, null, null)); + } + + void addSwitch(String n1, String n2, SwitchHandler h) + { + SwitchEntry e = new SwitchEntry(h, currentGroup, n1, NO_ALIASES, null, null, null); + map.put(n1, e); map.put(n2, e); + } + + void addSwitch(String n1, String n2, String n3, SwitchHandler h) + { + SwitchEntry e = new SwitchEntry(h, currentGroup, n1, NO_ALIASES, null, null, null); + map.put(n1, e); map.put(n2, e); map.put(n3, e); + } + + // ── Visible flag switch (argHint = "", no argument) ────────────────────── + + void addSwitch(String name, SwitchHandler h, String shortText) + { + map.put(name, new SwitchEntry(h, currentGroup, name, NO_ALIASES, "", shortText, null)); + } + + void addSwitch(String n1, String n2, SwitchHandler h, String shortText) + { + SwitchEntry e = new SwitchEntry(h, currentGroup, n1, new String[]{n2}, "", shortText, null); + map.put(n1, e); map.put(n2, e); + } + + void addSwitch(String n1, String n2, String n3, SwitchHandler h, String shortText) + { + SwitchEntry e = new SwitchEntry(h, currentGroup, n1, new String[]{n2, n3}, "", shortText, null); + map.put(n1, e); map.put(n2, e); map.put(n3, e); + } + + // ── Visible switch with argument ────────────────────────────────────────── + + void addSwitch(String name, SwitchHandler h, String argHint, String shortText) + { + map.put(name, new SwitchEntry(h, currentGroup, name, NO_ALIASES, argHint, shortText, null)); + } + + void addSwitch(String n1, String n2, SwitchHandler h, String argHint, String shortText) + { + SwitchEntry e = new SwitchEntry(h, currentGroup, n1, new String[]{n2}, argHint, shortText, null); + map.put(n1, e); map.put(n2, e); + } + + void addSwitch(String n1, String n2, String n3, SwitchHandler h, String argHint, String shortText) + { + SwitchEntry e = new SwitchEntry(h, currentGroup, n1, new String[]{n2, n3}, argHint, shortText, null); + map.put(n1, e); map.put(n2, e); map.put(n3, e); + } + + // ── Visible flag with detailed help ────────────────────────────────────── + + void addSwitch(String name, SwitchHandler h, String shortText, Consumer longDesc) + { + map.put(name, new SwitchEntry(h, currentGroup, name, NO_ALIASES, "", shortText, longDesc)); + } + + void addSwitch(String n1, String n2, SwitchHandler h, String shortText, Consumer longDesc) + { + SwitchEntry e = new SwitchEntry(h, currentGroup, n1, new String[]{n2}, "", shortText, longDesc); + map.put(n1, e); map.put(n2, e); + } + + // ── Visible switch with argument and detailed help ──────────────────────── + + void addSwitch(String name, SwitchHandler h, String argHint, String shortText, Consumer longDesc) + { + map.put(name, new SwitchEntry(h, currentGroup, name, NO_ALIASES, argHint, shortText, longDesc)); + } + + void addSwitch(String n1, String n2, SwitchHandler h, String argHint, String shortText, Consumer longDesc) + { + SwitchEntry e = new SwitchEntry(h, currentGroup, n1, new String[]{n2}, argHint, shortText, longDesc); + map.put(n1, e); map.put(n2, e); + } + + /** Insert a blank line in the {@code -help} output at this position. */ + void addBlankLine() + { + map.put("__blank_" + map.size(), new SwitchEntry(null, null, null, NO_ALIASES, null, null, null)); + } +} From 0e3ac52f84729a240321032111f18302b9d3d86b Mon Sep 17 00:00:00 2001 From: Dave Parker Date: Mon, 29 Jun 2026 23:15:23 +0100 Subject: [PATCH 03/15] More tidying of PrismCL switch definitions. * Replace some raw (sw, a) -> a.next(sw) lambdas with StringSwitch * Inline small switch actions, removing some transient fields * Generate the "Switch: -xx ..." automatically for prism -help -xx Note: drop the legacy comma separator for -exportresults (now only colon). --- prism/src/prism/PrismCL.java | 174 ++++++++++------------------- prism/src/prism/PrismSettings.java | 3 +- prism/src/prism/SwitchHandler.java | 15 +++ 3 files changed, 77 insertions(+), 115 deletions(-) diff --git a/prism/src/prism/PrismCL.java b/prism/src/prism/PrismCL.java index 051c5f6ee1..0a455c5828 100644 --- a/prism/src/prism/PrismCL.java +++ b/prism/src/prism/PrismCL.java @@ -207,9 +207,6 @@ public class PrismCL implements PrismModelListener private StrategyExportOptions exportStratOptions = null; // transient state used during option parsing for complex switches - private boolean exportResultsCsv; - private boolean exportResultsMatrix; - private ModelExportTask pendingLabelExportTask; private ModelExportOptions pendingExportOptions; private List pendingExportTasks; @@ -1107,6 +1104,7 @@ private void initSwitchHandlers() // ── General (no section header) ────────────────────────────────────── SwitchHandler helpHandler = (sw, a) -> { + // NB: -help [switch] is one of the few cases where the value arg is optional if (a.hasNext()) printHelpSwitch(a.next(sw)); else printHelp(); exit(); }; @@ -1137,12 +1135,11 @@ private void initSwitchHandlers() }; registry.addSwitch("property", "prop", propHandler, "", "Only model check properties included in list of indices/names"); - registry.addSwitch("const", (sw, a) -> { - String v = a.next(sw).trim(); + registry.addSwitch("const", new StringSwitch(s -> { + String v = s.trim(); if ("".equals(constSwitch)) constSwitch = v; else constSwitch += "," + v; - }, "", "Define constant values as (e.g. for experiments)", + }), "", "Define constant values as (e.g. for experiments)", log -> { - log.println("Switch: -const \n"); log.println(" is a comma-separated list of values or value ranges for undefined constants"); log.println("in the model or properties (i.e. those declared without values, such as \"const int a;\")."); log.println("You can either specify a single value (a=1), a range (a=1:10) or a range with a step (a=1:2:50)."); @@ -1155,7 +1152,7 @@ private void initSwitchHandlers() }); registry.addSwitch("steadystate", "ss", new FlagSwitch(() -> steadystate = true), "", "Compute steady-state probabilities (D/CTMCs only)"); - registry.addSwitch("transient", "tr", (sw, a) -> { dotransient = true; transientTime = a.next(sw); }, + registry.addSwitch("transient", "tr", new StringSwitch(s -> { dotransient = true; transientTime = s; }), "", "Compute transient probabilities for time (or time range) (D/CTMCs only)"); registry.addSwitch("simpath", (sw, a) -> { simpath = true; @@ -1163,7 +1160,6 @@ private void initSwitchHandlers() simpathFilename = a.next(sw); }, " ", "Generate a random path with the simulator", log -> { - log.println("Switch: -simpath \n"); log.println("Generate a random path with the simulator and export it to (or to the screen if =\"stdout\")."); log.println(" is a comma-separated list of options taken from:"); GenerateSimulationPath.printOptions(log); @@ -1217,9 +1213,8 @@ private void initSwitchHandlers() .when("umb", () -> { for (ModelImportSource s : modelImportSources) s.format = ModelExportFormat.UMB; })), this::processImportModelSwitch); registry.addSwitch("importmodel", importModelSwitch, - "", "Import the model directly from file(s)", + "[:options]", "Import the model directly from file(s)", log -> { - log.println("Switch: -importmodel [:options]\n"); log.println("Import the model directly from one or more file(s)."); log.println("Use a list of file extensions to indicate which files should be read, e.g.:"); log.println("\n -importmodel in.tra,sta\n"); @@ -1229,10 +1224,10 @@ private void initSwitchHandlers() log.println("If provided, is a comma-separated list of options taken from:"); importModelSwitch.printOptions(log); }); - registry.addSwitch("importtrans", (sw, a) -> { - modelFilename = a.next(sw); // recall for use as basename in model exports - modelImportSources.add(new ModelImportSource(ModelExportTask.ModelExportEntity.MODEL, ModelExportFormat.EXPLICIT, new File(modelFilename))); - }, "", "Import the transition matrix directly from a text file"); + registry.addSwitch("importtrans", new StringSwitch(s -> { + modelFilename = s; // recall for use as basename in model exports + modelImportSources.add(new ModelImportSource(ModelExportTask.ModelExportEntity.MODEL, ModelExportFormat.EXPLICIT, new File(s))); + }), "", "Import the transition matrix directly from a text file"); registry.addSwitch("importstates", new StringSwitch(s -> modelImportSources.add( new ModelImportSource(ModelExportTask.ModelExportEntity.STATES, ModelExportFormat.EXPLICIT, new File(s)))), "", "Import the list of states directly from a text file"); @@ -1248,7 +1243,7 @@ private void initSwitchHandlers() registry.addSwitch("importtransrewards", new StringSwitch(s -> modelImportSources.add( new ModelImportSource(ModelExportTask.ModelExportEntity.TRANSITION_REWARDS, ModelExportFormat.EXPLICIT, new File(s)))), "", "Import the transition rewards directly from a text file"); - registry.addSwitch("importinitdist", (sw, a) -> { importinitdist = true; importInitDistFilename = a.next(sw); }, + registry.addSwitch("importinitdist", new StringSwitch(s -> { importinitdist = true; importInitDistFilename = s; }), "", "Specify initial probability distribution for transient/steady-state analysis"); registry.addSwitch("dtmc", new FlagSwitch(() -> typeOverride = ModelType.DTMC), "", "Force imported/built model to be a DTMC"); @@ -1256,37 +1251,45 @@ private void initSwitchHandlers() "", "Force imported/built model to be a CTMC"); registry.addSwitch("mdp", new FlagSwitch(() -> typeOverride = ModelType.MDP), "", "Force imported/built model to be an MDP"); - registry.addSwitch("importresults", (sw, a) -> { + registry.addSwitch("importresults", new StringSwitch(s -> { importresults = true; modelFilename = "no-model-file.prism"; - importResultsFilename = a.next(sw); - }, "", "Import results from a data frame stored in CSV file", + importResultsFilename = s; + }), "", "Import results from a data frame stored in CSV file", log -> { - log.println("Switch: -importresults \n"); log.println("Import results from a data frame stored as comma-separated values in ."); }); // ── EXPORTS ────────────────────────────────────────────────────────── registry.beginGroup("EXPORTS"); + boolean[] csv = {false}, matrix = {false}; StringPlusOptionsSwitch exportResultsSwitch = new StringPlusOptionsSwitch( new OptionParser() - .flag("csv", "Export results as comma-separated values", () -> exportResultsCsv = true) - .flag("matrix", "Export results as one or more 2D matrices (e.g. for surface plots)", () -> exportResultsMatrix = true) + .flag("csv", "Export results as comma-separated values", () -> csv[0] = true) + .flag("matrix", "Export results as one or more 2D matrices (e.g. for surface plots)", () -> matrix[0] = true) .flag("dataframe", "Export results as dataframe in comma-separated values", () -> exportShape = ResultsExportShape.DATA_FRAME) .flag("comment", "Export results in comment format for regression testing",() -> exportShape = ResultsExportShape.COMMENT), - this::processExportResultsSwitch); + (file, parse) -> { + exportresults = true; + exportResultsFilename = file; + exportShape = ResultsExportShape.LIST_PLAIN; + csv[0] = false; matrix[0] = false; + parse.run(); + if (exportShape == ResultsExportShape.LIST_PLAIN) + exportShape = csv[0] ? (matrix[0] ? ResultsExportShape.MATRIX_CSV : ResultsExportShape.LIST_CSV) + : (matrix[0] ? ResultsExportShape.MATRIX_PLAIN : ResultsExportShape.LIST_PLAIN); + }); registry.addSwitch("exportresults", exportResultsSwitch, "", "Export the results of model checking to a file", log -> { - log.println("Switch: -exportresults \n"); log.println("Exports the results of model checking to (or to the screen if =\"stdout\")."); log.println("The default behaviour is to export a list of results in text form, using tabs to separate items."); log.println("If provided, is a comma-separated list of options taken from:"); exportResultsSwitch.printOptions(log); }); - registry.addSwitch("exportvector", (sw, a) -> { - exportvector = true; exportVectorFilename = a.next(sw); prism.setStoreVector(true); - }, "", "Export results of model checking for all states to a file"); + registry.addSwitch("exportvector", new StringSwitch(s -> { + exportvector = true; exportVectorFilename = s; prism.setStoreVector(true); + }), "", "Export results of model checking for all states to a file"); StringPlusOptionsSwitch exportModelSwitch = new StringPlusOptionsSwitch( new OptionParser() .choice("format", "model export format", new OptionParser.Choice() @@ -1327,7 +1330,6 @@ private void initSwitchHandlers() registry.addSwitch("exportmodel", exportModelSwitch, "", "Export the built model to file(s)", log -> { - log.println("Switch: -exportmodel \n"); log.println("Export the built model to file(s) (or to the screen if =\"stdout\")."); log.println("Use a list of file extensions to indicate which files should be generated, e.g.:"); log.println("\n -exportmodel out.tra,sta\n"); @@ -1361,15 +1363,19 @@ private void initSwitchHandlers() registry.addSwitch("exportobs", new StringSwitch(s -> modelExportTasks.add( new ModelExportTask(ModelExportTask.ModelExportEntity.OBSERVATIONS, s))), "", "Export the list of observations to a file"); + ModelExportTask[] pending = {null}; StringPlusOptionsSwitch exportLabelsSwitch = new StringPlusOptionsSwitch( new OptionParser() - .flag("matlab", "export data in Matlab format", () -> pendingLabelExportTask.getExportOptions().setFormat(ModelExportFormat.MATLAB)) - .flag("proplabels", "export labels from a properties file into the same file, too",() -> pendingLabelExportTask.setLabelExportSet(ModelExportTask.LabelExportSet.ALL)), - this::processExportLabelsSwitch); + .flag("matlab", "export data in Matlab format", () -> pending[0].getExportOptions().setFormat(ModelExportFormat.MATLAB)) + .flag("proplabels", "export labels from a properties file into the same file, too",() -> pending[0].setLabelExportSet(ModelExportTask.LabelExportSet.ALL)), + (file, parse) -> { + pending[0] = new ModelExportTask(ModelExportTask.ModelExportEntity.LABELS, file); + parse.run(); + modelExportTasks.add(pending[0]); + }); registry.addSwitch("exportlabels", exportLabelsSwitch, "", "Export the list of labels and satisfying states to a file", log -> { - log.println("Switch: -exportlabels \n"); log.println("Export the list of labels and satisfying states to a file (or to the screen if =\"stdout\")."); log.println(); log.println("If provided, is a comma-separated list of options taken from:"); @@ -1377,12 +1383,16 @@ private void initSwitchHandlers() }); StringPlusOptionsSwitch exportPropLabelsSwitch = new StringPlusOptionsSwitch( new OptionParser() - .flag("matlab", "export data in Matlab format", () -> pendingLabelExportTask.getExportOptions().setFormat(ModelExportFormat.MATLAB)), - this::processExportPropLabelsSwitch); + .flag("matlab", "export data in Matlab format", () -> pending[0].getExportOptions().setFormat(ModelExportFormat.MATLAB)), + (file, parse) -> { + pending[0] = new ModelExportTask(ModelExportTask.ModelExportEntity.LABELS, file); + pending[0].setLabelExportSet(ModelExportTask.LabelExportSet.EXTRA); + parse.run(); + modelExportTasks.add(pending[0]); + }); registry.addSwitch("exportproplabels", exportPropLabelsSwitch, "", "Export the list of labels and satisfying states from the properties file to a file", log -> { - log.println("Switch: -exportproplabels \n"); log.println("Export the list of labels and satisfying states from the properties file to a file (or to the screen if =\"stdout\")."); log.println(); log.println("If provided, is a comma-separated list of options taken from:"); @@ -1405,7 +1415,6 @@ private void initSwitchHandlers() registry.addSwitch("exportstrat", exportStratSwitch, "", "Generate and export a strategy to a file", log -> { - log.println("Switch: -exportstrat \n"); log.println("Generate and export a strategy to a file (or to the screen if =\"stdout\")."); log.println("Use file extension .tra or .dot to export as an induced model or Dot file, respectively."); log.println("If provided, is a comma-separated list of options taken from:"); @@ -1431,16 +1440,13 @@ private void initSwitchHandlers() ModelExportOptions exportOptions = new ModelExportOptions().setFormat(ModelExportFormat.DD_DOT); modelExportTasks.add(new ModelExportTask(ModelExportTask.ModelExportEntity.MODEL, s, exportOptions)); }), "", "Export the transition matrix MTBDD to a dot file"); - registry.addSwitch("exportsccs", (sw, a) -> { exportsccs = true; exportSCCsFilename = a.next(sw); }, + registry.addSwitch("exportsccs", new StringSwitch(s -> { exportsccs = true; exportSCCsFilename = s; }), "", "Compute and export all SCCs of the model"); - registry.addSwitch("exportbsccs", (sw, a) -> { exportbsccs = true; exportBSCCsFilename = a.next(sw); }, + registry.addSwitch("exportbsccs", new StringSwitch(s -> { exportbsccs = true; exportBSCCsFilename = s; }), "", "Compute and export all BSCCs of the model"); - registry.addSwitch("exportmecs", (sw, a) -> { exportmecs = true; exportMECsFilename = a.next(sw); }, + registry.addSwitch("exportmecs", new StringSwitch(s -> { exportmecs = true; exportMECsFilename = s; }), "", "Compute and export all maximal end components (MDPs only)"); - SwitchHandler exportSteadyStateHandler = (sw, a) -> { - exportSteadyStateFilename = a.next(sw); - steadystate = true; // compute if asked to export - }; + SwitchHandler exportSteadyStateHandler = new StringSwitch(s -> { exportSteadyStateFilename = s; steadystate = true; }); registry.addSwitch("exportsteadystate", exportSteadyStateHandler, "", "Export steady-state probabilities to a file"); registry.addSwitch("exportss", exportSteadyStateHandler); // hidden alias @@ -1448,9 +1454,9 @@ private void initSwitchHandlers() registry.addSwitch("exporttransient", exportTransientHandler, "", "Export transient probabilities to a file"); registry.addSwitch("exporttr", exportTransientHandler); // hidden alias - registry.addSwitch("exportprism", (sw, a) -> { exportprism = true; exportPrismFilename = a.next(sw); }, + registry.addSwitch("exportprism", new StringSwitch(s -> { exportprism = true; exportPrismFilename = s; }), "", "Export final PRISM model to a file"); - registry.addSwitch("exportprismconst", (sw, a) -> { exportprismconst = true; exportPrismConstFilename = a.next(sw); }, + registry.addSwitch("exportprismconst", new StringSwitch(s -> { exportprismconst = true; exportPrismConstFilename = s; }), "", "Export final PRISM model with expanded constants to a file"); // Hidden export switches @@ -1535,7 +1541,15 @@ private void initSwitchHandlers() // Hidden miscellaneous switches registry.addSwitch("explicitbuild", new FlagSwitch(() -> explicitbuild = true)); registry.addSwitch("explicitbuildtest", new FlagSwitch(() -> explicitbuildtest = true)); - registry.addSwitch("mainlog", new StringSwitch(this::processMainLogSwitch)); + boolean[] appendToLog = {false}; + registry.addSwitch("mainlog", new StringPlusOptionsSwitch( + new OptionParser().flag("append", () -> appendToLog[0] = true), + (file, parse) -> { + appendToLog[0] = false; + parse.run(); + try { mainLog = new PrismFileLog(file, appendToLog[0]); prism.setMainLog(mainLog); } + catch (PrismException e) { errorAndExit("Couldn't open log file \"" + file + "\""); } + })); registry.addSwitch("exportmodeldotview", new FlagSwitch(() -> exportmodeldotview = true)); registry.addSwitch("c1", new FlagSwitch(() -> prism.setConstruction(1))); registry.addSwitch("c2", new FlagSwitch(() -> prism.setConstruction(2))); @@ -1577,28 +1591,6 @@ private void processFileNames(List filenameArgs) throws PrismException } } - /** Process the argument to the -exportresults switch. */ - private void processExportResultsSwitch(String files, StringPlusOptionsSwitch.ParseCallback parse) throws PrismException - { - exportresults = true; - // Also accept , as a legacy separator if : is absent - String optionsStr = parse.options(); - String filesStr = files; - if (optionsStr.isEmpty() && files.indexOf(',') > -1) { - int comma = files.indexOf(','); - filesStr = files.substring(0, comma); - optionsStr = files.substring(comma + 1); - } - exportResultsFilename = filesStr; - exportShape = ResultsExportShape.LIST_PLAIN; - exportResultsCsv = false; - exportResultsMatrix = false; - parse.run(optionsStr); - if (exportShape == ResultsExportShape.LIST_PLAIN) - exportShape = exportResultsCsv ? (exportResultsMatrix ? ResultsExportShape.MATRIX_CSV : ResultsExportShape.LIST_CSV) - : (exportResultsMatrix ? ResultsExportShape.MATRIX_PLAIN : ResultsExportShape.LIST_PLAIN); - } - /** * Process the arguments (files, options) to the -importmodel switch * NB: This is done at the time of parsing switches (not later) @@ -1728,27 +1720,6 @@ private void addTransitionRewardImports(String basename, boolean assumeExists) } } - /** - * Process the arguments (file, options) to the -exportlabels switch. - */ - private void processExportLabelsSwitch(String file, StringPlusOptionsSwitch.ParseCallback parse) throws PrismException - { - pendingLabelExportTask = new ModelExportTask(ModelExportTask.ModelExportEntity.LABELS, file); - parse.run(); - modelExportTasks.add(pendingLabelExportTask); - } - - /** - * Process the arguments (file, options) to the -exportproplabels switch. - */ - private void processExportPropLabelsSwitch(String file, StringPlusOptionsSwitch.ParseCallback parse) throws PrismException - { - pendingLabelExportTask = new ModelExportTask(ModelExportTask.ModelExportEntity.LABELS, file); - pendingLabelExportTask.setLabelExportSet(ModelExportTask.LabelExportSet.EXTRA); - parse.run(); - modelExportTasks.add(pendingLabelExportTask); - } - /** * Process the arguments (files, options) to the -exportmodel switch * NB: This is done at the time of parsing switches (not later) @@ -1820,29 +1791,6 @@ private void processExportStratSwitch(String fileString, StringPlusOptionsSwitch parse.run(); } - /** - * Process the arguments (file, options) to the -mainlog switch - */ - private void processMainLogSwitch(String filesOptionsString) throws PrismException - { - // Split into file/options (on :) - String halves[] = StringPlusOptionsSwitch.splitFilesAndOptions(filesOptionsString); - String filename = halves[0]; - String optionsString = halves[1]; - // Process options - boolean[] append = { false }; - new OptionParser() - .flag("append", () -> append[0] = true) - .parse(optionsString, "mainlog"); - // Open the log - try { - mainLog = new PrismFileLog(filename, append[0]); - prism.setMainLog(mainLog); - } catch (PrismException e) { - errorAndExit("Couldn't open log file \"" + filename + "\""); - } - } - // print command line arguments public void printArguments(String[] args) @@ -2156,7 +2104,7 @@ private void printHelpSwitch(String sw) sw = sw.substring(1); SwitchEntry entry = switchHandlers.get(sw); if (entry != null && entry.longDesc != null) - entry.longDesc.accept(mainLog); + entry.printLongDesc(mainLog); else mainLog.println("Sorry - no help available for switch -" + sw); } diff --git a/prism/src/prism/PrismSettings.java b/prism/src/prism/PrismSettings.java index b7fac7fe03..38dc5cbae8 100644 --- a/prism/src/prism/PrismSettings.java +++ b/prism/src/prism/PrismSettings.java @@ -1085,7 +1085,7 @@ void registerSwitchHandlers(SwitchRegistry reg, Prism prism, SwitchHandler param } }, "", "Use interval iteration to solve MDPs/MCs (see -help -ii)", log -> { - log.println("Switch: -intervaliter (or -ii) optionally takes a comma-separated list of options:\n"); + log.println("Optionally takes a comma-separated list of options:\n"); log.println(" -intervaliter:option1,option2,...\n"); log.println("where the options are one of the following:\n"); log.println(OptionsIntervalIteration.getOptionsDescription()); @@ -1162,7 +1162,6 @@ void registerSwitchHandlers(SwitchRegistry reg, Prism prism, SwitchHandler param set(PRISM_AR_OPTIONS, "".equals(existing) ? v : existing + "," + v); }, "", "Abstraction-refinement engine options string", log -> { - log.println("Switch: -aroptions \n"); log.println(" is a comma-separated list of options regarding abstraction-refinement:"); QuantAbstractRefine.printOptions(log); }); diff --git a/prism/src/prism/SwitchHandler.java b/prism/src/prism/SwitchHandler.java index db7b1d8432..c42fac2157 100644 --- a/prism/src/prism/SwitchHandler.java +++ b/prism/src/prism/SwitchHandler.java @@ -208,6 +208,21 @@ class SwitchEntry this.shortText = shortText; this.longDesc = longDesc; } + + /** Print detailed help: auto-generates the "Switch: -name [aliases] [argHint]" header, then delegates to {@code longDesc}. */ + void printLongDesc(PrismLog log) + { + StringBuilder header = new StringBuilder("Switch: -").append(primaryName); + if (shownAliases != null && shownAliases.length > 0) { + header.append(" (or"); + for (String alias : shownAliases) header.append(" -").append(alias); + header.append(")"); + } + if (argHint != null && !argHint.isEmpty()) + header.append(" ").append(argHint); + log.println(header + "\n"); + longDesc.accept(log); + } } /** From f755a78c7809e65572065f3adb344fb723390c0d Mon Sep 17 00:00:00 2001 From: Dave Parker Date: Mon, 29 Jun 2026 23:39:57 +0100 Subject: [PATCH 04/15] CLI help: auto-generate -help output for all visible switches Previously -help only produced detailed output for the ~11 switches that had an explicit longDesc lambda; all others said "Sorry - no help available". Now SwitchEntry.printLongDesc generates useful output unconditionally for any visible switch (argHint != null): - header line "Switch: -name [aliases] [argHint]" (already auto-generated) - when longDesc is set: delegates to it as before - when longDesc is absent: prints the short description; and for EnumSwitch additionally prints "Valid options: ..." listing all accepted keys EnumSwitch gains a printOptions() method for this purpose. --- prism/src/prism/PrismCL.java | 2 +- prism/src/prism/SwitchHandler.java | 13 +++++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/prism/src/prism/PrismCL.java b/prism/src/prism/PrismCL.java index 0a455c5828..2e85288647 100644 --- a/prism/src/prism/PrismCL.java +++ b/prism/src/prism/PrismCL.java @@ -2103,7 +2103,7 @@ private void printHelpSwitch(String sw) if (sw.charAt(0) == '-') sw = sw.substring(1); SwitchEntry entry = switchHandlers.get(sw); - if (entry != null && entry.longDesc != null) + if (entry != null && entry.argHint != null) entry.printLongDesc(mainLog); else mainLog.println("Sorry - no help available for switch -" + sw); diff --git a/prism/src/prism/SwitchHandler.java b/prism/src/prism/SwitchHandler.java index c42fac2157..5e74b53e7c 100644 --- a/prism/src/prism/SwitchHandler.java +++ b/prism/src/prism/SwitchHandler.java @@ -209,7 +209,7 @@ class SwitchEntry this.longDesc = longDesc; } - /** Print detailed help: auto-generates the "Switch: -name [aliases] [argHint]" header, then delegates to {@code longDesc}. */ + /** Print detailed help: auto-generates the "Switch: -name [aliases] [argHint]" header, then the body. */ void printLongDesc(PrismLog log) { StringBuilder header = new StringBuilder("Switch: -").append(primaryName); @@ -221,7 +221,14 @@ void printLongDesc(PrismLog log) if (argHint != null && !argHint.isEmpty()) header.append(" ").append(argHint); log.println(header + "\n"); - longDesc.accept(log); + if (longDesc != null) { + longDesc.accept(log); + } else { + if (shortText != null && !shortText.isEmpty()) + log.println(shortText); + if (handler instanceof EnumSwitch) + ((EnumSwitch) handler).printOptions(log); + } } } @@ -258,4 +265,6 @@ public void handle(String sw, ArgConsumer a) throws PrismException " (options are: " + String.join(", ", choices.keySet()) + ")"); action.run(); } + + void printOptions(PrismLog log) { log.println("Valid options: " + String.join(", ", choices.keySet())); } } From 3ec5ca3e4a4df26c2f39cfa08c7388809c426774 Mon Sep 17 00:00:00 2001 From: Dave Parker Date: Tue, 30 Jun 2026 07:56:56 +0100 Subject: [PATCH 05/15] OptionsParser toggle(...) methods for -xx/-noxx switches. --- prism/src/prism/OptionParser.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/prism/src/prism/OptionParser.java b/prism/src/prism/OptionParser.java index 145da5d308..f16d9f5753 100644 --- a/prism/src/prism/OptionParser.java +++ b/prism/src/prism/OptionParser.java @@ -129,6 +129,20 @@ OptionParser string(String name, String argHint, String description, StringActio return this; } + // ── Toggle options ──────────────────────────────────────────────────────── + + /** + * Register a {@code [no]name} boolean toggle: bare {@code name} calls {@code action(true)}, + * bare {@code noname} calls {@code action(false)}. + */ + OptionParser toggle(String name, String description, BoolAction action) + { + flagHandlers.put(name, () -> action.accept(true)); + flagHandlers.put("no" + name, () -> action.accept(false)); + helpEntries.add(new HelpEntry("flag", "[no]" + name, null, description)); + return this; + } + // ── Boolean options ─────────────────────────────────────────────────────── /** Register a {@code key=true|false} option (action only). */ From 5bf395687303ee6534dcd8a87c8a2e9ffecd1d6f Mon Sep 17 00:00:00 2001 From: Dave Parker Date: Tue, 30 Jun 2026 08:10:17 +0100 Subject: [PATCH 06/15] SwitchHandler: OptionsOnlySwitch option for -xx:flag,opt=val,... --- prism/src/prism/SwitchHandler.java | 35 ++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/prism/src/prism/SwitchHandler.java b/prism/src/prism/SwitchHandler.java index 5e74b53e7c..c41d757a05 100644 --- a/prism/src/prism/SwitchHandler.java +++ b/prism/src/prism/SwitchHandler.java @@ -180,6 +180,41 @@ static String[] splitFilesAndOptions(String arg) } } +/** + * Switch with no leading file/string argument: only an optional {@code :} suffix on the + * switch token itself (PrismSettings-style, where {@code splitSwitch} performs the colon split + * before dispatch and the result is exposed via {@link ArgConsumer#optionsString()}). Unlike + * {@link StringPlusOptionsSwitch}, there is no separate leading "files" portion to split off -- + * the switch token's own colon suffix is itself the options string. + * + *

Reuses {@link StringPlusOptionsSwitch.ParseCallback} so an {@link Action} can call + * {@code parse.run()} to dispatch immediately via the stored {@link OptionParser}, or + * {@code parse.options()} to read the raw string (e.g. for deferred/two-phase parsing). + */ +class OptionsOnlySwitch implements SwitchHandler +{ + @FunctionalInterface + interface Action { void accept(StringPlusOptionsSwitch.ParseCallback parse) throws PrismException; } + + private final OptionParser parser; + private final Action action; + + OptionsOnlySwitch(OptionParser parser, Action action) + { + this.parser = parser; this.action = action; + } + + @Override + public void handle(String sw, ArgConsumer a) throws PrismException + { + String opts = a.optionsString(); + action.accept(new StringPlusOptionsSwitch.ParseCallback(parser, sw, opts == null ? "" : opts)); + } + + /** Print the sub-options registered in the stored parser (for use in help blocks). */ + void printOptions(PrismLog log) { parser.printOptions(log); } +} + /** * Bundles a {@link SwitchHandler} with its help metadata for the unified CLI switch map. * Visible entries have a non-null {@code argHint}; hidden entries have {@code argHint == null}; From 1fb8f809b6dc2af49c789b9375179c9bd8f2b6ee Mon Sep 17 00:00:00 2001 From: Dave Parker Date: Tue, 30 Jun 2026 08:27:13 +0100 Subject: [PATCH 07/15] CLI -help text generation: allow argHint = "[:]" When an argHint starts with [:, it's a colon-suffix on the switch token itself (no separate argument), so it's glued directly to the switch name with no space, rather than space-separated like normal hints (, [:]). --- prism/src/prism/PrismCL.java | 3 +-- prism/src/prism/SwitchHandler.java | 19 +++++++++++++++---- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/prism/src/prism/PrismCL.java b/prism/src/prism/PrismCL.java index 2e85288647..67c52a3580 100644 --- a/prism/src/prism/PrismCL.java +++ b/prism/src/prism/PrismCL.java @@ -2047,8 +2047,7 @@ else if (simMethodName.equals("sprt")) { private static String buildHelpLeft(SwitchEntry entry) { StringBuilder left = new StringBuilder("-").append(entry.primaryName); - if (!entry.argHint.isEmpty()) - left.append(" ").append(entry.argHint); + left.append(entry.formattedArgHint()); if (entry.shownAliases.length > 0) { left.append(" (or "); for (int i = 0; i < entry.shownAliases.length; i++) { diff --git a/prism/src/prism/SwitchHandler.java b/prism/src/prism/SwitchHandler.java index c41d757a05..a2103c567b 100644 --- a/prism/src/prism/SwitchHandler.java +++ b/prism/src/prism/SwitchHandler.java @@ -227,7 +227,7 @@ class SwitchEntry final String group; final String primaryName; // null = blank-line sentinel final String[] shownAliases; // aliases displayed in -help output (subset of all registered names) - final String argHint; // null = hidden; "" = flag switch; e.g. "", "" + final String argHint; // null = hidden; "" = flag switch; e.g. "", ""; "[:options]" for a colon suffix on the switch token itself final String shortText; // description text; null for hidden/blank-line sentinels final Consumer longDesc; @@ -244,17 +244,28 @@ class SwitchEntry this.longDesc = longDesc; } - /** Print detailed help: auto-generates the "Switch: -name [aliases] [argHint]" header, then the body. */ + /** + * The arg hint formatted for display directly after the switch name: glued with no space + * for a colon-suffix hint like {@code "[:options]"} (it attaches to the switch token itself, + * not a separate argument), space-separated otherwise (e.g. {@code ""}). Empty/null hints + * (flag switches / hidden entries) yield {@code ""}. + */ + String formattedArgHint() + { + if (argHint == null || argHint.isEmpty()) return ""; + return argHint.startsWith("[:") ? argHint : " " + argHint; + } + + /** Print detailed help: auto-generates the "Switch: -name [argHint] [aliases]" header, then the body. */ void printLongDesc(PrismLog log) { StringBuilder header = new StringBuilder("Switch: -").append(primaryName); + header.append(formattedArgHint()); if (shownAliases != null && shownAliases.length > 0) { header.append(" (or"); for (String alias : shownAliases) header.append(" -").append(alias); header.append(")"); } - if (argHint != null && !argHint.isEmpty()) - header.append(" ").append(argHint); log.println(header + "\n"); if (longDesc != null) { longDesc.accept(log); From d5bb1d658ce5afe3272e6e83d162834a6124b4e3 Mon Sep 17 00:00:00 2001 From: Dave Parker Date: Tue, 30 Jun 2026 08:35:02 +0100 Subject: [PATCH 08/15] Use OptionParser mechanisms for -intervaliter switch too. --- prism/src/prism/OptionsIntervalIteration.java | 150 +++++------------- prism/src/prism/PrismSettings.java | 25 ++- 2 files changed, 55 insertions(+), 120 deletions(-) diff --git a/prism/src/prism/OptionsIntervalIteration.java b/prism/src/prism/OptionsIntervalIteration.java index 78d01a8d58..03b479cf45 100644 --- a/prism/src/prism/OptionsIntervalIteration.java +++ b/prism/src/prism/OptionsIntervalIteration.java @@ -26,9 +26,6 @@ package prism; -import java.util.ArrayList; -import java.util.List; -import java.util.Map.Entry; /** Option handling for interval iteration */ public class OptionsIntervalIteration @@ -78,6 +75,8 @@ public OptionsIntervalIteration(String options) throws PrismException fromOptionsString(options); } + private OptionsIntervalIteration() {} // use field defaults (for buildParser dummy target) + /** Static constructor from PrismComponent settings, throws PrismException on errors */ public static OptionsIntervalIteration from(PrismComponent parent) throws PrismException { @@ -90,12 +89,6 @@ public static OptionsIntervalIteration from(PrismSettings settings) throws Prism return new OptionsIntervalIteration(settings.getString(PrismSettings.PRISM_INTERVAL_ITER_OPTIONS)); } - /** Validate an options String, throws PrismException on errors */ - public static void validate(String optionsString) throws PrismException - { - new OptionsIntervalIteration(optionsString); - } - /** The method for computing upper bounds for reward computations */ public BoundMethod getBoundMethod() { @@ -156,7 +149,7 @@ public Double getManualUpperBound() return manualUpperBound; } - /** Get helper text for the options */ + /** Get helper text for the options (used by GUI settings tooltip). */ public static String getOptionsDescription() { StringBuffer sb = new StringBuffer(); @@ -170,109 +163,54 @@ public static String getOptionsDescription() sb.append(" [no]monotonicabove Enforce monotonicity in iteration from above (default = yes)\n"); sb.append(" [no]checkmonotonic Check monotonicity, for testing (default = no)\n"); sb.append("\nExample: boundmethod=default,upper=3.0,noselectmidpoint,checkmonotonic\n"); - return sb.toString(); } - /** Parse options string, throw on error */ - private void fromOptionsString(String options) throws PrismException + /** Print the options using {@link OptionParser} formatting (for CLI -help output). */ + public static void printOptions(PrismLog log) { - for (Entry entry : splitOptionsString(options)){ - String option = entry.getKey(); - String extra = entry.getValue(); - - boolean isBooleanOption = true; - switch (option) { - case "boundverbose": - case "noboundverbose": - boundComputationVerbose = !option.startsWith("no"); - break; - case "selectmidpoint": - case "noselectmidpoint": - resultSelectMidpoint = !option.startsWith("no"); - break; - case "checkmonotonic": - case "nocheckmonotonic": - checkMonotonicity = !option.startsWith("no"); - break; - case "monotonicbelow": - case "nomonotonicbelow": - enforceMonotonicityBelow = !option.startsWith("no"); - break; - case "monotonicabove": - case "nomonotonicabove": - enforceMonotonicityAbove = !option.startsWith("no"); - break; - case "lower": - case "upper": { - if (extra == null) - throw new PrismException("Missing argument to interval iteration option '" + option + "'"); - try { - Double value = Double.parseDouble(extra); - if (option.equals("lower")) { - manualLowerBound = value; - } else { - manualUpperBound = value; - } - } catch (NumberFormatException e) { - throw new PrismException("Illegal argument to interval iteration option '" + option + "': " + e.getMessage()); - } - isBooleanOption = false; - break; - } - case "boundmethod": { - if (extra == null) - throw new PrismException("Missing argument to interval iteration option '" + option + "'"); - switch (extra) { - case "default": - boundMethod = BoundMethod.DEFAULT; - break; - case "variant-1-coarse": - boundMethod = BoundMethod.VARIANT_1_COARSE; - break; - case "variant-1-fine": - boundMethod = BoundMethod.VARIANT_1_FINE; - break; - case "variant-2": - boundMethod = BoundMethod.VARIANT_2; - break; - case "dsmpi": - boundMethod = BoundMethod.DSMPI; - break; - default: - throw new PrismException("Unknown argument to interval iteration option '" + option + "', expected one of " - + boundMethodsList); - } - isBooleanOption = false; - break; - } - default: - throw new PrismException("Unknown interval iteration option '" + option + "'"); - } - - if (isBooleanOption) { - if (extra != null) { - throw new PrismException("Interval iteration option '" + option + "' has additional argument (" + extra + "), but is boolean option"); - } - } - } + parser().printOptions(log); + log.println("\nExample: boundmethod=default,upper=3.0,noselectmidpoint,checkmonotonic"); } - /** Split options string into list of pairs */ - private static List> splitOptionsString(String options) + /** + * An {@link OptionParser} for parsing/validating an interval-iteration options string + * (built with a throwaway dummy target; for CLI switch registration and -help output). + */ + static OptionParser parser() { - List> list = new ArrayList<>(); - if ("".equals(options)) - return list; + return buildParser(new OptionsIntervalIteration()); + } - for (String option : options.split(",")) { - int j = option.indexOf("="); - if (j == -1) { - list.add(new Pair<>(option.trim(), null)); - } else { - list.add(new Pair<>(option.substring(0,j).trim(), option.substring(j+1).trim())); - } - } - return list; + /** Parse options string, throw on error */ + private void fromOptionsString(String options) throws PrismException + { + buildParser(this).parse(options, "intervaliter"); + } + + /** Build an {@link OptionParser} whose actions set fields on {@code t}. */ + private static OptionParser buildParser(OptionsIntervalIteration t) + { + return new OptionParser() + .choice("boundmethod", "Select upper bound heuristic for reward computations", + new OptionParser.Choice() + .when("default", () -> t.boundMethod = BoundMethod.DEFAULT) + .when("variant-1-coarse", () -> t.boundMethod = BoundMethod.VARIANT_1_COARSE) + .when("variant-1-fine", () -> t.boundMethod = BoundMethod.VARIANT_1_FINE) + .when("variant-2", () -> t.boundMethod = BoundMethod.VARIANT_2) + .when("dsmpi", () -> t.boundMethod = BoundMethod.DSMPI)) + .string("lower", "", "Manually specify lower bound for reward computations", v -> { + try { t.manualLowerBound = Double.parseDouble(v); } + catch (NumberFormatException e) { throw new PrismException("Invalid value for 'lower' option: " + v); } + }) + .string("upper", "", "Manually specify upper bound for reward computations", v -> { + try { t.manualUpperBound = Double.parseDouble(v); } + catch (NumberFormatException e) { throw new PrismException("Invalid value for 'upper' option: " + v); } + }) + .toggle("boundverbose", "Verbose output for upper bound computations (default = no)", v -> t.boundComputationVerbose = v) + .toggle("selectmidpoint", "Select midpoint between upper and lower as the result (default = yes)", v -> t.resultSelectMidpoint = v) + .toggle("monotonicbelow", "Enforce monotonicity in iteration from below (default = yes)", v -> t.enforceMonotonicityBelow = v) + .toggle("monotonicabove", "Enforce monotonicity in iteration from above (default = yes)", v -> t.enforceMonotonicityAbove = v) + .toggle("checkmonotonic", "Check monotonicity, for testing (default = no)", v -> t.checkMonotonicity = v); } } diff --git a/prism/src/prism/PrismSettings.java b/prism/src/prism/PrismSettings.java index 38dc5cbae8..488b932ee2 100644 --- a/prism/src/prism/PrismSettings.java +++ b/prism/src/prism/PrismSettings.java @@ -1070,25 +1070,22 @@ void registerSwitchHandlers(SwitchRegistry reg, Prism prism, SwitchHandler param "", "Use policy iteration for solving MDPs"); reg.addSwitch("modpoliter", new FlagSwitch(() -> set(PRISM_MDP_SOLN_METHOD, "Modified policy iteration")), "", "Use modified policy iteration for solving MDPs"); - reg.addSwitch("intervaliter", "ii", (sw, a) -> { + reg.addSwitch("intervaliter", "ii", new OptionsOnlySwitch(OptionsIntervalIteration.parser(), parse -> { set(PRISM_INTERVAL_ITER, true); - String opts = a.optionsString(); - if (opts != null) { - opts = opts.trim(); - try { - OptionsIntervalIteration.validate(opts); - } catch (PrismException e) { - throw new PrismException("In options for -" + sw + " switch: " + e.getMessage()); - } + String opts = parse.options().trim(); + if (!opts.isEmpty()) { + // Parse options just to make sure they are valid + parse.run(opts); + // Then concatenate with any other -intervaliter options, and process later String existing = getString(PRISM_INTERVAL_ITER_OPTIONS); set(PRISM_INTERVAL_ITER_OPTIONS, "".equals(existing) ? opts : existing + "," + opts); } - }, "", "Use interval iteration to solve MDPs/MCs (see -help -ii)", + }), "[:]", "Use interval iteration to solve MDPs/MCs (see -help -ii)", log -> { - log.println("Optionally takes a comma-separated list of options:\n"); - log.println(" -intervaliter:option1,option2,...\n"); - log.println("where the options are one of the following:\n"); - log.println(OptionsIntervalIteration.getOptionsDescription()); + log.println("Use interval iteration to solve MDPs/MCs."); + log.println(); + log.println("If provided, is a comma-separated list of options taken from:"); + OptionsIntervalIteration.printOptions(log); }); reg.addSwitch("topological", new FlagSwitch(() -> set(PRISM_TOPOLOGICAL_VI, true)), "", "Use topological value iteration"); From 9bbf749d3248ea6e7d2d435bea7ab2069e21bae4 Mon Sep 17 00:00:00 2001 From: Dave Parker Date: Tue, 30 Jun 2026 08:37:36 +0100 Subject: [PATCH 09/15] Help text tweaks. --- prism/src/prism/PrismCL.java | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/prism/src/prism/PrismCL.java b/prism/src/prism/PrismCL.java index 67c52a3580..5c7ddf14cd 100644 --- a/prism/src/prism/PrismCL.java +++ b/prism/src/prism/PrismCL.java @@ -1213,7 +1213,7 @@ private void initSwitchHandlers() .when("umb", () -> { for (ModelImportSource s : modelImportSources) s.format = ModelExportFormat.UMB; })), this::processImportModelSwitch); registry.addSwitch("importmodel", importModelSwitch, - "[:options]", "Import the model directly from file(s)", + "[:]", "Import the model directly from file(s)", log -> { log.println("Import the model directly from one or more file(s)."); log.println("Use a list of file extensions to indicate which files should be read, e.g.:"); @@ -1280,7 +1280,7 @@ private void initSwitchHandlers() : (matrix[0] ? ResultsExportShape.MATRIX_PLAIN : ResultsExportShape.LIST_PLAIN); }); registry.addSwitch("exportresults", exportResultsSwitch, - "", "Export the results of model checking to a file", + "]>", "Export the results of model checking to a file", log -> { log.println("Exports the results of model checking to (or to the screen if =\"stdout\")."); log.println("The default behaviour is to export a list of results in text form, using tabs to separate items."); @@ -1328,18 +1328,18 @@ private void initSwitchHandlers() .bool("headers", "include headers in explicit (reward) files", v -> pendingExportOptions.setPrintHeaders(v)), this::processExportModelSwitch); registry.addSwitch("exportmodel", exportModelSwitch, - "", "Export the built model to file(s)", + "]>", "Export the built model to file(s)", log -> { log.println("Export the built model to file(s) (or to the screen if =\"stdout\")."); log.println("Use a list of file extensions to indicate which files should be generated, e.g.:"); - log.println("\n -exportmodel out.tra,sta\n"); - log.println("\n -exportmodel out.umb\n"); - log.println("Possible extensions are: .tra, .srew, .trew, .lab, .sta, .obs, .dot, .umb, .drn"); + log.println("\n -exportmodel out.tra,sta"); + log.println(" -exportmodel out.umb"); + log.println("\nPossible extensions are: .tra, .srew, .trew, .lab, .sta, .obs, .dot, .umb, .drn"); log.println("Use extension .all to export all explicit files (.tra/srew/trew/lab/sta/obs), e.g.:"); - log.println("\n -exportmodel out.all\n"); - log.println("Omit the file basename to use the basename of the model file, e.g.:"); - log.println("\n -exportmodel .all\n"); - log.println("Use extension .rew to export both .srew/.trew files"); + log.println("\n -exportmodel out.all"); + log.println("\nOmit the file basename to use the basename of the model file, e.g.:"); + log.println("\n -exportmodel .all"); + log.println("\nUse extension .rew to export both .srew/.trew files"); log.println(); log.println("If provided, is a comma-separated list of options taken from:"); exportModelSwitch.printOptions(log); @@ -1374,7 +1374,7 @@ private void initSwitchHandlers() modelExportTasks.add(pending[0]); }); registry.addSwitch("exportlabels", exportLabelsSwitch, - "", "Export the list of labels and satisfying states to a file", + "]>", "Export the list of labels and satisfying states to a file", log -> { log.println("Export the list of labels and satisfying states to a file (or to the screen if =\"stdout\")."); log.println(); @@ -1391,7 +1391,7 @@ private void initSwitchHandlers() modelExportTasks.add(pending[0]); }); registry.addSwitch("exportproplabels", exportPropLabelsSwitch, - "", "Export the list of labels and satisfying states from the properties file to a file", + "]>", "Export the list of labels and satisfying states from the properties file to a file", log -> { log.println("Export the list of labels and satisfying states from the properties file to a file (or to the screen if =\"stdout\")."); log.println(); @@ -1413,7 +1413,7 @@ private void initSwitchHandlers() .bool("obs", "for partially observable models, whether to merge observationally equivalent states", v -> exportStratOptions.setMergeObservations(v)), this::processExportStratSwitch); registry.addSwitch("exportstrat", exportStratSwitch, - "", "Generate and export a strategy to a file", + "]>", "Generate and export a strategy to a file", log -> { log.println("Generate and export a strategy to a file (or to the screen if =\"stdout\")."); log.println("Use file extension .tra or .dot to export as an induced model or Dot file, respectively."); From eb1c8bf99b9db5be0bced540c1d22f6f60a99d55 Mon Sep 17 00:00:00 2001 From: Dave Parker Date: Tue, 30 Jun 2026 09:30:26 +0100 Subject: [PATCH 10/15] -exporttrans, -exportstates, etc. take options (plus refactoring). For now, just precision=x is supported. The code to generate switches for exporting individual model entities is refactored to avoid repetition. To support this, we also add an int option for OptionalParser, with an optional validity predicate, and a default long text description. --- prism/src/prism/OptionParser.java | 53 ++++++++++ prism/src/prism/PrismCL.java | 153 ++++++++++++++++++----------- prism/src/prism/SwitchHandler.java | 10 +- 3 files changed, 158 insertions(+), 58 deletions(-) diff --git a/prism/src/prism/OptionParser.java b/prism/src/prism/OptionParser.java index f16d9f5753..86fd1fbecf 100644 --- a/prism/src/prism/OptionParser.java +++ b/prism/src/prism/OptionParser.java @@ -29,6 +29,7 @@ import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; +import java.util.function.IntPredicate; /** * Builder for parsing a comma-separated list of sub-options within a CLI switch argument @@ -55,6 +56,7 @@ class OptionParser @FunctionalInterface interface FlagAction { void run() throws PrismException; } @FunctionalInterface interface StringAction { void accept(String v) throws PrismException; } @FunctionalInterface interface BoolAction { void accept(boolean b) throws PrismException; } + @FunctionalInterface interface IntAction { void accept(int n) throws PrismException; } /** Internal handler for key=value options. */ @FunctionalInterface @@ -129,6 +131,57 @@ OptionParser string(String name, String argHint, String description, StringActio return this; } + // ── Integer options ─────────────────────────────────────────────────────── + + /** Register a {@code key=} integer option (action only; no extra validation beyond being a valid int). */ + OptionParser integer(String name, IntAction action) + { + valueHandlers.put(name, intHandler(null, action)); + return this; + } + + /** Record a {@code key=} integer option for help output only. */ + OptionParser integer(String name, String argHint, String description) + { + helpEntries.add(new HelpEntry("string", name, argHint, description)); + return this; + } + + /** Register a {@code key=} integer option with both execution handler and help metadata (no extra validation). */ + OptionParser integer(String name, String argHint, String description, IntAction action) + { + valueHandlers.put(name, intHandler(null, action)); + helpEntries.add(new HelpEntry("string", name, argHint, description)); + return this; + } + + /** + * Register a {@code key=} integer option with both execution handler and help metadata, + * additionally validated by {@code valid} (e.g. a range check) beyond being a valid int. + */ + OptionParser integer(String name, String argHint, IntPredicate valid, String description, IntAction action) + { + valueHandlers.put(name, intHandler(valid, action)); + helpEntries.add(new HelpEntry("string", name, argHint, description)); + return this; + } + + /** Build a {@link ValueHandler} that parses an int, optionally checks {@code valid}, then dispatches. */ + private static ValueHandler intHandler(IntPredicate valid, IntAction action) + { + return (key, val, sw) -> { + int n; + try { + n = Integer.parseInt(val); + } catch (NumberFormatException e) { + throw new PrismException("Invalid value \"" + val + "\" for \"" + key + "\" option of -" + sw); + } + if (valid != null && !valid.test(n)) + throw new PrismException("Invalid value \"" + val + "\" for \"" + key + "\" option of -" + sw); + action.accept(n); + }; + } + // ── Toggle options ──────────────────────────────────────────────────────── /** diff --git a/prism/src/prism/PrismCL.java b/prism/src/prism/PrismCL.java index 5c7ddf14cd..75a6f36a1d 100644 --- a/prism/src/prism/PrismCL.java +++ b/prism/src/prism/PrismCL.java @@ -1303,15 +1303,9 @@ private void initSwitchHandlers() .bool("states", "whether to include state definitions", v -> pendingExportOptions.setShowStates(v)) .bool("obs", "whether to include observation definitions", v -> pendingExportOptions.setShowObservations(v)) .bool("actions", "whether to include actions on choices/transitions", v -> pendingExportOptions.setShowActions(v)) - .string("precision","", "use significant figures for floating point values (in text)", v -> { - try { - int n = Integer.parseInt(v); - if (!RANGE_EXPORT_DOUBLE_PRECISION.contains(n)) throw new NumberFormatException(); - pendingExportOptions.setModelPrecision(n); - } catch (NumberFormatException e) { - throw new PrismException("Invalid value \"" + v + "\" for \"precision\" option of -exportmodel"); - } - }) + .integer("precision", "", n -> RANGE_EXPORT_DOUBLE_PRECISION.contains(n), + "use significant figures for floating point values (in text)", + n -> pendingExportOptions.setModelPrecision(n)) .choice("zip", "whether to zip UMB files", new OptionParser.Choice() .when("true", () -> pendingExportOptions.setZipped(true)) .when("false", () -> pendingExportOptions.setZipped(false)) @@ -1344,60 +1338,59 @@ private void initSwitchHandlers() log.println("If provided, is a comma-separated list of options taken from:"); exportModelSwitch.printOptions(log); }); - registry.addSwitch("exporttrans", new StringSwitch(s -> modelExportTasks.add( - new ModelExportTask(ModelExportTask.ModelExportEntity.MODEL, s))), - "", "Export the transition matrix to a file"); - registry.addSwitch("exportstaterewards", new StringSwitch(s -> modelExportTasks.add( - new ModelExportTask(ModelExportTask.ModelExportEntity.STATE_REWARDS, s))), - "", "Export the state rewards vector to a file"); - registry.addSwitch("exporttransrewards", new StringSwitch(s -> modelExportTasks.add( - new ModelExportTask(ModelExportTask.ModelExportEntity.TRANSITION_REWARDS, s))), - "", "Export the transition rewards matrix to a file"); + StringPlusOptionsSwitch exportTransSwitch = exportEntitySwitch(ModelExportTask.ModelExportEntity.MODEL); + registry.addSwitch("exporttrans", exportTransSwitch, + "[:]", "Export the transition matrix to a file"); + StringPlusOptionsSwitch exportStateRewardsSwitch = exportEntitySwitch(ModelExportTask.ModelExportEntity.STATE_REWARDS); + registry.addSwitch("exportstaterewards", exportStateRewardsSwitch, + "[:]", "Export the state rewards vector to a file"); + StringPlusOptionsSwitch exportTransRewardsSwitch = exportEntitySwitch(ModelExportTask.ModelExportEntity.TRANSITION_REWARDS); + registry.addSwitch("exporttransrewards", exportTransRewardsSwitch, + "[:]", "Export the transition rewards matrix to a file"); + OptionParser exportRewardsOptionsParser = exportTaskOptionsParser(); registry.addSwitch("exportrewards", (sw, a) -> { - modelExportTasks.add(new ModelExportTask(ModelExportTask.ModelExportEntity.STATE_REWARDS, a.next(sw))); - modelExportTasks.add(new ModelExportTask(ModelExportTask.ModelExportEntity.TRANSITION_REWARDS, a.next(sw))); - }, " ", "Export state/transition rewards to files 1/2"); - registry.addSwitch("exportstates", new StringSwitch(s -> modelExportTasks.add( - new ModelExportTask(ModelExportTask.ModelExportEntity.STATES, s))), - "", "Export the list of reachable states to a file"); - registry.addSwitch("exportobs", new StringSwitch(s -> modelExportTasks.add( - new ModelExportTask(ModelExportTask.ModelExportEntity.OBSERVATIONS, s))), - "", "Export the list of observations to a file"); - ModelExportTask[] pending = {null}; - StringPlusOptionsSwitch exportLabelsSwitch = new StringPlusOptionsSwitch( - new OptionParser() - .flag("matlab", "export data in Matlab format", () -> pending[0].getExportOptions().setFormat(ModelExportFormat.MATLAB)) - .flag("proplabels", "export labels from a properties file into the same file, too",() -> pending[0].setLabelExportSet(ModelExportTask.LabelExportSet.ALL)), - (file, parse) -> { - pending[0] = new ModelExportTask(ModelExportTask.ModelExportEntity.LABELS, file); - parse.run(); - modelExportTasks.add(pending[0]); - }); - registry.addSwitch("exportlabels", exportLabelsSwitch, - "]>", "Export the list of labels and satisfying states to a file", + String[] file1 = StringPlusOptionsSwitch.splitFilesAndOptions(a.next(sw)); + pendingExportOptions = new ModelExportOptions(); + exportRewardsOptionsParser.parse(file1[1], sw); + ModelExportTask stateRewardsTask = new ModelExportTask(ModelExportTask.ModelExportEntity.STATE_REWARDS, file1[0]); + stateRewardsTask.getExportOptions().apply(pendingExportOptions); + modelExportTasks.add(stateRewardsTask); + + String[] file2 = StringPlusOptionsSwitch.splitFilesAndOptions(a.next(sw)); + pendingExportOptions = new ModelExportOptions(); + exportRewardsOptionsParser.parse(file2[1], sw); + ModelExportTask transRewardsTask = new ModelExportTask(ModelExportTask.ModelExportEntity.TRANSITION_REWARDS, file2[0]); + transRewardsTask.getExportOptions().apply(pendingExportOptions); + modelExportTasks.add(transRewardsTask); + }, "]> ]>", "Export state/transition rewards to files 1/2", log -> { - log.println("Export the list of labels and satisfying states to a file (or to the screen if =\"stdout\")."); + log.println("Export state/transition rewards to files 1/2 (or to the screen if =\"stdout\")."); log.println(); - log.println("If provided, is a comma-separated list of options taken from:"); - exportLabelsSwitch.printOptions(log); + log.println("If provided, is a comma-separated list of options (for either file) taken from:"); + exportRewardsOptionsParser.printOptions(log); }); - StringPlusOptionsSwitch exportPropLabelsSwitch = new StringPlusOptionsSwitch( - new OptionParser() - .flag("matlab", "export data in Matlab format", () -> pending[0].getExportOptions().setFormat(ModelExportFormat.MATLAB)), - (file, parse) -> { - pending[0] = new ModelExportTask(ModelExportTask.ModelExportEntity.LABELS, file); - pending[0].setLabelExportSet(ModelExportTask.LabelExportSet.EXTRA); - parse.run(); - modelExportTasks.add(pending[0]); + StringPlusOptionsSwitch exportStatesSwitch = exportEntitySwitch(ModelExportTask.ModelExportEntity.STATES); + registry.addSwitch("exportstates", exportStatesSwitch, + "[:]", "Export the list of reachable states to a file"); + StringPlusOptionsSwitch exportObsSwitch = exportEntitySwitch(ModelExportTask.ModelExportEntity.OBSERVATIONS); + registry.addSwitch("exportobs", exportObsSwitch, + "[:]", "Export the list of observations to a file"); + ModelExportTask.LabelExportSet[] pendingLabelExportSet = {ModelExportTask.LabelExportSet.MODEL}; + StringPlusOptionsSwitch exportLabelsSwitch = exportEntitySwitch(ModelExportTask.ModelExportEntity.LABELS, + p -> p + .flag("matlab", "export data in Matlab format", () -> pendingExportOptions.setFormat(ModelExportFormat.MATLAB)) + .flag("proplabels", "export labels from a properties file into the same file, too", () -> pendingLabelExportSet[0] = ModelExportTask.LabelExportSet.ALL), + t -> { + t.setLabelExportSet(pendingLabelExportSet[0]); + pendingLabelExportSet[0] = ModelExportTask.LabelExportSet.MODEL; }); + registry.addSwitch("exportlabels", exportLabelsSwitch, + "]>", "Export the list of labels and satisfying states to a file"); + StringPlusOptionsSwitch exportPropLabelsSwitch = exportEntitySwitch(ModelExportTask.ModelExportEntity.LABELS, + p -> p.flag("matlab", "export data in Matlab format", () -> pendingExportOptions.setFormat(ModelExportFormat.MATLAB)), + t -> t.setLabelExportSet(ModelExportTask.LabelExportSet.EXTRA)); registry.addSwitch("exportproplabels", exportPropLabelsSwitch, - "]>", "Export the list of labels and satisfying states from the properties file to a file", - log -> { - log.println("Export the list of labels and satisfying states from the properties file to a file (or to the screen if =\"stdout\")."); - log.println(); - log.println("If provided, is a comma-separated list of options taken from:"); - exportPropLabelsSwitch.printOptions(log); - }); + "]>", "Export the list of labels and satisfying states from the properties file to a file"); StringPlusOptionsSwitch exportStratSwitch = new StringPlusOptionsSwitch( new OptionParser() .choice("type", "type of strategy export", new OptionParser.Choice() @@ -1720,6 +1713,52 @@ private void addTransitionRewardImports(String basename, boolean assumeExists) } } + /** + * Build an {@link OptionParser} for the {@code precision}/{@code headers} sub-options shared + * by the simple per-entity export switches ({@code -exporttrans}, {@code -exportstates}, etc., + * as opposed to the full option set of {@code -exportmodel}). Actions target + * {@link #pendingExportOptions}, which the caller must reset before parsing and apply to the + * resulting {@link ModelExportTask}(s) afterwards. + */ + private OptionParser exportTaskOptionsParser() + { + return new OptionParser() + .integer("precision", "", n -> RANGE_EXPORT_DOUBLE_PRECISION.contains(n), + "use significant figures for floating point values (in text)", + n -> pendingExportOptions.setModelPrecision(n)); + } + + /** + * Build a {@link StringPlusOptionsSwitch} that put a single {@link ModelExportTask} of the + * given entity into {@link #modelExportTasks}, adding common options for exporting + * model entities (see {@link #exportTaskOptionsParser}). + */ + private StringPlusOptionsSwitch exportEntitySwitch(ModelExportTask.ModelExportEntity entity) + { + return exportEntitySwitch(entity, p -> {}, t -> {}); + } + + /** + * As {@link #exportEntitySwitch(ModelExportTask.ModelExportEntity)}, but additionally lets the + * caller register extra sub-options on the parser ({@code extraOptions}, applied once at + * registration time) and post-process the created task ({@code postProcess}, run on every + * invocation after the {@code precision}/{@code headers} options have been applied). + */ + private StringPlusOptionsSwitch exportEntitySwitch(ModelExportTask.ModelExportEntity entity, + Consumer extraOptions, Consumer postProcess) + { + OptionParser parser = exportTaskOptionsParser(); + extraOptions.accept(parser); + return new StringPlusOptionsSwitch(parser, (file, parse) -> { + pendingExportOptions = new ModelExportOptions(); + parse.run(); + ModelExportTask task = new ModelExportTask(entity, file); + task.getExportOptions().apply(pendingExportOptions); + postProcess.accept(task); + modelExportTasks.add(task); + }); + } + /** * Process the arguments (files, options) to the -exportmodel switch * NB: This is done at the time of parsing switches (not later) diff --git a/prism/src/prism/SwitchHandler.java b/prism/src/prism/SwitchHandler.java index a2103c567b..77cec5f668 100644 --- a/prism/src/prism/SwitchHandler.java +++ b/prism/src/prism/SwitchHandler.java @@ -272,8 +272,16 @@ void printLongDesc(PrismLog log) } else { if (shortText != null && !shortText.isEmpty()) log.println(shortText); - if (handler instanceof EnumSwitch) + if (handler instanceof EnumSwitch) { ((EnumSwitch) handler).printOptions(log); + } else if (handler instanceof StringPlusOptionsSwitch || handler instanceof OptionsOnlySwitch) { + log.println(); + log.println("If provided, is a comma-separated list of options taken from:"); + if (handler instanceof StringPlusOptionsSwitch) + ((StringPlusOptionsSwitch) handler).printOptions(log); + else + ((OptionsOnlySwitch) handler).printOptions(log); + } } } } From 88988be0990e6630be7c01c16c2f917d13830a41 Mon Sep 17 00:00:00 2001 From: Dave Parker Date: Tue, 30 Jun 2026 09:50:01 +0100 Subject: [PATCH 11/15] Restore -gs switch in MDP methods help text. (got lost in refactor because was duplicated across sections) --- prism/src/prism/PrismSettings.java | 1 + prism/src/prism/SwitchRegistry.java | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/prism/src/prism/PrismSettings.java b/prism/src/prism/PrismSettings.java index 488b932ee2..8cd673afaf 100644 --- a/prism/src/prism/PrismSettings.java +++ b/prism/src/prism/PrismSettings.java @@ -1066,6 +1066,7 @@ void registerSwitchHandlers(SwitchRegistry reg, Prism prism, SwitchHandler param set(PRISM_MDP_MULTI_SOLN_METHOD, "Value iteration"); set(PRISM_IMDP_SOLN_METHOD, "Value iteration"); }), "", "Use value iteration for solving MDPs [default]"); + reg.addSwitchAlias("gaussseidel", new String[]{"gs"}, "Use Gauss-Seidel value iteration for solving MDPs"); reg.addSwitch("politer", new FlagSwitch(() -> set(PRISM_MDP_SOLN_METHOD, "Policy iteration")), "", "Use policy iteration for solving MDPs"); reg.addSwitch("modpoliter", new FlagSwitch(() -> set(PRISM_MDP_SOLN_METHOD, "Modified policy iteration")), diff --git a/prism/src/prism/SwitchRegistry.java b/prism/src/prism/SwitchRegistry.java index 001dab27c1..69b4573d00 100644 --- a/prism/src/prism/SwitchRegistry.java +++ b/prism/src/prism/SwitchRegistry.java @@ -140,4 +140,14 @@ void addBlankLine() { map.put("__blank_" + map.size(), new SwitchEntry(null, null, null, NO_ALIASES, null, null, null)); } + + /** + * Add a second, doc-only listing of an already-registered flag switch under the current group + * (e.g. a switch relevant to two different sections). Purely for {@code -help} output; + * does not affect dispatch since it is stored under a synthetic key. + */ + void addSwitchAlias(String name, String[] shownAliases, String shortText) + { + map.put("__alias_" + map.size(), new SwitchEntry(null, currentGroup, name, shownAliases, "", shortText, null)); + } } From ba1dcd4dc466ac74a0d352e50b0f725e8267436b Mon Sep 17 00:00:00 2001 From: Dave Parker Date: Tue, 30 Jun 2026 10:18:00 +0100 Subject: [PATCH 12/15] Restore -test:umb switch. (got lost in refactor) --- prism/src/prism/PrismCL.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/prism/src/prism/PrismCL.java b/prism/src/prism/PrismCL.java index 75a6f36a1d..8d978261ef 100644 --- a/prism/src/prism/PrismCL.java +++ b/prism/src/prism/PrismCL.java @@ -1166,8 +1166,10 @@ private void initSwitchHandlers() }); registry.addSwitch("nobuild", new FlagSwitch(() -> nobuild = true), "", "Skip model construction (just do parse/export)"); - registry.addSwitch("test", new FlagSwitch(() -> test = true), - "", "Enable \"test\" mode"); + registry.addSwitch("test", new OptionsOnlySwitch( + new OptionParser().flag("umb", "Enable UMB round trip test", () -> prism.setTestUMB(true)), + parse -> { test = true; parse.run(); }), + "[:]", "Enable \"test\" mode"); registry.addSwitch("testall", new FlagSwitch(() -> { test = true; testExitsOnFail = false; }), "", "Enable \"test\" mode, but don't exit on error"); registry.addSwitch("javamaxmem", (sw, a) -> a.next(sw), // consumed before JVM launch @@ -1186,7 +1188,6 @@ private void initSwitchHandlers() // Hidden general switches registry.addSwitch("keywords", new FlagSwitch(() -> { printListOfKeywords(); exit(); })); - registry.addSwitch("test:umb", new FlagSwitch(() -> prism.setTestUMB(true))); registry.addSwitch("dddebug", new FlagSwitch(() -> jdd.DebugJDD.enable())); registry.addSwitch("ddtraceall", new FlagSwitch(() -> jdd.DebugJDD.traceAll = true)); registry.addSwitch("ddtracefollowcopies", new FlagSwitch(() -> jdd.DebugJDD.traceFollowCopies = true)); From 6eaa4a6e039c234150b731a2d922e876d26bdabc Mon Sep 17 00:00:00 2001 From: Dave Parker Date: Tue, 30 Jun 2026 10:19:09 +0100 Subject: [PATCH 13/15] Minor fix for splitFilesAndOptions. (if no : and starts with \) --- prism/src/prism/SwitchHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prism/src/prism/SwitchHandler.java b/prism/src/prism/SwitchHandler.java index 77cec5f668..93b0c698bd 100644 --- a/prism/src/prism/SwitchHandler.java +++ b/prism/src/prism/SwitchHandler.java @@ -171,7 +171,7 @@ void handleFilesOnly(String sw, String files) throws PrismException static String[] splitFilesAndOptions(String arg) { int i = arg.indexOf(':'); - while (arg.length() > i + 1 && arg.charAt(i + 1) == '\\') + while (i != -1 && arg.length() > i + 1 && arg.charAt(i + 1) == '\\') i = arg.indexOf(':', i + 1); if (i != -1) return new String[]{arg.substring(0, i), arg.substring(i + 1)}; From debf055449626aa5d00e48e46e17a8f5c41ef40c Mon Sep 17 00:00:00 2001 From: Dave Parker Date: Tue, 30 Jun 2026 10:20:40 +0100 Subject: [PATCH 14/15] OptionParser: add white space trimming. --- prism/src/prism/OptionParser.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/prism/src/prism/OptionParser.java b/prism/src/prism/OptionParser.java index 86fd1fbecf..ef69935972 100644 --- a/prism/src/prism/OptionParser.java +++ b/prism/src/prism/OptionParser.java @@ -291,7 +291,8 @@ void printOptions(PrismLog log) void parse(String optionsString, String switchName) throws PrismException { if (optionsString == null || optionsString.isEmpty()) return; - for (String opt : optionsString.split(",")) { + for (String rawOpt : optionsString.split(",")) { + String opt = rawOpt.trim(); if (opt.isEmpty()) continue; int eq = opt.indexOf('='); if (eq == -1) { @@ -301,8 +302,8 @@ void parse(String optionsString, String switchName) throws PrismException throw new PrismException("No value provided for \"" + opt + "\" option of -" + switchName); throw new PrismException("Unknown option \"" + opt + "\" for -" + switchName + " switch"); } else { - String key = opt.substring(0, eq); - String val = opt.substring(eq + 1); + String key = opt.substring(0, eq).trim(); + String val = opt.substring(eq + 1).trim(); ValueHandler vh = valueHandlers.get(key); if (vh != null) { vh.accept(key, val, switchName); continue; } if (flagHandlers.containsKey(key)) From a2b1a81fef856b455872075bafee7a32baab8abf Mon Sep 17 00:00:00 2001 From: Dave Parker Date: Tue, 30 Jun 2026 10:35:34 +0100 Subject: [PATCH 15/15] Unhide the -mainlog switch (from -help). --- prism/src/prism/PrismCL.java | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/prism/src/prism/PrismCL.java b/prism/src/prism/PrismCL.java index 8d978261ef..d25f9d8e13 100644 --- a/prism/src/prism/PrismCL.java +++ b/prism/src/prism/PrismCL.java @@ -1118,6 +1118,17 @@ private void initSwitchHandlers() "

", "Set current working directory"); registry.addSwitch("settings", new StringSwitch(s -> settingsFilename = s.trim()), "", "Load settings from "); + boolean[] appendToLog = {false}; + registry.addSwitch("mainlog", new StringPlusOptionsSwitch( + new OptionParser().flag("append", "Append to the log file, rather than overwriting it", () -> appendToLog[0] = true), + (file, parse) -> { + appendToLog[0] = false; + parse.run(); + try { mainLog = new PrismFileLog(file, appendToLog[0]); prism.setMainLog(mainLog); } + + catch (PrismException e) { errorAndExit("Couldn't open log file \"" + file + "\""); } + }), + "[:]", "Write the log to instead of stdout"); registry.addBlankLine(); @@ -1535,15 +1546,6 @@ private void initSwitchHandlers() // Hidden miscellaneous switches registry.addSwitch("explicitbuild", new FlagSwitch(() -> explicitbuild = true)); registry.addSwitch("explicitbuildtest", new FlagSwitch(() -> explicitbuildtest = true)); - boolean[] appendToLog = {false}; - registry.addSwitch("mainlog", new StringPlusOptionsSwitch( - new OptionParser().flag("append", () -> appendToLog[0] = true), - (file, parse) -> { - appendToLog[0] = false; - parse.run(); - try { mainLog = new PrismFileLog(file, appendToLog[0]); prism.setMainLog(mainLog); } - catch (PrismException e) { errorAndExit("Couldn't open log file \"" + file + "\""); } - })); registry.addSwitch("exportmodeldotview", new FlagSwitch(() -> exportmodeldotview = true)); registry.addSwitch("c1", new FlagSwitch(() -> prism.setConstruction(1))); registry.addSwitch("c2", new FlagSwitch(() -> prism.setConstruction(2)));