From 9db68c3ab1e8a1b688691907c1acde58199b3920 Mon Sep 17 00:00:00 2001
From: dgelessus <dgelessus@users.noreply.github.com>
Date: Mon, 13 Jan 2020 11:23:42 +0100
Subject: [PATCH] Allow loading machines without an explicit ::load command

This allows typing machine code directly into a cell (just like an
expression or predicate), which is more convenient in most cases.
---
 CHANGELOG.md                                  |  1 +
 notebooks/tests/load_cell.ipynb               | 96 ++++++++++++++-----
 .../java/de/prob2/jupyter/ProBKernel.java     | 14 ++-
 .../prob2/jupyter/kernelspecfiles/kernel.js   |  2 +-
 4 files changed, 88 insertions(+), 25 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index cba20fc..ffc7986 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,7 @@
 * Added a `:modelcheck` command to run the ProB model checker.
 * Added support for additional languages and file extensions. The `:load` command now recognizes all languages and file extensions supported by ProB 2.
 	* **Note:** Some languages are not fully working yet (for example XTL).
+* Allowed loading B machines by entering their code directly, without an explicit `::load` command, similar to how this is already allowed with expressions.
 * Added a `:bsymb` command to load ProB's custom bsymb.sty LaTeX definitions on demand. After this command is executed, bsymb commands can be used in LaTeX formulas in Markdown cells.
 * Added support for Java 11.
 * Updated ProB 2 to version 3.2.12.
diff --git a/notebooks/tests/load_cell.ipynb b/notebooks/tests/load_cell.ipynb
index 936659a..f122de4 100644
--- a/notebooks/tests/load_cell.ipynb
+++ b/notebooks/tests/load_cell.ipynb
@@ -49,6 +49,28 @@
     "Machines can be loaded from code in the notebook."
    ]
   },
+  {
+   "cell_type": "code",
+   "execution_count": 1,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "Loaded machine: dinge"
+      ]
+     },
+     "execution_count": 1,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "MACHINE dinge\n",
+    "SETS DINGE = {EINS, ZWEI, DREI, VIER}\n",
+    "END"
+   ]
+  },
   {
    "cell_type": "code",
    "execution_count": 2,
@@ -56,8 +78,11 @@
    "outputs": [
     {
      "data": {
+      "text/markdown": [
+       "$\\{\\mathit{EINS},\\mathit{ZWEI},\\mathit{DREI},\\mathit{VIER}\\}$"
+      ],
       "text/plain": [
-       "Loaded machine: things"
+       "{EINS,ZWEI,DREI,VIER}"
       ]
      },
      "execution_count": 2,
@@ -65,6 +90,33 @@
      "output_type": "execute_result"
     }
    ],
+   "source": [
+    "DINGE"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Machines can be loaded with an explicit `::load` command."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 3,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "Loaded machine: things"
+      ]
+     },
+     "execution_count": 3,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
    "source": [
     "::load\n",
     "MACHINE things\n",
@@ -74,7 +126,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 3,
+   "execution_count": 4,
    "metadata": {},
    "outputs": [
     {
@@ -86,7 +138,7 @@
        "{ONE,TWO,THREE,FOUR}"
       ]
      },
-     "execution_count": 3,
+     "execution_count": 4,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -104,7 +156,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 4,
+   "execution_count": 5,
    "metadata": {},
    "outputs": [
     {
@@ -113,7 +165,7 @@
        "Loaded machine: prefs"
       ]
      },
-     "execution_count": 4,
+     "execution_count": 5,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -126,7 +178,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 5,
+   "execution_count": 6,
    "metadata": {},
    "outputs": [
     {
@@ -138,7 +190,7 @@
        "{−5,−4,−3,−2,−1,0,1,2,3,4,5}"
       ]
      },
-     "execution_count": 5,
+     "execution_count": 6,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -156,7 +208,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 6,
+   "execution_count": 7,
    "metadata": {},
    "outputs": [
     {
@@ -177,22 +229,22 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 7,
+   "execution_count": 8,
    "metadata": {
     "scrolled": true
    },
    "outputs": [
     {
      "ename": "WithSourceCodeException",
-     "evalue": "de.prob.exception.ProBError: Prolog said no.\nProB returned error messages:\nError: Could not infer type of x (/Users/Shared/Uni/SHK/ProB2/prob2-jupyter-kernel/notebooks/tests/(machine from Jupyter cell).mch:2:17 to 2:19)\nError: Identifier 'x' declared twice at (Line:Col[:File]) (Line:2 Col:17) and (Line:2 Col:14) (/Users/Shared/Uni/SHK/ProB2/prob2-jupyter-kernel/notebooks/tests/(machine from Jupyter cell).mch:2:17 to 2:19)",
+     "evalue": "de.prob.exception.ProBError: Prolog said no.\nProB returned error messages:\nError: Could not infer type of x (/Users/Shared/Uni/SHK/ProB2/prob2-jupyter-kernel/notebooks/tests/(machine from Jupyter cell).mch:2:17 to 2:18)\nError: Identifier 'x' declared twice at (Line:Col[:File]) (Line:2 Col:17) and (Line:2 Col:14) (/Users/Shared/Uni/SHK/ProB2/prob2-jupyter-kernel/notebooks/tests/(machine from Jupyter cell).mch:2:17 to 2:18)",
      "output_type": "error",
      "traceback": [
       "\u001b[1m\u001b[30mError from ProB: \u001b[0m\u001b[1m\u001b[31mProlog said no.\u001b[0m",
       "\u001b[1m\u001b[30m2 errors:\u001b[0m",
-      "\u001b[1m\u001b[31mError: Could not infer type of x (/Users/Shared/Uni/SHK/ProB2/prob2-jupyter-kernel/notebooks/tests/(machine from Jupyter cell).mch:2:17 to 2:19)\u001b[0m",
-      "\u001b[1m\u001b[31mError end column 19 out of bounds (0..17)\u001b[0m",
-      "\u001b[1m\u001b[31mError: Identifier 'x' declared twice at (Line:Col[:File]) (Line:2 Col:17) and (Line:2 Col:14) (/Users/Shared/Uni/SHK/ProB2/prob2-jupyter-kernel/notebooks/tests/(machine from Jupyter cell).mch:2:17 to 2:19)\u001b[0m",
-      "\u001b[1m\u001b[31mError end column 19 out of bounds (0..17)\u001b[0m"
+      "\u001b[1m\u001b[31mError: Could not infer type of x (/Users/Shared/Uni/SHK/ProB2/prob2-jupyter-kernel/notebooks/tests/(machine from Jupyter cell).mch:2:17 to 2:18)\u001b[0m",
+      "\u001b[1m\u001b[30m    CONSTANTS x, \u001b[0m\u001b[1m\u001b[30m\u001b[41mx\u001b[0m\u001b[1m\u001b[30m\u001b[0m",
+      "\u001b[1m\u001b[31mError: Identifier 'x' declared twice at (Line:Col[:File]) (Line:2 Col:17) and (Line:2 Col:14) (/Users/Shared/Uni/SHK/ProB2/prob2-jupyter-kernel/notebooks/tests/(machine from Jupyter cell).mch:2:17 to 2:18)\u001b[0m",
+      "\u001b[1m\u001b[30m    CONSTANTS x, \u001b[0m\u001b[1m\u001b[30m\u001b[41mx\u001b[0m\u001b[1m\u001b[30m\u001b[0m"
      ]
     }
    ],
@@ -206,24 +258,24 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 8,
+   "execution_count": 9,
    "metadata": {},
    "outputs": [
     {
      "ename": "WithSourceCodeException",
-     "evalue": "de.prob.exception.ProBError: Prolog said no.\nProB returned error messages:\nError: Could not infer type of x (/Users/Shared/Uni/SHK/ProB2/prob2-jupyter-kernel/notebooks/tests/(machine from Jupyter cell).mch:2:14 to 2:16)\nError: Type mismatch: Expected INTEGER, but was STRING in '\"string\" ^ \"string\" ^ \"string\"' (/Users/Shared/Uni/SHK/ProB2/prob2-jupyter-kernel/notebooks/tests/(machine from Jupyter cell).mch:3:35 to 5:17)\nError: Type mismatch: Expected INTEGER, but was STRING in '\"string\"' (/Users/Shared/Uni/SHK/ProB2/prob2-jupyter-kernel/notebooks/tests/(machine from Jupyter cell).mch:3:19 to 3:26)",
+     "evalue": "de.prob.exception.ProBError: Prolog said no.\nProB returned error messages:\nError: Could not infer type of x (/Users/Shared/Uni/SHK/ProB2/prob2-jupyter-kernel/notebooks/tests/(machine from Jupyter cell).mch:2:14 to 2:15)\nError: Type mismatch: Expected INTEGER, but was STRING in '\"string\" ^ \"string\" ^ \"string\"' (/Users/Shared/Uni/SHK/ProB2/prob2-jupyter-kernel/notebooks/tests/(machine from Jupyter cell).mch:3:35 to 5:18)\nError: Type mismatch: Expected INTEGER, but was STRING in '\"string\"' (/Users/Shared/Uni/SHK/ProB2/prob2-jupyter-kernel/notebooks/tests/(machine from Jupyter cell).mch:3:19 to 3:27)",
      "output_type": "error",
      "traceback": [
       "\u001b[1m\u001b[30mError from ProB: \u001b[0m\u001b[1m\u001b[31mProlog said no.\u001b[0m",
       "\u001b[1m\u001b[30m3 errors:\u001b[0m",
-      "\u001b[1m\u001b[31mError: Could not infer type of x (/Users/Shared/Uni/SHK/ProB2/prob2-jupyter-kernel/notebooks/tests/(machine from Jupyter cell).mch:2:14 to 2:16)\u001b[0m",
-      "\u001b[1m\u001b[31mError end column 16 out of bounds (0..14)\u001b[0m",
-      "\u001b[1m\u001b[31mError: Type mismatch: Expected INTEGER, but was STRING in '\"string\" ^ \"string\" ^ \"string\"' (/Users/Shared/Uni/SHK/ProB2/prob2-jupyter-kernel/notebooks/tests/(machine from Jupyter cell).mch:3:35 to 5:17)\u001b[0m",
+      "\u001b[1m\u001b[31mError: Could not infer type of x (/Users/Shared/Uni/SHK/ProB2/prob2-jupyter-kernel/notebooks/tests/(machine from Jupyter cell).mch:2:14 to 2:15)\u001b[0m",
+      "\u001b[1m\u001b[30m    CONSTANTS \u001b[0m\u001b[1m\u001b[30m\u001b[41mx\u001b[0m\u001b[1m\u001b[30m\u001b[0m",
+      "\u001b[1m\u001b[31mError: Type mismatch: Expected INTEGER, but was STRING in '\"string\" ^ \"string\" ^ \"string\"' (/Users/Shared/Uni/SHK/ProB2/prob2-jupyter-kernel/notebooks/tests/(machine from Jupyter cell).mch:3:35 to 5:18)\u001b[0m",
       "\u001b[1m\u001b[30m    PROPERTIES 1 = \"string\" & 1 = (\u001b[0m\u001b[1m\u001b[30m\u001b[41m\"string\"\u001b[0m",
       "\u001b[1m\u001b[30m\u001b[41m        ^ \"string\"\u001b[0m",
-      "\u001b[1m\u001b[30m\u001b[41m        ^ \"string\u001b[0m\u001b[1m\u001b[30m\")\u001b[0m",
-      "\u001b[1m\u001b[31mError: Type mismatch: Expected INTEGER, but was STRING in '\"string\"' (/Users/Shared/Uni/SHK/ProB2/prob2-jupyter-kernel/notebooks/tests/(machine from Jupyter cell).mch:3:19 to 3:26)\u001b[0m",
-      "\u001b[1m\u001b[30m    PROPERTIES 1 = \u001b[0m\u001b[1m\u001b[30m\u001b[41m\"string\u001b[0m\u001b[1m\u001b[30m\" & 1 = (\"string\"\u001b[0m"
+      "\u001b[1m\u001b[30m\u001b[41m        ^ \"string\"\u001b[0m\u001b[1m\u001b[30m)\u001b[0m",
+      "\u001b[1m\u001b[31mError: Type mismatch: Expected INTEGER, but was STRING in '\"string\"' (/Users/Shared/Uni/SHK/ProB2/prob2-jupyter-kernel/notebooks/tests/(machine from Jupyter cell).mch:3:19 to 3:27)\u001b[0m",
+      "\u001b[1m\u001b[30m    PROPERTIES 1 = \u001b[0m\u001b[1m\u001b[30m\u001b[41m\"string\"\u001b[0m\u001b[1m\u001b[30m & 1 = (\"string\"\u001b[0m"
      ]
     }
    ],
diff --git a/src/main/java/de/prob2/jupyter/ProBKernel.java b/src/main/java/de/prob2/jupyter/ProBKernel.java
index a599d36..a150536 100644
--- a/src/main/java/de/prob2/jupyter/ProBKernel.java
+++ b/src/main/java/de/prob2/jupyter/ProBKernel.java
@@ -277,19 +277,29 @@ public final class ProBKernel extends BaseKernel {
 		return result;
 	}
 	
+	private static boolean isMachineCode(final @NotNull String code) {
+		return code.startsWith("MACHINE");
+	}
+	
 	@Override
 	public @Nullable DisplayData eval(final String expr) {
 		assert expr != null;
 		
 		final Matcher commandMatcher = COMMAND_PATTERN.matcher(expr);
 		if (commandMatcher.matches()) {
+			// The input is a command, execute it directly.
 			final String name = commandMatcher.group(1);
 			assert name != null;
 			final String argString = commandMatcher.group(2) == null ? "" : commandMatcher.group(2);
 			return this.executeCommand(name, argString);
+		} else if (isMachineCode(expr)) {
+			// The input appears to be a machine, load it.
+			// The leading newline here is important. ::load expects the first input line to contain preference assignments; the actual machine code has to start on the second line.
+			return this.executeCommand("::load", "\n" + expr);
+		} else {
+			// By default, assume that the input is an expression and evaluate it.
+			return this.executeCommand(":eval", expr);
 		}
-		
-		return this.executeCommand(":eval", expr);
 	}
 	
 	@Override
diff --git a/src/main/resources/de/prob2/jupyter/kernelspecfiles/kernel.js b/src/main/resources/de/prob2/jupyter/kernelspecfiles/kernel.js
index 0a8e0a5..d771df0 100644
--- a/src/main/resources/de/prob2/jupyter/kernelspecfiles/kernel.js
+++ b/src/main/resources/de/prob2/jupyter/kernelspecfiles/kernel.js
@@ -309,7 +309,7 @@ define([
 									}
 									return "meta";
 								} else {
-									// No command found, switch to B mode.
+									// No command found, meaning the input is a B machine or expression, so switch to B mode.
 									switchToMode(state, "classicalb");
 									return state.innerMode.token(stream, state.innerState);
 								}
-- 
GitLab