Commit 6b8d6bb6 authored by dgelessus's avatar dgelessus
Browse files

Allow switching the language used to parse formulas input by the user

This doesn't affect predicates passed to :exec/:constants/:init yet,
because ProB 2's StateSpace.transitionFromPredicate method currently
only accepts a string predicate (which is parsed using the model's
languate) and not an arbitrary IEvalElement.
parent 6307c0bd
Pipeline #49379 passed with stage
in 3 minutes and 37 seconds
......@@ -3,6 +3,7 @@
## [(next version)](./README.md#for-developers)
* Updated ProB 2 to version 4.0.0-SNAPSHOT.
* Added a `:language` command to allow changing the language used to parse user input. For example `:language event_b` can be used to switch to Event-B syntax when a non-Event-B machine is loaded (or no machine at all).
* Improved the performance of loading machines by reusing the existing instance of ProB instead of starting a new one for each machine.
* Improved error highlighting for machines loaded from files and not from the notebook.
* Significantly refactored the logic for parsing commands and their arguments.
......
{
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [
{
"data": {
"text/markdown": [
"```\n",
":language [LANGUAGE]\n",
"```\n",
"\n",
"Change the language used to parse formulas entered by the user.\n",
"\n",
"By default, formulas entered by the user are parsed using the language of the currently loaded model. Using this command, the language can be changed, so that for example formulas in Event-B syntax can be evaluated in the context of a classical B machine.\n",
"\n",
"Some features will not work correctly when a non-default language is set, such as `DEFINITIONS` from classical B machines."
],
"text/plain": [
":language [LANGUAGE]\n",
"Change the language used to parse formulas entered by the user.\n",
"\n",
"By default, formulas entered by the user are parsed using the language of the currently loaded model. Using this command, the language can be changed, so that for example formulas in Event-B syntax can be evaluated in the context of a classical B machine.\n",
"\n",
"Some features will not work correctly when a non-default language is set, such as `DEFINITIONS` from classical B machines."
]
},
"execution_count": 1,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
":help :language"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The language can be switched when no machine is loaded."
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"Current language for user input is classical B (default for model)"
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
":language"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"data": {
"text/markdown": [
"$8$"
],
"text/plain": [
"8"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"2**3"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"Changed language for user input to Event-B (forced)"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
":language event_b"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"ename": "CommandExecutionException",
"evalue": ":eval: Computation not completed: Type mismatch: Expected POW(_A), but was INTEGER in '2'",
"output_type": "error",
"traceback": [
"\u001b[1m\u001b[31m:eval: Computation not completed: Type mismatch: Expected POW(_A), but was INTEGER in '2'\u001b[0m"
]
}
],
"source": [
"2**3"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"data": {
"text/markdown": [
"$\\{(2\\mapsto 3)\\}$"
],
"text/plain": [
"{(2↦3)}"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"{2}**{3}"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"Changed language for user input to classical B (forced)"
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
":language classical_b"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"data": {
"text/markdown": [
"$8$"
],
"text/plain": [
"8"
]
},
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"2**3"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"Changed language for user input to classical B (default for model)"
]
},
"execution_count": 9,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
":language default"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [
{
"data": {
"text/markdown": [
"$8$"
],
"text/plain": [
"8"
]
},
"execution_count": 10,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"2**3"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The language can be switched when a classical B machine is loaded."
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"Loaded machine: things"
]
},
"execution_count": 11,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
":load things.mch"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {},
"outputs": [
{
"data": {
"text/markdown": [
"$\\{\\mathit{ONE},\\mathit{TWO}\\}$"
],
"text/plain": [
"{ONE,TWO}"
]
},
"execution_count": 12,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"{ONE, TWO}"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"Changed language for user input to Event-B (forced)"
]
},
"execution_count": 13,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
":language event_b"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Identifiers from the machine can still be accessed when using a non-default language."
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [
{
"data": {
"text/markdown": [
"$\\{(\\mathit{ONE}\\mapsto \\mathit{TWO})\\}$"
],
"text/plain": [
"{(ONE↦TWO)}"
]
},
"execution_count": 14,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"{ONE}**{TWO}"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"TODO: Test switching language when a non-classical-B file is loaded (Event-B, TLA, CSP, Z, etc.)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "ProB 2",
"language": "prob",
"name": "prob2"
},
"language_info": {
"codemirror_mode": "prob2_jupyter_repl",
"file_extension": ".prob",
"mimetype": "text/x-prob2-jupyter-repl",
"name": "prob"
}
},
"nbformat": 4,
"nbformat_minor": 4
}
%% Cell type:code id: tags:
``` prob
:help :language
```
%%%% Output: execute_result
```
:language [LANGUAGE]
```
Change the language used to parse formulas entered by the user.
By default, formulas entered by the user are parsed using the language of the currently loaded model. Using this command, the language can be changed, so that for example formulas in Event-B syntax can be evaluated in the context of a classical B machine.
Some features will not work correctly when a non-default language is set, such as `DEFINITIONS` from classical B machines.
:language [LANGUAGE]
Change the language used to parse formulas entered by the user.
By default, formulas entered by the user are parsed using the language of the currently loaded model. Using this command, the language can be changed, so that for example formulas in Event-B syntax can be evaluated in the context of a classical B machine.
Some features will not work correctly when a non-default language is set, such as `DEFINITIONS` from classical B machines.
%% Cell type:markdown id: tags:
The language can be switched when no machine is loaded.
%% Cell type:code id: tags:
``` prob
:language
```
%%%% Output: execute_result
Current language for user input is classical B (default for model)
%% Cell type:code id: tags:
``` prob
2**3
```
%%%% Output: execute_result
$8$
8
%% Cell type:code id: tags:
``` prob
:language event_b
```
%%%% Output: execute_result
Changed language for user input to Event-B (forced)
%% Cell type:code id: tags:
``` prob
2**3
```
%%%% Output: error
:eval: Computation not completed: Type mismatch: Expected POW(_A), but was INTEGER in '2'
%% Cell type:code id: tags:
``` prob
{2}**{3}
```
%%%% Output: execute_result
$\{(2\mapsto 3)\}$
{(2↦3)}
%% Cell type:code id: tags:
``` prob
:language classical_b
```
%%%% Output: execute_result
Changed language for user input to classical B (forced)
%% Cell type:code id: tags:
``` prob
2**3
```
%%%% Output: execute_result
$8$
8
%% Cell type:code id: tags:
``` prob
:language default
```
%%%% Output: execute_result
Changed language for user input to classical B (default for model)
%% Cell type:code id: tags:
``` prob
2**3
```
%%%% Output: execute_result
$8$
8
%% Cell type:markdown id: tags:
The language can be switched when a classical B machine is loaded.
%% Cell type:code id: tags:
``` prob
:load things.mch
```
%%%% Output: execute_result
Loaded machine: things
%% Cell type:code id: tags:
``` prob
{ONE, TWO}
```
%%%% Output: execute_result
$\{\mathit{ONE},\mathit{TWO}\}$
{ONE,TWO}
%% Cell type:code id: tags:
``` prob
:language event_b
```
%%%% Output: execute_result
Changed language for user input to Event-B (forced)
%% Cell type:markdown id: tags:
Identifiers from the machine can still be accessed when using a non-default language.
%% Cell type:code id: tags:
``` prob
{ONE}**{TWO}
```
%%%% Output: execute_result
$\{(\mathit{ONE}\mapsto \mathit{TWO})\}$
{(ONE↦TWO)}
%% Cell type:markdown id: tags:
TODO: Test switching language when a non-classical-B file is loaded (Event-B, TLA, CSP, Z, etc.)
......@@ -374,6 +374,7 @@ public final class CommandUtils {
return null;
}
final String identifier = identifierMatcher.group();
// TODO Handle non-default languages (i. e. forced classical B or Event-B parsing) - important for the different meaning of INT in classical B vs. Event-B
final IEvalElement formula = trace.getModel().parseFormula(identifier, FormulaExpand.TRUNCATE);
final TypeCheckResult type = trace.getStateSpace().typeCheck(formula);
final AbstractEvalResult currentValue = trace.evalCurrent(formula);
......
package de.prob2.jupyter;
public enum FormulaLanguage {
DEFAULT,
CLASSICAL_B,
EVENT_B,
;
}
......@@ -34,8 +34,11 @@ import com.google.inject.Injector;
import com.google.inject.Singleton;
import de.prob.animator.ReusableAnimator;
import de.prob.animator.domainobjects.ClassicalB;
import de.prob.animator.domainobjects.ErrorItem;
import de.prob.animator.domainobjects.EventB;
import de.prob.animator.domainobjects.FormulaExpand;
import de.prob.animator.domainobjects.IEvalElement;
import de.prob.exception.ProBError;
import de.prob.scripting.ClassicalBFactory;
import de.prob.statespace.AnimationSelector;
......@@ -55,6 +58,7 @@ import de.prob2.jupyter.commands.GotoCommand;
import de.prob2.jupyter.commands.GroovyCommand;
import de.prob2.jupyter.commands.HelpCommand;
import de.prob2.jupyter.commands.InitialiseCommand;
import de.prob2.jupyter.commands.LanguageCommand;
import de.prob2.jupyter.commands.LetCommand;
import de.prob2.jupyter.commands.LoadCellCommand;
import de.prob2.jupyter.commands.LoadFileCommand;
......@@ -153,6 +157,7 @@ public final class ProBKernel extends BaseKernel {
GroovyCommand.class,
HelpCommand.class,
InitialiseCommand.class,
LanguageCommand.class,
LetCommand.class,
LoadCellCommand.class,
LoadFileCommand.class,
......@@ -242,6 +247,7 @@ public final class ProBKernel extends BaseKernel {
private final @NotNull Map<@NotNull String, @NotNull String> variables;
private @NotNull Path currentMachineDirectory;
private @NotNull FormulaLanguage currentFormulaLanguage;
private @Nullable String currentCellSourceCode;
@Inject
......@@ -262,7 +268,10 @@ public final class ProBKernel extends BaseKernel {
this.currentCommandFuture = new AtomicReference<>(Futures.immediateCancelledFuture());
this.variables = new HashMap<>();
// These variables need to be initialized here,
// but they will be overwritten immediately by the switchMachine call.
this.currentMachineDirectory = Paths.get("");
this.currentFormulaLanguage = FormulaLanguage.DEFAULT;
this.currentCellSourceCode = null;
this.switchMachine(Paths.get(""), null, this::loadDefaultMachine);
}
......@@ -295,6 +304,14 @@ public final class ProBKernel extends BaseKernel {
this.currentMachineDirectory = currentMachineDirectory;
}
public @NotNull FormulaLanguage getCurrentFormulaLanguage() {
return this.currentFormulaLanguage;
}
public void setCurrentFormulaLanguage(final @NotNull FormulaLanguage currentFormulaLanguage) {
this.currentFormulaLanguage = currentFormulaLanguage;
}
public void unloadMachine() {
final Trace oldTrace = this.animationSelector.getCurrentTrace();
if (oldTrace != null) {
......@@ -312,6 +329,7 @@ public final class ProBKernel extends BaseKernel {
public void switchMachine(final @NotNull Path machineDirectory, final @Nullable String cellSourceCode, final @NotNull Function<@NotNull StateSpace, @NotNull Trace> newTraceCreator) {
this.unloadMachine();
this.setCurrentFormulaLanguage(FormulaLanguage.DEFAULT);
this.currentCellSourceCode = cellSourceCode;
final StateSpace newStateSpace = this.animator.createStateSpace();
try {
......@@ -325,6 +343,30 @@ public final class ProBKernel extends BaseKernel {
this.setCurrentMachineDirectory(machineDirectory);
}
/**
* Parse the given formula code into an {@link IEvalElement}.
* The language used for parsing depends on the current formula language (see {@link #getCurrentFormulaLanguage()}.
*
* @param code the formula code
* @param expand the expansion mode to use when evaluating the formula
* @return the parsed formula
*/
public IEvalElement parseFormula(final String code, final FormulaExpand expand) {
switch (this.getCurrentFormulaLanguage()) {
case DEFAULT:
return this.animationSelector.getCurrentTrace().getModel().parseFormula(code, expand);
case CLASSICAL_B:
return new ClassicalB(code, expand);
case EVENT_B:
return new EventB(code, expand);
default:
throw new AssertionError("Unhandled formula parse mode: " + this.getCurrentFormulaLanguage());
}
}
public @NotNull DisplayData executeOperation(final @NotNull String name, final @Nullable String predicate) {
final Trace trace = this.animationSelector.getCurrentTrace();
final String translatedOpName = Transition.unprettifyName(name);
......
......@@ -8,6 +8,7 @@ import com.google.inject.Provider;
import de.prob.animator.domainobjects.AbstractEvalResult;
import de.prob.animator.domainobjects.EvalResult;
import de.prob.animator.domainobjects.FormulaExpand;
import de.prob.animator.domainobjects.IEvalElement;
import de.prob.statespace.AnimationSelector;
import de.prob2.jupyter.Command;
import de.prob2.jupyter.CommandUtils;
......@@ -67,8 +68,10 @@ public final class AssertCommand implements Command {
@Override
public @NotNull DisplayData run(final @NotNull ParsedArguments args) {
final String code = this.kernelProvider.get().insertLetVariables(args.get(FORMULA_PARAM));
final AbstractEvalResult result = CommandUtils.withSourceCode(code, () -> this.animationSelector.getCurrentTrace().evalCurrent(code, FormulaExpand.TRUNCATE));
final ProBKernel kernel = this.kernelProvider.get();
final String code = kernel.insertLetVariables(args.get(FORMULA_PARAM));
final IEvalElement formula = kernel.parseFormula(code, FormulaExpand.TRUNCATE);
final AbstractEvalResult result = CommandUtils.withSourceCode(code, () -> this.animationSelector.getCurrentTrace().evalCurrent(formula));
if (result instanceof EvalResult && "TRUE".equals(((EvalResult)result).getValue())) {
// Use EvalResult.TRUE instead of the real result so that solution variables are not displayed.
return CommandUtils.displayDataForEvalResult(EvalResult.TRUE);
......
......@@ -95,8 +95,9 @@ public final class DotCommand implements Command {
final List<IEvalElement> dotCommandArgs;
final String code;
if (args.get(FORMULA_PARAM).isPresent()) {
code = this.kernelProvider.get().insertLetVariables(args.get(FORMULA_PARAM).get());
final IEvalElement formula = CommandUtils.withSourceCode(code, () -> this.animationSelector.getCurrentTrace().getModel().parseFormula(code, FormulaExpand.EXPAND));
final ProBKernel kernel = this.kernelProvider.get();
code = kernel.insertLetVariables(args.get(FORMULA_PARAM).get());
final IEvalElement formula = CommandUtils.withSourceCode(code, () -> kernel.parseFormula(code, FormulaExpand.EXPAND));
dotCommandArgs = Collections.singletonList(formula);
} else