From 09c95ecea53b92a28ed2ff82aa0da258031afdc3 Mon Sep 17 00:00:00 2001
From: dgelessus <dgelessus@users.noreply.github.com>
Date: Thu, 14 May 2020 19:30:30 +0200
Subject: [PATCH] Make parameter classes less generic and add factory methods

There are now only three specific parameter classes: RequiredSingle,
OptionalSingle, and Multiple, corresponding to the three possible value
types (String, Optional<String>, and List<String>). Parameter objects
are no longer created directly via constructors, but using factory
methods. This is shorter and decouples the parameter creation from
their implementation.

There is also a new Parameter.body factory method to create body
parameters, which previously used the same class/constructor as
required remainder parameters.

The PositionalParameter namespace class is now completely removed; all
parameter-related classes and methods are now under the Parameter
class.
---
 src/main/java/de/prob2/jupyter/Parameter.java | 56 +++++++++++++++++++
 .../java/de/prob2/jupyter/Parameters.java     | 12 ++--
 .../de/prob2/jupyter/PositionalParameter.java | 50 -----------------
 .../prob2/jupyter/commands/AssertCommand.java |  4 +-
 .../prob2/jupyter/commands/CheckCommand.java  |  4 +-
 .../jupyter/commands/ConstantsCommand.java    |  4 +-
 .../de/prob2/jupyter/commands/DotCommand.java |  6 +-
 .../prob2/jupyter/commands/EvalCommand.java   |  4 +-
 .../prob2/jupyter/commands/ExecCommand.java   |  6 +-
 .../prob2/jupyter/commands/FindCommand.java   |  4 +-
 .../prob2/jupyter/commands/GotoCommand.java   |  5 +-
 .../prob2/jupyter/commands/GroovyCommand.java |  5 +-
 .../prob2/jupyter/commands/HelpCommand.java   |  4 +-
 .../jupyter/commands/InitialiseCommand.java   |  4 +-
 .../de/prob2/jupyter/commands/LetCommand.java |  6 +-
 .../jupyter/commands/LoadCellCommand.java     |  6 +-
 .../jupyter/commands/LoadFileCommand.java     |  6 +-
 .../prob2/jupyter/commands/PrefCommand.java   |  5 +-
 .../jupyter/commands/PrettyPrintCommand.java  |  4 +-
 .../prob2/jupyter/commands/RenderCommand.java |  6 +-
 .../prob2/jupyter/commands/SolveCommand.java  |  7 +--
 .../prob2/jupyter/commands/TableCommand.java  |  4 +-
 .../prob2/jupyter/commands/TimeCommand.java   |  4 +-
 .../prob2/jupyter/commands/TypeCommand.java   |  4 +-
 .../prob2/jupyter/commands/UnletCommand.java  |  4 +-
 25 files changed, 113 insertions(+), 111 deletions(-)
 delete mode 100644 src/main/java/de/prob2/jupyter/PositionalParameter.java

diff --git a/src/main/java/de/prob2/jupyter/Parameter.java b/src/main/java/de/prob2/jupyter/Parameter.java
index ae7c214..5368a3c 100644
--- a/src/main/java/de/prob2/jupyter/Parameter.java
+++ b/src/main/java/de/prob2/jupyter/Parameter.java
@@ -67,6 +67,24 @@ public abstract class Parameter<T> {
 		public abstract T validate(final @NotNull Parameter<T> param, final @NotNull List<@NotNull String> argValues);
 	}
 	
+	public static class RequiredSingle extends Parameter<@NotNull String> {
+		public RequiredSingle(final @NotNull String identifier, final boolean repeating, final @NotNull Parameter.Splitter splitter, final @NotNull Parameter.Validator<@NotNull String> validator) {
+			super(identifier, repeating, splitter, validator);
+		}
+	}
+	
+	public static class OptionalSingle extends Parameter<@NotNull Optional<String>> {
+		public OptionalSingle(final @NotNull String identifier, final boolean repeating, final @NotNull Parameter.Splitter splitter, final @NotNull Parameter.Validator<@NotNull Optional<String>> validator) {
+			super(identifier, repeating, splitter, validator);
+		}
+	}
+	
+	public static class Multiple extends Parameter<@NotNull List<@NotNull String>> {
+		public Multiple(final @NotNull String identifier, final boolean repeating, final @NotNull Parameter.Splitter splitter, final @NotNull Parameter.Validator<@NotNull List<@NotNull String>> validator) {
+			super(identifier, repeating, splitter, validator);
+		}
+	}
+	
 	private final @NotNull String identifier;
 	private final boolean repeating;
 	private final @NotNull Parameter.Splitter splitter;
@@ -106,4 +124,42 @@ public abstract class Parameter<T> {
 			.add("validator", this.getValidator())
 			.toString();
 	}
+	
+	public static Parameter.RequiredSingle required(final String identifier) {
+		return new Parameter.RequiredSingle(identifier, false, Parameter.Splitter.REGULAR, Parameter.Validator.EXACTLY_ONE);
+	}
+	
+	public static Parameter.OptionalSingle optional(final String identifier) {
+		return new Parameter.OptionalSingle(identifier, false, Parameter.Splitter.REGULAR, Parameter.Validator.ZERO_OR_ONE);
+	}
+	
+	public static Parameter.Multiple requiredMultiple(final String identifier) {
+		return new Parameter.Multiple(identifier, true, Parameter.Splitter.REGULAR, Parameter.Validator.ONE_OR_MORE);
+	}
+	
+	public static Parameter.Multiple optionalMultiple(final String identifier) {
+		return new Parameter.Multiple(identifier, true, Parameter.Splitter.REGULAR, Parameter.Validator.ZERO_OR_MORE);
+	}
+	
+	public static Parameter.RequiredSingle requiredRemainder(final String identifier) {
+		return new Parameter.RequiredSingle(identifier, false, Parameter.Splitter.REMAINDER, Parameter.Validator.EXACTLY_ONE);
+	}
+	
+	public static Parameter.OptionalSingle optionalRemainder(final String identifier) {
+		return new Parameter.OptionalSingle(identifier, false, Parameter.Splitter.REMAINDER, Parameter.Validator.ZERO_OR_ONE);
+	}
+	
+	public static Parameter.RequiredSingle body(final String identifier) {
+		return new Parameter.RequiredSingle(identifier, false, argString -> {
+			throw new AssertionError("Splitter of a body parameter should never be used");
+		}, (param, argValues) -> {
+			if (argValues.isEmpty()) {
+				throw new UserErrorException("Missing required body " + param.getIdentifier());
+			} else if (argValues.size() > 1) {
+				throw new AssertionError("Body " + param.getIdentifier() + " appeared more than once, this should never happen!");
+			}
+			
+			return argValues.get(0);
+		});
+	}
 }
diff --git a/src/main/java/de/prob2/jupyter/Parameters.java b/src/main/java/de/prob2/jupyter/Parameters.java
index e61339b..4d08aa6 100644
--- a/src/main/java/de/prob2/jupyter/Parameters.java
+++ b/src/main/java/de/prob2/jupyter/Parameters.java
@@ -11,9 +11,9 @@ public final class Parameters {
 	public static final @NotNull Parameters NONE = new Parameters(Collections.emptyList());
 	
 	private final @NotNull List<Parameter<?>> positionalParameters;
-	private final @Nullable PositionalParameter.RequiredRemainder bodyParam;
+	private final @Nullable Parameter.RequiredSingle bodyParam;
 	
-	public Parameters(final @NotNull List<Parameter<?>> positionalParameters, final @Nullable PositionalParameter.RequiredRemainder bodyParam) {
+	public Parameters(final @NotNull List<Parameter<?>> positionalParameters, final @Nullable Parameter.RequiredSingle bodyParam) {
 		super();
 		
 		this.positionalParameters = positionalParameters;
@@ -21,10 +21,10 @@ public final class Parameters {
 		boolean seenOptional = false;
 		boolean seenOnlyLast = false;
 		for (final Parameter<?> param : positionalParameters) {
-			final boolean isOptional = param instanceof PositionalParameter.OptionalSingle || param instanceof PositionalParameter.OptionalRemainder;
-			final boolean isOnlyLast = param instanceof PositionalParameter.RequiredRemainder || param instanceof PositionalParameter.OptionalRemainder;
+			final boolean isOptional = param.getValidator() == Parameter.Validator.ZERO_OR_ONE || param.getValidator() == Parameter.Validator.ZERO_OR_MORE;
+			final boolean isOnlyLast = param.isRepeating() || param.getSplitter() == Parameter.Splitter.REMAINDER;
 			if (seenOnlyLast) {
-				throw new IllegalArgumentException("A remainder positional parameter cannot be followed by any more positional parameters");
+				throw new IllegalArgumentException("A repeating or remainder positional parameter cannot be followed by any more positional parameters");
 			}
 			if (seenOptional && isOptional) {
 				throw new IllegalArgumentException("Required positional parameter " + param + " cannot follow an optional positional parameter");
@@ -44,7 +44,7 @@ public final class Parameters {
 		return this.positionalParameters;
 	}
 	
-	public @NotNull Optional<PositionalParameter.RequiredRemainder> getBodyParam() {
+	public @NotNull Optional<Parameter.RequiredSingle> getBodyParam() {
 		return Optional.ofNullable(this.bodyParam);
 	}
 }
diff --git a/src/main/java/de/prob2/jupyter/PositionalParameter.java b/src/main/java/de/prob2/jupyter/PositionalParameter.java
deleted file mode 100644
index 51fc846..0000000
--- a/src/main/java/de/prob2/jupyter/PositionalParameter.java
+++ /dev/null
@@ -1,50 +0,0 @@
-package de.prob2.jupyter;
-
-import java.util.List;
-import java.util.Optional;
-
-import org.jetbrains.annotations.NotNull;
-
-public final class PositionalParameter {
-	public static final class RequiredSingle extends Parameter<@NotNull String> {
-		public RequiredSingle(final @NotNull String identifier) {
-			super(identifier, false, Parameter.Splitter.REGULAR, Parameter.Validator.EXACTLY_ONE);
-		}
-	}
-	
-	public static final class OptionalSingle extends Parameter<@NotNull Optional<String>> {
-		public OptionalSingle(final @NotNull String identifier) {
-			super(identifier, false, Parameter.Splitter.REGULAR, Parameter.Validator.ZERO_OR_ONE);
-		}
-	}
-	
-	public static final class RequiredMultiple extends Parameter<@NotNull List<@NotNull String>> {
-		public RequiredMultiple(final @NotNull String identifier) {
-			super(identifier, true, Parameter.Splitter.REGULAR, Parameter.Validator.ONE_OR_MORE);
-		}
-	}
-	
-	public static final class OptionalMultiple extends Parameter<@NotNull List<@NotNull String>> {
-		public OptionalMultiple(final @NotNull String identifier) {
-			super(identifier, true, Parameter.Splitter.REGULAR, Parameter.Validator.ZERO_OR_MORE);
-		}
-	}
-	
-	public static final class RequiredRemainder extends Parameter<@NotNull String> {
-		public RequiredRemainder(final @NotNull String identifier) {
-			super(identifier, false, Parameter.Splitter.REMAINDER, Parameter.Validator.EXACTLY_ONE);
-		}
-	}
-	
-	public static final class OptionalRemainder extends Parameter<@NotNull Optional<String>> {
-		public OptionalRemainder(final @NotNull String identifier) {
-			super(identifier, false, Parameter.Splitter.REMAINDER, Parameter.Validator.ZERO_OR_ONE);
-		}
-	}
-	
-	private PositionalParameter() {
-		super();
-		
-		throw new AssertionError("Utility class");
-	}
-}
diff --git a/src/main/java/de/prob2/jupyter/commands/AssertCommand.java b/src/main/java/de/prob2/jupyter/commands/AssertCommand.java
index 55574b0..b886c6f 100644
--- a/src/main/java/de/prob2/jupyter/commands/AssertCommand.java
+++ b/src/main/java/de/prob2/jupyter/commands/AssertCommand.java
@@ -11,9 +11,9 @@ import de.prob.animator.domainobjects.FormulaExpand;
 import de.prob.statespace.AnimationSelector;
 import de.prob2.jupyter.Command;
 import de.prob2.jupyter.CommandUtils;
+import de.prob2.jupyter.Parameter;
 import de.prob2.jupyter.Parameters;
 import de.prob2.jupyter.ParsedArguments;
-import de.prob2.jupyter.PositionalParameter;
 import de.prob2.jupyter.ProBKernel;
 import de.prob2.jupyter.UserErrorException;
 
@@ -25,7 +25,7 @@ import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
 public final class AssertCommand implements Command {
-	private static final @NotNull PositionalParameter.RequiredRemainder FORMULA_PARAM = new PositionalParameter.RequiredRemainder("formula");
+	private static final @NotNull Parameter.RequiredSingle FORMULA_PARAM = Parameter.requiredRemainder("formula");
 	
 	private final @NotNull Provider<@NotNull ProBKernel> kernelProvider;
 	private final @NotNull AnimationSelector animationSelector;
diff --git a/src/main/java/de/prob2/jupyter/commands/CheckCommand.java b/src/main/java/de/prob2/jupyter/commands/CheckCommand.java
index 088dcce..a20db9e 100644
--- a/src/main/java/de/prob2/jupyter/commands/CheckCommand.java
+++ b/src/main/java/de/prob2/jupyter/commands/CheckCommand.java
@@ -22,9 +22,9 @@ import de.prob.statespace.Trace;
 import de.prob.unicode.UnicodeTranslator;
 import de.prob2.jupyter.Command;
 import de.prob2.jupyter.CommandUtils;
+import de.prob2.jupyter.Parameter;
 import de.prob2.jupyter.Parameters;
 import de.prob2.jupyter.ParsedArguments;
-import de.prob2.jupyter.PositionalParameter;
 import de.prob2.jupyter.UserErrorException;
 
 import io.github.spencerpark.jupyter.kernel.ReplacementOptions;
@@ -34,7 +34,7 @@ import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
 public final class CheckCommand implements Command {
-	private static final @NotNull PositionalParameter.RequiredSingle WHAT_PARAM = new PositionalParameter.RequiredSingle("what");
+	private static final @NotNull Parameter.RequiredSingle WHAT_PARAM = Parameter.required("what");
 	
 	private static final @NotNull Map<@NotNull String, @NotNull Class<? extends AbstractTheoremElement>> CHILDREN_BASE_CLASS_MAP;
 	static {
diff --git a/src/main/java/de/prob2/jupyter/commands/ConstantsCommand.java b/src/main/java/de/prob2/jupyter/commands/ConstantsCommand.java
index 1dc90cc..0388125 100644
--- a/src/main/java/de/prob2/jupyter/commands/ConstantsCommand.java
+++ b/src/main/java/de/prob2/jupyter/commands/ConstantsCommand.java
@@ -12,9 +12,9 @@ import de.prob.statespace.Trace;
 import de.prob.statespace.Transition;
 import de.prob2.jupyter.Command;
 import de.prob2.jupyter.CommandUtils;
+import de.prob2.jupyter.Parameter;
 import de.prob2.jupyter.Parameters;
 import de.prob2.jupyter.ParsedArguments;
-import de.prob2.jupyter.PositionalParameter;
 import de.prob2.jupyter.ProBKernel;
 
 import io.github.spencerpark.jupyter.kernel.ReplacementOptions;
@@ -24,7 +24,7 @@ import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
 public final class ConstantsCommand implements Command {
-	private static final @NotNull PositionalParameter.OptionalRemainder PREDICATE_PARAM = new PositionalParameter.OptionalRemainder("predicate");
+	private static final @NotNull Parameter.OptionalSingle PREDICATE_PARAM = Parameter.optionalRemainder("predicate");
 	
 	private final @NotNull Provider<@NotNull ProBKernel> kernelProvider;
 	private final @NotNull AnimationSelector animationSelector;
diff --git a/src/main/java/de/prob2/jupyter/commands/DotCommand.java b/src/main/java/de/prob2/jupyter/commands/DotCommand.java
index 75e48d9..9148588 100644
--- a/src/main/java/de/prob2/jupyter/commands/DotCommand.java
+++ b/src/main/java/de/prob2/jupyter/commands/DotCommand.java
@@ -22,9 +22,9 @@ import de.prob.statespace.AnimationSelector;
 import de.prob.statespace.Trace;
 import de.prob2.jupyter.Command;
 import de.prob2.jupyter.CommandUtils;
+import de.prob2.jupyter.Parameter;
 import de.prob2.jupyter.Parameters;
 import de.prob2.jupyter.ParsedArguments;
-import de.prob2.jupyter.PositionalParameter;
 import de.prob2.jupyter.ProBKernel;
 import de.prob2.jupyter.UserErrorException;
 
@@ -35,8 +35,8 @@ import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
 public final class DotCommand implements Command {
-	private static final @NotNull PositionalParameter.RequiredSingle COMMAND_PARAM = new PositionalParameter.RequiredSingle("command");
-	private static final @NotNull PositionalParameter.OptionalRemainder FORMULA_PARAM = new PositionalParameter.OptionalRemainder("formula");
+	private static final @NotNull Parameter.RequiredSingle COMMAND_PARAM = Parameter.required("command");
+	private static final @NotNull Parameter.OptionalSingle FORMULA_PARAM = Parameter.optionalRemainder("formula");
 	
 	private final @NotNull Provider<@NotNull ProBKernel> kernelProvider;
 	private final @NotNull AnimationSelector animationSelector;
diff --git a/src/main/java/de/prob2/jupyter/commands/EvalCommand.java b/src/main/java/de/prob2/jupyter/commands/EvalCommand.java
index 30de5cd..34c3399 100644
--- a/src/main/java/de/prob2/jupyter/commands/EvalCommand.java
+++ b/src/main/java/de/prob2/jupyter/commands/EvalCommand.java
@@ -9,9 +9,9 @@ import de.prob.animator.domainobjects.FormulaExpand;
 import de.prob.statespace.AnimationSelector;
 import de.prob2.jupyter.Command;
 import de.prob2.jupyter.CommandUtils;
+import de.prob2.jupyter.Parameter;
 import de.prob2.jupyter.Parameters;
 import de.prob2.jupyter.ParsedArguments;
-import de.prob2.jupyter.PositionalParameter;
 import de.prob2.jupyter.ProBKernel;
 
 import io.github.spencerpark.jupyter.kernel.ReplacementOptions;
@@ -21,7 +21,7 @@ import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
 public final class EvalCommand implements Command {
-	private static final @NotNull PositionalParameter.RequiredRemainder FORMULA_PARAM = new PositionalParameter.RequiredRemainder("formula");
+	private static final @NotNull Parameter.RequiredSingle FORMULA_PARAM = Parameter.requiredRemainder("formula");
 	
 	private final @NotNull Injector injector;
 	private final @NotNull AnimationSelector animationSelector;
diff --git a/src/main/java/de/prob2/jupyter/commands/ExecCommand.java b/src/main/java/de/prob2/jupyter/commands/ExecCommand.java
index 931a5ca..5515f0f 100644
--- a/src/main/java/de/prob2/jupyter/commands/ExecCommand.java
+++ b/src/main/java/de/prob2/jupyter/commands/ExecCommand.java
@@ -14,9 +14,9 @@ import de.prob.statespace.Trace;
 import de.prob.statespace.Transition;
 import de.prob2.jupyter.Command;
 import de.prob2.jupyter.CommandUtils;
+import de.prob2.jupyter.Parameter;
 import de.prob2.jupyter.Parameters;
 import de.prob2.jupyter.ParsedArguments;
-import de.prob2.jupyter.PositionalParameter;
 import de.prob2.jupyter.ProBKernel;
 
 import io.github.spencerpark.jupyter.kernel.ReplacementOptions;
@@ -26,8 +26,8 @@ import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
 public final class ExecCommand implements Command {
-	private static final @NotNull PositionalParameter.RequiredSingle OPERATION_PARAM = new PositionalParameter.RequiredSingle("operation");
-	private static final @NotNull PositionalParameter.OptionalRemainder PREDICATE_PARAM = new PositionalParameter.OptionalRemainder("predicate");
+	private static final @NotNull Parameter.RequiredSingle OPERATION_PARAM = Parameter.required("operation");
+	private static final @NotNull Parameter.OptionalSingle PREDICATE_PARAM = Parameter.optionalRemainder("predicate");
 	
 	private final @NotNull Provider<@NotNull ProBKernel> kernelProvider;
 	private final @NotNull AnimationSelector animationSelector;
diff --git a/src/main/java/de/prob2/jupyter/commands/FindCommand.java b/src/main/java/de/prob2/jupyter/commands/FindCommand.java
index e1aae32..88aa529 100644
--- a/src/main/java/de/prob2/jupyter/commands/FindCommand.java
+++ b/src/main/java/de/prob2/jupyter/commands/FindCommand.java
@@ -11,9 +11,9 @@ import de.prob.statespace.AnimationSelector;
 import de.prob.statespace.Trace;
 import de.prob2.jupyter.Command;
 import de.prob2.jupyter.CommandUtils;
+import de.prob2.jupyter.Parameter;
 import de.prob2.jupyter.Parameters;
 import de.prob2.jupyter.ParsedArguments;
-import de.prob2.jupyter.PositionalParameter;
 import de.prob2.jupyter.ProBKernel;
 
 import io.github.spencerpark.jupyter.kernel.ReplacementOptions;
@@ -23,7 +23,7 @@ import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
 public final class FindCommand implements Command {
-	private static final @NotNull PositionalParameter.RequiredRemainder PREDICATE_PARAM = new PositionalParameter.RequiredRemainder("predicate");
+	private static final @NotNull Parameter.RequiredSingle PREDICATE_PARAM = Parameter.requiredRemainder("predicate");
 	
 	private final @NotNull Provider<@NotNull ProBKernel> kernelProvider;
 	private final @NotNull AnimationSelector animationSelector;
diff --git a/src/main/java/de/prob2/jupyter/commands/GotoCommand.java b/src/main/java/de/prob2/jupyter/commands/GotoCommand.java
index 78770f2..798d0e3 100644
--- a/src/main/java/de/prob2/jupyter/commands/GotoCommand.java
+++ b/src/main/java/de/prob2/jupyter/commands/GotoCommand.java
@@ -6,11 +6,10 @@ import com.google.inject.Inject;
 
 import de.prob.statespace.AnimationSelector;
 import de.prob.statespace.Trace;
-
 import de.prob2.jupyter.Command;
+import de.prob2.jupyter.Parameter;
 import de.prob2.jupyter.Parameters;
 import de.prob2.jupyter.ParsedArguments;
-import de.prob2.jupyter.PositionalParameter;
 import de.prob2.jupyter.UserErrorException;
 
 import io.github.spencerpark.jupyter.kernel.ReplacementOptions;
@@ -20,7 +19,7 @@ import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
 public final class GotoCommand implements Command {
-	private static final @NotNull PositionalParameter.RequiredSingle INDEX_PARAM = new PositionalParameter.RequiredSingle("index");
+	private static final @NotNull Parameter.RequiredSingle INDEX_PARAM = Parameter.required("index");
 	
 	private final @NotNull AnimationSelector animationSelector;
 	
diff --git a/src/main/java/de/prob2/jupyter/commands/GroovyCommand.java b/src/main/java/de/prob2/jupyter/commands/GroovyCommand.java
index 43bfe4f..083cef6 100644
--- a/src/main/java/de/prob2/jupyter/commands/GroovyCommand.java
+++ b/src/main/java/de/prob2/jupyter/commands/GroovyCommand.java
@@ -10,11 +10,10 @@ import com.google.inject.Inject;
 import com.google.inject.Injector;
 
 import de.prob.scripting.ScriptEngineProvider;
-
 import de.prob2.jupyter.Command;
+import de.prob2.jupyter.Parameter;
 import de.prob2.jupyter.Parameters;
 import de.prob2.jupyter.ParsedArguments;
-import de.prob2.jupyter.PositionalParameter;
 import de.prob2.jupyter.ProBKernel;
 
 import io.github.spencerpark.jupyter.kernel.ReplacementOptions;
@@ -24,7 +23,7 @@ import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
 public final class GroovyCommand implements Command {
-	private static final @NotNull PositionalParameter.RequiredRemainder EXPRESSION_PARAM = new PositionalParameter.RequiredRemainder("expression");
+	private static final @NotNull Parameter.RequiredSingle EXPRESSION_PARAM = Parameter.requiredRemainder("expression");
 	
 	private final @NotNull Injector injector;
 	private final @NotNull ScriptEngine groovyScriptEngine;
diff --git a/src/main/java/de/prob2/jupyter/commands/HelpCommand.java b/src/main/java/de/prob2/jupyter/commands/HelpCommand.java
index 487e63a..57d4418 100644
--- a/src/main/java/de/prob2/jupyter/commands/HelpCommand.java
+++ b/src/main/java/de/prob2/jupyter/commands/HelpCommand.java
@@ -13,9 +13,9 @@ import com.google.inject.Inject;
 import com.google.inject.Injector;
 
 import de.prob2.jupyter.Command;
+import de.prob2.jupyter.Parameter;
 import de.prob2.jupyter.Parameters;
 import de.prob2.jupyter.ParsedArguments;
-import de.prob2.jupyter.PositionalParameter;
 import de.prob2.jupyter.ProBKernel;
 import de.prob2.jupyter.UserErrorException;
 
@@ -26,7 +26,7 @@ import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
 public final class HelpCommand implements Command {
-	private static final @NotNull PositionalParameter.OptionalSingle COMMAND_NAME_PARAM = new PositionalParameter.OptionalSingle("commandName");
+	private static final @NotNull Parameter.OptionalSingle COMMAND_NAME_PARAM = Parameter.optional("commandName");
 	
 	private static final @NotNull Map<@NotNull String, @NotNull List<@NotNull Class<? extends Command>>> COMMAND_CLASS_CATEGORIES;
 	static {
diff --git a/src/main/java/de/prob2/jupyter/commands/InitialiseCommand.java b/src/main/java/de/prob2/jupyter/commands/InitialiseCommand.java
index b7bf155..b93fc03 100644
--- a/src/main/java/de/prob2/jupyter/commands/InitialiseCommand.java
+++ b/src/main/java/de/prob2/jupyter/commands/InitialiseCommand.java
@@ -12,9 +12,9 @@ import de.prob.statespace.Trace;
 import de.prob.statespace.Transition;
 import de.prob2.jupyter.Command;
 import de.prob2.jupyter.CommandUtils;
+import de.prob2.jupyter.Parameter;
 import de.prob2.jupyter.Parameters;
 import de.prob2.jupyter.ParsedArguments;
-import de.prob2.jupyter.PositionalParameter;
 import de.prob2.jupyter.ProBKernel;
 
 import io.github.spencerpark.jupyter.kernel.ReplacementOptions;
@@ -24,7 +24,7 @@ import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
 public final class InitialiseCommand implements Command {
-	private static final @NotNull PositionalParameter.OptionalRemainder PREDICATE_PARAM = new PositionalParameter.OptionalRemainder("predicate");
+	private static final @NotNull Parameter.OptionalSingle PREDICATE_PARAM = Parameter.optionalRemainder("predicate");
 	
 	private final @NotNull Provider<@NotNull ProBKernel> kernelProvider;
 	private final @NotNull AnimationSelector animationSelector;
diff --git a/src/main/java/de/prob2/jupyter/commands/LetCommand.java b/src/main/java/de/prob2/jupyter/commands/LetCommand.java
index 4d7d682..24868de 100644
--- a/src/main/java/de/prob2/jupyter/commands/LetCommand.java
+++ b/src/main/java/de/prob2/jupyter/commands/LetCommand.java
@@ -11,9 +11,9 @@ import de.prob.animator.domainobjects.FormulaExpand;
 import de.prob.statespace.AnimationSelector;
 import de.prob2.jupyter.Command;
 import de.prob2.jupyter.CommandUtils;
+import de.prob2.jupyter.Parameter;
 import de.prob2.jupyter.Parameters;
 import de.prob2.jupyter.ParsedArguments;
-import de.prob2.jupyter.PositionalParameter;
 import de.prob2.jupyter.ProBKernel;
 
 import io.github.spencerpark.jupyter.kernel.ReplacementOptions;
@@ -23,8 +23,8 @@ import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
 public final class LetCommand implements Command {
-	private static final @NotNull PositionalParameter.RequiredSingle NAME_PARAM = new PositionalParameter.RequiredSingle("name");
-	private static final @NotNull PositionalParameter.RequiredRemainder EXPRESSION_PARAM = new PositionalParameter.RequiredRemainder("expression");
+	private static final @NotNull Parameter.RequiredSingle NAME_PARAM = Parameter.required("name");
+	private static final @NotNull Parameter.RequiredSingle EXPRESSION_PARAM = Parameter.requiredRemainder("expression");
 	
 	private final @NotNull Provider<@NotNull ProBKernel> kernelProvider;
 	private final @NotNull AnimationSelector animationSelector;
diff --git a/src/main/java/de/prob2/jupyter/commands/LoadCellCommand.java b/src/main/java/de/prob2/jupyter/commands/LoadCellCommand.java
index cdb7b21..1a39922 100644
--- a/src/main/java/de/prob2/jupyter/commands/LoadCellCommand.java
+++ b/src/main/java/de/prob2/jupyter/commands/LoadCellCommand.java
@@ -12,9 +12,9 @@ import de.prob.statespace.AnimationSelector;
 import de.prob.statespace.Trace;
 import de.prob2.jupyter.Command;
 import de.prob2.jupyter.CommandUtils;
+import de.prob2.jupyter.Parameter;
 import de.prob2.jupyter.Parameters;
 import de.prob2.jupyter.ParsedArguments;
-import de.prob2.jupyter.PositionalParameter;
 import de.prob2.jupyter.ProBKernel;
 
 import io.github.spencerpark.jupyter.kernel.ReplacementOptions;
@@ -24,8 +24,8 @@ import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
 public final class LoadCellCommand implements Command {
-	private static final @NotNull PositionalParameter.OptionalMultiple PREFS_PARAM = new PositionalParameter.OptionalMultiple("prefs");
-	private static final @NotNull PositionalParameter.RequiredRemainder CODE_PARAM = new PositionalParameter.RequiredRemainder("code");
+	private static final @NotNull Parameter.Multiple PREFS_PARAM = Parameter.optionalMultiple("prefs");
+	private static final @NotNull Parameter.RequiredSingle CODE_PARAM = Parameter.body("code");
 	
 	private final @NotNull ClassicalBFactory classicalBFactory;
 	private final @NotNull AnimationSelector animationSelector;
diff --git a/src/main/java/de/prob2/jupyter/commands/LoadFileCommand.java b/src/main/java/de/prob2/jupyter/commands/LoadFileCommand.java
index f3e5dd3..d8980f5 100644
--- a/src/main/java/de/prob2/jupyter/commands/LoadFileCommand.java
+++ b/src/main/java/de/prob2/jupyter/commands/LoadFileCommand.java
@@ -21,9 +21,9 @@ import de.prob.statespace.AnimationSelector;
 import de.prob.statespace.Trace;
 import de.prob2.jupyter.Command;
 import de.prob2.jupyter.CommandUtils;
+import de.prob2.jupyter.Parameter;
 import de.prob2.jupyter.Parameters;
 import de.prob2.jupyter.ParsedArguments;
-import de.prob2.jupyter.PositionalParameter;
 import de.prob2.jupyter.ProBKernel;
 import de.prob2.jupyter.UserErrorException;
 
@@ -38,8 +38,8 @@ import org.slf4j.LoggerFactory;
 public final class LoadFileCommand implements Command {
 	private static final @NotNull Logger LOGGER = LoggerFactory.getLogger(LoadFileCommand.class);
 	
-	private static final @NotNull PositionalParameter.RequiredSingle FILE_NAME_PARAM = new PositionalParameter.RequiredSingle("fileName");
-	private static final @NotNull PositionalParameter.OptionalMultiple PREFS_PARAM = new PositionalParameter.OptionalMultiple("prefs");
+	private static final @NotNull Parameter.RequiredSingle FILE_NAME_PARAM = Parameter.required("fileName");
+	private static final @NotNull Parameter.Multiple PREFS_PARAM = Parameter.optionalMultiple("prefs");
 	
 	private final @NotNull Injector injector;
 	private final @NotNull AnimationSelector animationSelector;
diff --git a/src/main/java/de/prob2/jupyter/commands/PrefCommand.java b/src/main/java/de/prob2/jupyter/commands/PrefCommand.java
index c416a83..2baac28 100644
--- a/src/main/java/de/prob2/jupyter/commands/PrefCommand.java
+++ b/src/main/java/de/prob2/jupyter/commands/PrefCommand.java
@@ -12,12 +12,11 @@ import de.prob.animator.command.GetCurrentPreferencesCommand;
 import de.prob.animator.command.GetPreferenceCommand;
 import de.prob.animator.command.SetPreferenceCommand;
 import de.prob.statespace.AnimationSelector;
-
 import de.prob2.jupyter.Command;
 import de.prob2.jupyter.CommandUtils;
+import de.prob2.jupyter.Parameter;
 import de.prob2.jupyter.Parameters;
 import de.prob2.jupyter.ParsedArguments;
-import de.prob2.jupyter.PositionalParameter;
 import de.prob2.jupyter.UserErrorException;
 
 import io.github.spencerpark.jupyter.kernel.ReplacementOptions;
@@ -27,7 +26,7 @@ import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
 public final class PrefCommand implements Command {
-	private static final @NotNull PositionalParameter.OptionalMultiple PREFS_PARAM = new PositionalParameter.OptionalMultiple("prefs");
+	private static final @NotNull Parameter.Multiple PREFS_PARAM = Parameter.optionalMultiple("prefs");
 	
 	private final @NotNull AnimationSelector animationSelector;
 	
diff --git a/src/main/java/de/prob2/jupyter/commands/PrettyPrintCommand.java b/src/main/java/de/prob2/jupyter/commands/PrettyPrintCommand.java
index a6fe79a..752d005 100644
--- a/src/main/java/de/prob2/jupyter/commands/PrettyPrintCommand.java
+++ b/src/main/java/de/prob2/jupyter/commands/PrettyPrintCommand.java
@@ -10,9 +10,9 @@ import de.prob.animator.domainobjects.IEvalElement;
 import de.prob.statespace.AnimationSelector;
 import de.prob2.jupyter.Command;
 import de.prob2.jupyter.CommandUtils;
+import de.prob2.jupyter.Parameter;
 import de.prob2.jupyter.Parameters;
 import de.prob2.jupyter.ParsedArguments;
-import de.prob2.jupyter.PositionalParameter;
 
 import io.github.spencerpark.jupyter.kernel.ReplacementOptions;
 import io.github.spencerpark.jupyter.kernel.display.DisplayData;
@@ -21,7 +21,7 @@ import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
 public final class PrettyPrintCommand implements Command {
-	private static final @NotNull PositionalParameter.RequiredRemainder PREDICATE_PARAM = new PositionalParameter.RequiredRemainder("predicate");
+	private static final @NotNull Parameter.RequiredSingle PREDICATE_PARAM = Parameter.requiredRemainder("predicate");
 	
 	private final AnimationSelector animationSelector;
 	
diff --git a/src/main/java/de/prob2/jupyter/commands/RenderCommand.java b/src/main/java/de/prob2/jupyter/commands/RenderCommand.java
index 22e4d2b..cafd252 100644
--- a/src/main/java/de/prob2/jupyter/commands/RenderCommand.java
+++ b/src/main/java/de/prob2/jupyter/commands/RenderCommand.java
@@ -5,9 +5,9 @@ import java.util.Collections;
 import com.google.inject.Inject;
 
 import de.prob2.jupyter.Command;
+import de.prob2.jupyter.Parameter;
 import de.prob2.jupyter.Parameters;
 import de.prob2.jupyter.ParsedArguments;
-import de.prob2.jupyter.PositionalParameter;
 
 import io.github.spencerpark.jupyter.kernel.ReplacementOptions;
 import io.github.spencerpark.jupyter.kernel.display.DisplayData;
@@ -16,8 +16,8 @@ import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
 public final class RenderCommand implements Command {
-	private static final @NotNull PositionalParameter.RequiredSingle MIME_TYPE_PARAM = new PositionalParameter.RequiredSingle("mimeType");
-	private static final @NotNull PositionalParameter.RequiredRemainder CONTENT_PARAM = new PositionalParameter.RequiredRemainder("content");
+	private static final @NotNull Parameter.RequiredSingle MIME_TYPE_PARAM = Parameter.required("mimeType");
+	private static final @NotNull Parameter.RequiredSingle CONTENT_PARAM = Parameter.body("content");
 	
 	@Inject
 	private RenderCommand() {
diff --git a/src/main/java/de/prob2/jupyter/commands/SolveCommand.java b/src/main/java/de/prob2/jupyter/commands/SolveCommand.java
index d172344..d8d3973 100644
--- a/src/main/java/de/prob2/jupyter/commands/SolveCommand.java
+++ b/src/main/java/de/prob2/jupyter/commands/SolveCommand.java
@@ -13,12 +13,11 @@ import de.prob.animator.domainobjects.FormulaExpand;
 import de.prob.animator.domainobjects.IEvalElement;
 import de.prob.statespace.AnimationSelector;
 import de.prob.statespace.Trace;
-
 import de.prob2.jupyter.Command;
 import de.prob2.jupyter.CommandUtils;
+import de.prob2.jupyter.Parameter;
 import de.prob2.jupyter.Parameters;
 import de.prob2.jupyter.ParsedArguments;
-import de.prob2.jupyter.PositionalParameter;
 import de.prob2.jupyter.ProBKernel;
 import de.prob2.jupyter.UserErrorException;
 
@@ -29,8 +28,8 @@ import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
 public final class SolveCommand implements Command {
-	private static final @NotNull PositionalParameter.RequiredSingle SOLVER_PARAM = new PositionalParameter.RequiredSingle("solver");
-	private static final @NotNull PositionalParameter.RequiredRemainder PREDICATE_PARAM = new PositionalParameter.RequiredRemainder("predicate");
+	private static final @NotNull Parameter.RequiredSingle SOLVER_PARAM = Parameter.required("solver");
+	private static final @NotNull Parameter.RequiredSingle PREDICATE_PARAM = Parameter.requiredRemainder("predicate");
 	
 	private static final @NotNull Map<@NotNull String, CbcSolveCommand.@NotNull Solvers> SOLVERS = Arrays.stream(CbcSolveCommand.Solvers.values())
 		.collect(Collectors.toMap(s -> s.name().toLowerCase(), s -> s));
diff --git a/src/main/java/de/prob2/jupyter/commands/TableCommand.java b/src/main/java/de/prob2/jupyter/commands/TableCommand.java
index c43bb8c..74553f1 100644
--- a/src/main/java/de/prob2/jupyter/commands/TableCommand.java
+++ b/src/main/java/de/prob2/jupyter/commands/TableCommand.java
@@ -18,9 +18,9 @@ import de.prob.statespace.Trace;
 import de.prob.unicode.UnicodeTranslator;
 import de.prob2.jupyter.Command;
 import de.prob2.jupyter.CommandUtils;
+import de.prob2.jupyter.Parameter;
 import de.prob2.jupyter.Parameters;
 import de.prob2.jupyter.ParsedArguments;
-import de.prob2.jupyter.PositionalParameter;
 import de.prob2.jupyter.ProBKernel;
 
 import io.github.spencerpark.jupyter.kernel.ReplacementOptions;
@@ -30,7 +30,7 @@ import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
 public final class TableCommand implements Command {
-	private static final @NotNull PositionalParameter.RequiredRemainder EXPRESSION_PARAM = new PositionalParameter.RequiredRemainder("expression");
+	private static final @NotNull Parameter.RequiredSingle EXPRESSION_PARAM = Parameter.requiredRemainder("expression");
 	
 	private final @NotNull Provider<@NotNull ProBKernel> kernelProvider;
 	private final @NotNull AnimationSelector animationSelector;
diff --git a/src/main/java/de/prob2/jupyter/commands/TimeCommand.java b/src/main/java/de/prob2/jupyter/commands/TimeCommand.java
index aacd756..844936b 100644
--- a/src/main/java/de/prob2/jupyter/commands/TimeCommand.java
+++ b/src/main/java/de/prob2/jupyter/commands/TimeCommand.java
@@ -9,9 +9,9 @@ import com.google.inject.Inject;
 import com.google.inject.Injector;
 
 import de.prob2.jupyter.Command;
+import de.prob2.jupyter.Parameter;
 import de.prob2.jupyter.Parameters;
 import de.prob2.jupyter.ParsedArguments;
-import de.prob2.jupyter.PositionalParameter;
 import de.prob2.jupyter.ProBKernel;
 
 import io.github.spencerpark.jupyter.kernel.ReplacementOptions;
@@ -21,7 +21,7 @@ import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
 public final class TimeCommand implements Command {
-	private static final @NotNull PositionalParameter.RequiredRemainder COMMAND_AND_ARGS_PARAM = new PositionalParameter.RequiredRemainder("commandAndArgs");
+	private static final @NotNull Parameter.RequiredSingle COMMAND_AND_ARGS_PARAM = Parameter.requiredRemainder("commandAndArgs");
 	
 	private final @NotNull Injector injector;
 	
diff --git a/src/main/java/de/prob2/jupyter/commands/TypeCommand.java b/src/main/java/de/prob2/jupyter/commands/TypeCommand.java
index d8a9236..67ed0c3 100644
--- a/src/main/java/de/prob2/jupyter/commands/TypeCommand.java
+++ b/src/main/java/de/prob2/jupyter/commands/TypeCommand.java
@@ -13,9 +13,9 @@ import de.prob.statespace.AnimationSelector;
 import de.prob.statespace.Trace;
 import de.prob2.jupyter.Command;
 import de.prob2.jupyter.CommandUtils;
+import de.prob2.jupyter.Parameter;
 import de.prob2.jupyter.Parameters;
 import de.prob2.jupyter.ParsedArguments;
-import de.prob2.jupyter.PositionalParameter;
 import de.prob2.jupyter.ProBKernel;
 
 import io.github.spencerpark.jupyter.kernel.ReplacementOptions;
@@ -25,7 +25,7 @@ import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
 public final class TypeCommand implements Command {
-	private static final @NotNull PositionalParameter.RequiredRemainder FORMULA_PARAM = new PositionalParameter.RequiredRemainder("formula");
+	private static final @NotNull Parameter.RequiredSingle FORMULA_PARAM = Parameter.requiredRemainder("formula");
 	
 	private final @NotNull Provider<@NotNull ProBKernel> kernelProvider;
 	private final @NotNull AnimationSelector animationSelector;
diff --git a/src/main/java/de/prob2/jupyter/commands/UnletCommand.java b/src/main/java/de/prob2/jupyter/commands/UnletCommand.java
index f3da397..c2bb02b 100644
--- a/src/main/java/de/prob2/jupyter/commands/UnletCommand.java
+++ b/src/main/java/de/prob2/jupyter/commands/UnletCommand.java
@@ -7,9 +7,9 @@ import com.google.inject.Inject;
 import com.google.inject.Injector;
 
 import de.prob2.jupyter.Command;
+import de.prob2.jupyter.Parameter;
 import de.prob2.jupyter.Parameters;
 import de.prob2.jupyter.ParsedArguments;
-import de.prob2.jupyter.PositionalParameter;
 import de.prob2.jupyter.ProBKernel;
 import de.prob2.jupyter.UserErrorException;
 
@@ -20,7 +20,7 @@ import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
 public final class UnletCommand implements Command {
-	private static final @NotNull PositionalParameter.RequiredSingle NAME_PARAM = new PositionalParameter.RequiredSingle("name");
+	private static final @NotNull Parameter.RequiredSingle NAME_PARAM = Parameter.required("name");
 	
 	private final @NotNull Injector injector;
 	
-- 
GitLab