diff --git a/llvm/utils/TableGen/jupyter/LLVM_TableGen.ipynb b/llvm/utils/TableGen/jupyter/LLVM_TableGen.ipynb --- a/llvm/utils/TableGen/jupyter/LLVM_TableGen.ipynb +++ b/llvm/utils/TableGen/jupyter/LLVM_TableGen.ipynb @@ -169,11 +169,187 @@ "def other_thing : Stuff {}" ] }, + { + "cell_type": "markdown", + "id": "8f120cee", + "metadata": {}, + "source": [ + "You can also configure the default reset behaviour using the `%config` magic." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "7c356853", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "------------- Classes -----------------\n", + "class Thing {\n", + "}\n", + "------------- Defs -----------------\n" + ] + } + ], + "source": [ + "%config cellreset on\n", + "class Thing {}" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "c5399401", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + ":2:13: error: Couldn't find class 'Thing'\n", + "def AThing: Thing {}\n", + " ^\n" + ] + } + ], + "source": [ + "// The cache is reset here so this is an error.\n", + "def AThing: Thing {}" + ] + }, + { + "cell_type": "markdown", + "id": "53220700", + "metadata": {}, + "source": [ + "The default value is `off`, meaning cells are connected. If you want to override the default for one cell only, use the `%reset` or `%noreset` magic. These always override the default." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "50a865ea", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "------------- Classes -----------------\n", + "class Thing {\n", + "}\n", + "------------- Defs -----------------\n" + ] + } + ], + "source": [ + "class Thing {}" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "3c079649", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "------------- Classes -----------------\n", + "class Thing {\n", + "}\n", + "------------- Defs -----------------\n", + "def AThing {\t// Thing\n", + "}\n" + ] + } + ], + "source": [ + "%noreset\n", + "// This works because of the noreset above.\n", + "def AThing: Thing {}" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "75f3c51c", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + ":2:19: error: Couldn't find class 'Thing'\n", + "def AnotherThing: Thing {}\n", + " ^\n" + ] + } + ], + "source": [ + "// This does not because we're not changing the default.\n", + "def AnotherThing: Thing {}" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "9abe502e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "------------- Classes -----------------\n", + "------------- Defs -----------------\n" + ] + } + ], + "source": [ + "%config cellreset off\n", + "%reset\n", + "// Here we have an empty cache and default reset behaviour." + ] + }, + { + "cell_type": "markdown", + "id": "0de42c3c", + "metadata": {}, + "source": [ + "It is not valid to have `%reset` and `%noreset` in the same cell." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "a763daa4", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "%reset and %noreset in the same cell is not allowed. Use only one, or neither." + ] + } + ], + "source": [ + "%reset\n", + "%noreset" + ] + }, { "cell_type": "markdown", "id": "f6d4613b", "metadata": {}, "source": [ + "Consider setting `cellreset` to the majority usecase for your notebook. For example a tutorial building a large example across many cells will likely want it `off`. One with many standalone examples, `on`.\n", + "\n", "There is a \"magic\" directive `%args` that you can use to send command line arguments to `llvm-tblgen`.\n", "\n", "For example, here we have some code that shows a warning." @@ -181,7 +357,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 13, "id": "2586893b", "metadata": {}, "outputs": [ @@ -212,7 +388,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 14, "id": "ae078bc4", "metadata": {}, "outputs": [ @@ -242,7 +418,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 15, "id": "9634170c", "metadata": {}, "outputs": [ @@ -264,7 +440,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 16, "id": "fa15b542", "metadata": {}, "outputs": [ @@ -293,7 +469,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 17, "id": "2ac46c7a", "metadata": {}, "outputs": [ diff --git a/llvm/utils/TableGen/jupyter/LLVM_TableGen.md b/llvm/utils/TableGen/jupyter/LLVM_TableGen.md --- a/llvm/utils/TableGen/jupyter/LLVM_TableGen.md +++ b/llvm/utils/TableGen/jupyter/LLVM_TableGen.md @@ -77,6 +77,93 @@ ^ +You can also configure the default reset behaviour using the `%config` magic. + + +```tablegen +%config cellreset on +class Thing {} +``` + + ------------- Classes ----------------- + class Thing { + } + ------------- Defs ----------------- + + + +```tablegen +// The cache is reset here so this is an error. +def AThing: Thing {} +``` + + :2:13: error: Couldn't find class 'Thing' + def AThing: Thing {} + ^ + + +The default value is `off`, meaning cells are connected. If you want to override the default for one cell only, use the `%reset` or `%noreset` magic. These always override the default. + + +```tablegen +class Thing {} +``` + + ------------- Classes ----------------- + class Thing { + } + ------------- Defs ----------------- + + + +```tablegen +%noreset +// This works because of the noreset above. +def AThing: Thing {} +``` + + ------------- Classes ----------------- + class Thing { + } + ------------- Defs ----------------- + def AThing { // Thing + } + + + +```tablegen +// This does not because we're not changing the default. +def AnotherThing: Thing {} +``` + + :2:19: error: Couldn't find class 'Thing' + def AnotherThing: Thing {} + ^ + + + +```tablegen +%config cellreset off +%reset +// Here we have an empty cache and default reset behaviour. +``` + + ------------- Classes ----------------- + ------------- Defs ----------------- + + +It is not valid to have `%reset` and `%noreset` in the same cell. + + +```tablegen +%reset +%noreset +``` + + %reset and %noreset in the same cell is not allowed. Use only one, or neither. + +Consider setting `cellreset` to the majority usecase for your notebook. For example a tutorial building a large example across many cells will likely want it `off`. One with many standalone examples, `on`. + There is a "magic" directive `%args` that you can use to send command line arguments to `llvm-tblgen`. For example, here we have some code that shows a warning. diff --git a/llvm/utils/TableGen/jupyter/tablegen_kernel/kernel.py b/llvm/utils/TableGen/jupyter/tablegen_kernel/kernel.py --- a/llvm/utils/TableGen/jupyter/tablegen_kernel/kernel.py +++ b/llvm/utils/TableGen/jupyter/tablegen_kernel/kernel.py @@ -11,20 +11,34 @@ __version__ = "0.0.1" +class TableGenKernelException(Exception): + pass + + class TableGenKernel(Kernel): """Kernel using llvm-tblgen inside jupyter. All input is treated as TableGen unless the first non whitespace character is "%" in which case it is a "magic" line. - The supported magic is: - * %args - to set the arguments passed to llvm-tblgen. - * %reset - to reset the cached code and magic state. + The supported cell magic is: + * %args - to set the arguments passed to llvm-tblgen. + * %reset - to reset the cached code and magic state. + * %noreset - to not reset the cached code and magic state + (useful when you have changed the default to always + reset the cache). These are "cell magic" meaning it applies to the whole cell. Therefore it must be the first line, or part of a run of magic lines starting from the first line. + The following are global magic (that applies to all cells going + forward): + * %config - to change the behaviour of the kernel overall, including + changing defaults for things like resets. + + Global magic must be written in the same way as cell magic. + ```tablgen %args %reset @@ -59,6 +73,9 @@ self._previous_code = "" # The most recent set of magic since the last reset. self._previous_magic = {} + # The default cache reset behaviour. True means do not cache anything + # between cells. + self._cell_reset = False @property def banner(self): @@ -82,6 +99,61 @@ return self._executable + def parse_config_magic(self, config): + """Config should be a list of parameters given to the %config command. + We allow only one setting per %config line and that setting can only + have one value. + + Assuming the parameters are valid, update the kernel's setting with + the new value. + + If there is an error, raise a TableGenKernelException. + + >>> k.parse_config_magic([]) + Traceback (most recent call last): + ... + TableGenKernelException: Incorrect number of parameters to %config. Expected %config . + >>> k._cell_reset + False + >>> k.parse_config_magic(["a", "b", "c"]) + Traceback (most recent call last): + ... + TableGenKernelException: Incorrect number of parameters to %config. Expected %config . + >>> k.parse_config_magic(["notasetting", "..."]) + Traceback (most recent call last): + ... + TableGenKernelException: Unknown kernel setting "notasetting". Possible settings are: "cellreset". + >>> k.parse_config_magic(["cellreset", "food"]) + Traceback (most recent call last): + ... + TableGenKernelException: Invalid value for setting "cellreset", expected "on" or "off". + >>> k.parse_config_magic(["cellreset", "on"]) + >>> k._cell_reset + True + >>> k.parse_config_magic(["cellreset", "off"]) + >>> k._cell_reset + False + """ + if len(config) != 2: + raise TableGenKernelException( + "Incorrect number of parameters to %config. Expected %config ." + ) + + name, value = config + if name != "cellreset": + raise TableGenKernelException( + 'Unknown kernel setting "{}". ' + 'Possible settings are: "cellreset".'.format(name) + ) + + try: + self._cell_reset = {"on": True, "off": False}[value.lower()] + except KeyError: + raise TableGenKernelException( + 'Invalid value for setting "{}", ' + 'expected "on" or "off".'.format(name) + ) + def get_magic(self, code): """Given a block of code remove the magic lines from it. Returns a tuple of newline joined code lines and a dictionary of magic. @@ -128,6 +200,51 @@ return "\n".join(code_lines), magic + def should_reset(self, magic): + """Return true if we should reset the cache, based on the default + setting and the current cell's magic %reset and/or %noreset. + + >>> k._cell_reset = False + >>> k.should_reset({}) + False + >>> k.should_reset({'reset': [], 'noreset': []}) + Traceback (most recent call last): + ... + TableGenKernelException: %reset and %noreset in the same cell is not allowed. Use only one, or neither. + >>> k.should_reset({'reset': []}) + True + >>> k.should_reset({'noreset': []}) + False + >>> k._cell_reset = True + >>> k.should_reset({}) + True + >>> k.should_reset({'reset': [], 'noreset': []}) + Traceback (most recent call last): + ... + TableGenKernelException: %reset and %noreset in the same cell is not allowed. Use only one, or neither. + >>> k.should_reset({'reset': []}) + True + >>> k.should_reset({'noreset': []}) + False + """ + # Cell reset is the default unless told otherwise. + should_reset = self._cell_reset + # Magic reset commands always win if present. + reset = magic.get("reset") is not None + noreset = magic.get("noreset") is not None + + if reset and not noreset: + should_reset = True + elif noreset and not reset: + should_reset = False + elif noreset and reset: + raise TableGenKernelException( + "%reset and %noreset in the same cell is not allowed. Use only one, or neither." + ) + # else neither are set so use the default. + + return should_reset + def get_code_and_args(self, new_code): """Get the code that do_execute should use, taking into account the code from any cached cells. @@ -149,8 +266,12 @@ """ new_code, new_magic = self.get_magic(new_code) - # Only a reset in the newest cell actually does a reset. - if new_magic.get("reset") is not None: + # Update kernel configuration first, if needed. + config_magic = new_magic.get("config") + if config_magic is not None: + self.parse_config_magic(config_magic) + + if self.should_reset(new_magic): self._previous_code = new_code self._previous_magic = new_magic else: @@ -182,7 +303,10 @@ self, code, silent, store_history=True, user_expressions=None, allow_stdin=False ): """Execute user code using llvm-tblgen binary.""" - all_code, args = self.get_code_and_args(code) + try: + all_code, args = self.get_code_and_args(code) + except TableGenKernelException as e: + return self.send_stderr(str(e)) # If we cannot find llvm-tblgen, propogate the error to the notebook. # (in case the user is not able to see the output from the Jupyter server)