diff --git a/llvm/utils/TableGen/jupyter/.gitignore b/llvm/utils/TableGen/jupyter/.gitignore new file mode 100644 --- /dev/null +++ b/llvm/utils/TableGen/jupyter/.gitignore @@ -0,0 +1,5 @@ +__pycache__ +*.pyc +build/ +dist/ +MANIFEST diff --git a/llvm/utils/TableGen/jupyter/LLVM_TableGen.ipynb b/llvm/utils/TableGen/jupyter/LLVM_TableGen.ipynb new file mode 100644 --- /dev/null +++ b/llvm/utils/TableGen/jupyter/LLVM_TableGen.ipynb @@ -0,0 +1,252 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "c45bc113", + "metadata": {}, + "source": [ + "# LLVM TableGen Kernel" + ] + }, + { + "cell_type": "markdown", + "id": "483313fc", + "metadata": {}, + "source": [ + "This notebook is running `llvm-tblgen`." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "e238dd42", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "------------- Classes -----------------\n", + "class Foo {\n", + "}\n", + "------------- Defs -----------------\n" + ] + } + ], + "source": [ + "// This is some tablegen\n", + "class Foo {}" + ] + }, + { + "cell_type": "markdown", + "id": "27f5aa83", + "metadata": {}, + "source": [ + "Errors printed to stderr are shown." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "e8b10293", + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + ":1:1: error: Unexpected token at top level\n", + "This is not tablegen.\n", + "^\n" + ] + } + ], + "source": [ + "This is not tablegen." + ] + }, + { + "cell_type": "markdown", + "id": "83907141", + "metadata": {}, + "source": [ + "Add some classes to get some output." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "4ca8a9cb", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "------------- Classes -----------------\n", + "class Stuff {\n", + "}\n", + "------------- Defs -----------------\n", + "def thing {\t// Stuff\n", + "}\n" + ] + } + ], + "source": [ + "class Stuff {}\n", + "def thing : Stuff {}" + ] + }, + { + "cell_type": "markdown", + "id": "3f29d1a0", + "metadata": {}, + "source": [ + "Currently cells are not connected. Meaning that this next cell doesn't have the class from the previous one." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "3a204c70", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + ":1:19: error: Couldn't find class 'Stuff'\n", + "def other_thing : Stuff {}\n", + " ^\n" + ] + } + ], + "source": [ + "def other_thing : Stuff {}" + ] + }, + { + "cell_type": "markdown", + "id": "f6d4613b", + "metadata": {}, + "source": [ + "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." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "2586893b", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + ":1:25: warning: unused template argument: Thing:B\n", + "class Thing {\n", + " ^\n" + ] + } + ], + "source": [ + "class Thing {\n", + " int num = A;\n", + "}" + ] + }, + { + "cell_type": "markdown", + "id": "41be44e7", + "metadata": {}, + "source": [ + "We can pass an argument to ignore that warning." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "ae078bc4", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "------------- Classes -----------------\n", + "class Thing {\n", + " int num = Thing:A;\n", + "}\n", + "------------- Defs -----------------\n" + ] + } + ], + "source": [ + "%args --no-warn-on-unused-template-args\n", + "class Thing {\n", + " int num = A;\n", + "}" + ] + }, + { + "cell_type": "markdown", + "id": "316b9543", + "metadata": {}, + "source": [ + "The last `%args` in a cell will be the arguments used." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "9634170c", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + ":1:25: warning: unused template argument: Thing:B\n", + "class Thing {\n", + " ^\n" + ] + } + ], + "source": [ + "%args --no-warn-on-unused-template-args\n", + "%args\n", + "class Thing {\n", + " int num = A;\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "29bd004b", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "LLVM TableGen", + "language": "tablegen", + "name": "tablegen" + }, + "language_info": { + "file_extension": ".td", + "mimetype": "text/x-tablegen", + "name": "tablegen", + "pygments_lexer": "text" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/llvm/utils/TableGen/jupyter/LLVM_TableGen.md b/llvm/utils/TableGen/jupyter/LLVM_TableGen.md new file mode 100644 --- /dev/null +++ b/llvm/utils/TableGen/jupyter/LLVM_TableGen.md @@ -0,0 +1,109 @@ +# LLVM TableGen Kernel + +This notebook is running `llvm-tblgen`. + + +```tablegen +// This is some tablegen +class Foo {} +``` + + ------------- Classes ----------------- + class Foo { + } + ------------- Defs ----------------- + + +Errors printed to stderr are shown. + + +```tablegen +This is not tablegen. +``` + + :1:1: error: Unexpected token at top level + This is not tablegen. + ^ + + +Add some classes to get some output. + + +```tablegen +class Stuff {} +def thing : Stuff {} +``` + + ------------- Classes ----------------- + class Stuff { + } + ------------- Defs ----------------- + def thing { // Stuff + } + + +Currently cells are not connected. Meaning that this next cell doesn't have the class from the previous one. + + +```tablegen +def other_thing : Stuff {} +``` + + :1:19: error: Couldn't find class 'Stuff' + def other_thing : Stuff {} + ^ + + +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. + + +```tablegen +class Thing { + int num = A; +} +``` + + :1:25: warning: unused template argument: Thing:B + class Thing { + ^ + + +We can pass an argument to ignore that warning. + + +```tablegen +%args --no-warn-on-unused-template-args +class Thing { + int num = A; +} +``` + + ------------- Classes ----------------- + class Thing { + int num = Thing:A; + } + ------------- Defs ----------------- + + +The last `%args` in a cell will be the arguments used. + + +```tablegen +%args --no-warn-on-unused-template-args +%args +class Thing { + int num = A; +} +``` + + :1:25: warning: unused template argument: Thing:B + class Thing { + ^ + + + +```tablegen + +``` diff --git a/llvm/utils/TableGen/jupyter/README.md b/llvm/utils/TableGen/jupyter/README.md new file mode 100644 --- /dev/null +++ b/llvm/utils/TableGen/jupyter/README.md @@ -0,0 +1,36 @@ +A Jupyter kernel for TableGen (llvm-tblgen) + +To use the kernel, first install it into jupyter: + + python3 -m tablegen_kernel.install + +Then put this folder on your PYTHONPATH so jupyter can find it: + +```shell + export PYTHONPATH=$PYTHONPATH: +``` + +Then run one of: + +```shell + jupyter notebook + # Then in the notebook interface, select 'LLVM TableGen' from the 'New' menu. + + # To run the example notebook in this folder. + jupyter notebook LLVM_Tablegen.ipynb + + # To use the kernel from the command line. + jupyter console --kernel tablegen +``` + +If you just want to see the results of the notebook, you can read +`LLVM_Tablegen.md` instead. + +`llvm-tblgen` is expected to be either in the `PATH` or you can set +the environment variable `LLVM_TBLGEN_EXECUTABLE` to point to it directly. + +To run the kernel's doctests do: + +```shell + python3 tablegen_kernel/kernel.py +``` diff --git a/llvm/utils/TableGen/jupyter/tablegen_kernel/__init__.py b/llvm/utils/TableGen/jupyter/tablegen_kernel/__init__.py new file mode 100644 --- /dev/null +++ b/llvm/utils/TableGen/jupyter/tablegen_kernel/__init__.py @@ -0,0 +1,6 @@ +# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +# See https://llvm.org/LICENSE.txt for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +"""An llvm-tblgen kernel for Jupyter""" + +from .kernel import __version__ diff --git a/llvm/utils/TableGen/jupyter/tablegen_kernel/__main__.py b/llvm/utils/TableGen/jupyter/tablegen_kernel/__main__.py new file mode 100644 --- /dev/null +++ b/llvm/utils/TableGen/jupyter/tablegen_kernel/__main__.py @@ -0,0 +1,8 @@ +# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +# See https://llvm.org/LICENSE.txt for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +from ipykernel.kernelapp import IPKernelApp +from .kernel import TableGenKernel + +IPKernelApp.launch_instance(kernel_class=TableGenKernel) diff --git a/llvm/utils/TableGen/jupyter/tablegen_kernel/assets/kernel.json b/llvm/utils/TableGen/jupyter/tablegen_kernel/assets/kernel.json new file mode 100644 --- /dev/null +++ b/llvm/utils/TableGen/jupyter/tablegen_kernel/assets/kernel.json @@ -0,0 +1,14 @@ +{ + "argv": [ + "python3", "-m", "tablegen_kernel", "-f", "{connection_file}" + ], + "display_name": "LLVM TableGen", + "language": "tablegen", + "language_info": { + "name": "tablegen", + "codemirror_mode": "tablegen", + "mimetype": "text/x-tablegen", + "file_extension": ".td", + "pygments_lexer": "text" + } +} diff --git a/llvm/utils/TableGen/jupyter/tablegen_kernel/install.py b/llvm/utils/TableGen/jupyter/tablegen_kernel/install.py new file mode 100644 --- /dev/null +++ b/llvm/utils/TableGen/jupyter/tablegen_kernel/install.py @@ -0,0 +1,50 @@ +# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +# See https://llvm.org/LICENSE.txt for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +import os +import argparse +from jupyter_client.kernelspec import KernelSpecManager + + +def install_my_kernel_spec(user=True, prefix=None): + """Install the kernel spec for user in given prefix.""" + print("Installing llvm-tblgen IPython kernel spec") + pkgroot = os.path.dirname(__file__) + KernelSpecManager().install_kernel_spec( + os.path.join(pkgroot, "assets"), "tablegen", user=user, prefix=prefix + ) + + +def _is_root(): + """Returns whether the current user is root.""" + try: + return os.geteuid() == 0 + except AttributeError: + # Return false wherever unknown. + return False + + +def main(argv=None): + parser = argparse.ArgumentParser( + description="Install KernelSpec for LLVM TableGen Kernel" + ) + prefix_locations = parser.add_mutually_exclusive_group() + + prefix_locations.add_argument( + "--user", help="Install in user home directory", action="store_true" + ) + prefix_locations.add_argument( + "--prefix", help="Install directory prefix", default=None + ) + + args = parser.parse_args(argv) + + user = args.user or not _is_root() + prefix = args.prefix + + install_my_kernel_spec(user=user, prefix=prefix) + + +if __name__ == "__main__": + main() diff --git a/llvm/utils/TableGen/jupyter/tablegen_kernel/kernel.py b/llvm/utils/TableGen/jupyter/tablegen_kernel/kernel.py new file mode 100644 --- /dev/null +++ b/llvm/utils/TableGen/jupyter/tablegen_kernel/kernel.py @@ -0,0 +1,160 @@ +# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +# See https://llvm.org/LICENSE.txt for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +import os +import shutil +import subprocess +import tempfile +from ipykernel.kernelbase import Kernel + +__version__ = "0.0.1" + + +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 only magic line supported is "%args". The rest of the line is the + arguments passed to llvm-tblgen. + + This is "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. + + ```tablgen + %args + %args --print-records --print-detailed-records + class Stuff { + string Name; + } + + def a_thing : Stuff {} + ``` + + """ + + implementation = "tablegen" + implementation_version = __version__ + + language_version = __version__ + language = "tablegen" + language_info = { + "name": "tablegen", + "mimetype": "text/x-tablegen", + "file_extension": ".td", + "pygments_lexer": "text", + } + + def __init__(self, **kwargs): + Kernel.__init__(self, **kwargs) + self._executable = None + + @property + def banner(self): + return "llvm-tblgen kernel %s" % __version__ + + @property + def executable(self): + """If this is the first run, search for llvm-tblgen. + Otherwise return the cached path to it.""" + if self._executable is None: + path = os.environ.get("LLVM_TBLGEN_EXECUTABLE") + if path is not None and os.path.isfile(path) and os.access(path, os.X_OK): + self._executable = path + else: + path = shutil.which("llvm-tblgen") + if path is None: + raise OSError("llvm-tblgen not found, please see README") + self._executable = path + + return self._executable + + def get_magic(self, code): + """Given a block of code remove the magic lines from it and return + a tuple of the code lines (newline joined) and a list of magic lines + with their leading spaces removed. + + Currently we only look for "cell magic" which must be at the start of + the cell. Meaning the first line, or a set of lines beginning with % + that come before the first non-magic line. + + >>> k.get_magic("") + ('', []) + >>> k.get_magic("Hello World.\\nHello again.") + ('Hello World.\\nHello again.', []) + >>> k.get_magic(" %foo a b c") + ('', ['%foo a b c']) + >>> k.get_magic(" %foo a b c\\n%foo\\nFoo") + ('Foo', ['%foo a b c', '%foo']) + >>> k.get_magic("Foo\\n%foo\\nFoo") + ('Foo\\n%foo\\nFoo', []) + >>> k.get_magic("%foo\\n Foo\\n%foo") + (' Foo\\n%foo', ['%foo']) + >>> k.get_magic("%foo\\n\\n%foo") + ('\\n%foo', ['%foo']) + >>> k.get_magic("%foo\\n \\n%foo") + (' \\n%foo', ['%foo']) + """ + magic_lines = [] + code_lines = [] + + lines = code.splitlines() + while lines: + line = lines.pop(0) + possible_magic = line.lstrip() + if possible_magic.startswith("%"): + magic_lines.append(possible_magic) + else: + code_lines = [line, *lines] + break + + return "\n".join(code_lines), magic_lines + + def do_execute( + self, code, silent, store_history=True, user_expressions=None, allow_stdin=False + ): + """Execute user code using llvm-tblgen binary.""" + code, magic = self.get_magic(code) + + extra_args = [] + for m in magic: + if m.startswith("%args"): + # Last one in wins + extra_args = m.split()[1:] + + with tempfile.TemporaryFile("w+") as f: + f.write(code) + f.seek(0) + got = subprocess.run( + [self.executable, *extra_args], + stdin=f, + stderr=subprocess.PIPE, + stdout=subprocess.PIPE, + universal_newlines=True, + ) + + if not silent: + if got.stderr: + self.send_response( + self.iopub_socket, "stream", {"name": "stderr", "text": got.stderr} + ) + else: + self.send_response( + self.iopub_socket, "stream", {"name": "stdout", "text": got.stdout} + ) + + return { + "status": "ok", + "execution_count": self.execution_count, + "payload": [], + "user_expressions": {}, + } + + +if __name__ == "__main__": + import doctest + + doctest.testmod(extraglobs={"k": TableGenKernel()})