| # 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 supported magic is: |
| * %args - to set the arguments passed to llvm-tblgen. |
| * %reset - to reset the cached code and magic state. |
| |
| 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. |
| |
| ```tablgen |
| %args |
| %reset |
| %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 |
| # A list of (code, magic) tuples. |
| # All the previous cell's code we have run since the last reset. |
| # This emulates a persistent state like a Python interpreter would have. |
| self._previous_code = "" |
| # The most recent set of magic since the last reset. |
| self._previous_magic = {} |
| |
| @property |
| def banner(self): |
| return "llvm-tblgen kernel %s" % __version__ |
| |
| def get_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. Put it on your PATH or set the" |
| " environment variable LLVM_TBLGEN_EXECUTABLE to point to it." |
| ) |
| self._executable = path |
| |
| return self._executable |
| |
| 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. |
| Where the key is the magic name (minus the %) and the values are lists |
| of the arguments to the magic. |
| |
| 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\\n %foo a b c\\nFoo") |
| ('Foo', {'foo': ['a', 'b', 'c']}) |
| >>> k.get_magic("%foo\\n%bar\\nFoo") |
| ('Foo', {'foo': [], 'bar': []}) |
| >>> k.get_magic("Foo\\n%foo\\nFoo") |
| ('Foo\\n%foo\\nFoo', {}) |
| >>> k.get_magic("%bar\\n\\n%foo") |
| ('\\n%foo', {'bar': []}) |
| >>> k.get_magic("%foo a b\\n Foo\\n%foo c d") |
| (' Foo\\n%foo c d', {'foo': ['a', 'b']}) |
| >>> k.get_magic("%foo a b\\n \\n%foo c d") |
| (' \\n%foo c d', {'foo': ['a', 'b']}) |
| """ |
| magic = {} |
| code_lines = [] |
| |
| lines = code.splitlines() |
| while lines: |
| line = lines.pop(0) |
| possible_magic = line.lstrip() |
| if possible_magic.startswith("%"): |
| magic_parts = possible_magic.split() |
| # Key has the % removed |
| magic[magic_parts[0][1:]] = magic_parts[1:] |
| else: |
| code_lines = [line, *lines] |
| break |
| |
| return "\n".join(code_lines), magic |
| |
| 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. |
| |
| Returns the code to compile and the arguments to use to do so. |
| |
| >>> k._previous_code = "" |
| >>> k._previous_magic = {} |
| >>> k.get_code_and_args("") |
| ('', []) |
| >>> k.get_code_and_args("%args 1\\nSome code") |
| ('Some code', ['1']) |
| >>> k.get_code_and_args("%args 2\\nSome more code") |
| ('Some code\\nSome more code', ['2']) |
| >>> k.get_code_and_args("%reset\\n%args 3 4\\nSome new code") |
| ('Some new code', ['3', '4']) |
| >>> k.get_code_and_args("%reset\\nSome new code") |
| ('Some new code', []) |
| """ |
| 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: |
| self._previous_code = new_code |
| self._previous_magic = new_magic |
| else: |
| self._previous_code += ("\n" if self._previous_code else "") + new_code |
| self._previous_magic.update(new_magic) |
| |
| return self._previous_code, self._previous_magic.get("args", []) |
| |
| def make_status(self): |
| return { |
| "status": "ok", |
| "execution_count": self.execution_count, |
| "payload": [], |
| "user_expressions": {}, |
| } |
| |
| def send_stream(self, name, content): |
| self.send_response(self.iopub_socket, "stream", {"name": name, "text": content}) |
| |
| return self.make_status() |
| |
| def send_stderr(self, stderr): |
| return self.send_stream("stderr", stderr) |
| |
| def send_stdout(self, stdout): |
| return self.send_stream("stdout", stdout) |
| |
| def do_execute( |
| 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) |
| |
| # 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) |
| try: |
| executable = self.get_executable() |
| except Exception as e: |
| return self.send_stderr(str(e)) |
| |
| with tempfile.TemporaryFile("w+") as f: |
| f.write(all_code) |
| f.seek(0) |
| got = subprocess.run( |
| [executable, *args], |
| stdin=f, |
| stderr=subprocess.PIPE, |
| stdout=subprocess.PIPE, |
| universal_newlines=True, |
| ) |
| |
| if not silent: |
| if got.stderr: |
| return self.send_stderr(got.stderr) |
| else: |
| return self.send_stdout(got.stdout) |
| else: |
| return self.make_status() |
| |
| |
| if __name__ == "__main__": |
| import doctest |
| |
| doctest.testmod(extraglobs={"k": TableGenKernel()}) |