1210a383eSDavid Spickett# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 2210a383eSDavid Spickett# See https://llvm.org/LICENSE.txt for license information. 3210a383eSDavid Spickett# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 4210a383eSDavid Spickett 5210a383eSDavid Spickettimport os 6210a383eSDavid Spickettimport shutil 7210a383eSDavid Spickettimport subprocess 8210a383eSDavid Spickettimport tempfile 9210a383eSDavid Spickettfrom ipykernel.kernelbase import Kernel 10210a383eSDavid Spickett 11210a383eSDavid Spickett__version__ = "0.0.1" 12210a383eSDavid Spickett 13210a383eSDavid Spickett 14fbec83cbSDavid Spickettclass TableGenKernelException(Exception): 15fbec83cbSDavid Spickett pass 16fbec83cbSDavid Spickett 17fbec83cbSDavid Spickett 18210a383eSDavid Spickettclass TableGenKernel(Kernel): 19210a383eSDavid Spickett """Kernel using llvm-tblgen inside jupyter. 20210a383eSDavid Spickett 21210a383eSDavid Spickett All input is treated as TableGen unless the first non whitespace character 22210a383eSDavid Spickett is "%" in which case it is a "magic" line. 23210a383eSDavid Spickett 24fbec83cbSDavid Spickett The supported cell magic is: 2592787e3eSDavid Spickett * %args - to set the arguments passed to llvm-tblgen. 2692787e3eSDavid Spickett * %reset - to reset the cached code and magic state. 27fbec83cbSDavid Spickett * %noreset - to not reset the cached code and magic state 28fbec83cbSDavid Spickett (useful when you have changed the default to always 29fbec83cbSDavid Spickett reset the cache). 30210a383eSDavid Spickett 3192787e3eSDavid Spickett These are "cell magic" meaning it applies to the whole cell. Therefore 32210a383eSDavid Spickett it must be the first line, or part of a run of magic lines starting 33210a383eSDavid Spickett from the first line. 34210a383eSDavid Spickett 35fbec83cbSDavid Spickett The following are global magic (that applies to all cells going 36fbec83cbSDavid Spickett forward): 37fbec83cbSDavid Spickett * %config - to change the behaviour of the kernel overall, including 38fbec83cbSDavid Spickett changing defaults for things like resets. 39fbec83cbSDavid Spickett 40fbec83cbSDavid Spickett Global magic must be written in the same way as cell magic. 41fbec83cbSDavid Spickett 42210a383eSDavid Spickett ```tablgen 43210a383eSDavid Spickett %args 4492787e3eSDavid Spickett %reset 45210a383eSDavid Spickett %args --print-records --print-detailed-records 46210a383eSDavid Spickett class Stuff { 47210a383eSDavid Spickett string Name; 48210a383eSDavid Spickett } 49210a383eSDavid Spickett 50210a383eSDavid Spickett def a_thing : Stuff {} 51210a383eSDavid Spickett ``` 52210a383eSDavid Spickett 53210a383eSDavid Spickett """ 54210a383eSDavid Spickett 55210a383eSDavid Spickett implementation = "tablegen" 56210a383eSDavid Spickett implementation_version = __version__ 57210a383eSDavid Spickett 58210a383eSDavid Spickett language_version = __version__ 59210a383eSDavid Spickett language = "tablegen" 60210a383eSDavid Spickett language_info = { 61210a383eSDavid Spickett "name": "tablegen", 62210a383eSDavid Spickett "mimetype": "text/x-tablegen", 63210a383eSDavid Spickett "file_extension": ".td", 64210a383eSDavid Spickett "pygments_lexer": "text", 65210a383eSDavid Spickett } 66210a383eSDavid Spickett 67210a383eSDavid Spickett def __init__(self, **kwargs): 68210a383eSDavid Spickett Kernel.__init__(self, **kwargs) 69210a383eSDavid Spickett self._executable = None 7092787e3eSDavid Spickett # A list of (code, magic) tuples. 7192787e3eSDavid Spickett # All the previous cell's code we have run since the last reset. 7292787e3eSDavid Spickett # This emulates a persistent state like a Python interpreter would have. 7392787e3eSDavid Spickett self._previous_code = "" 7492787e3eSDavid Spickett # The most recent set of magic since the last reset. 7592787e3eSDavid Spickett self._previous_magic = {} 76fbec83cbSDavid Spickett # The default cache reset behaviour. True means do not cache anything 77fbec83cbSDavid Spickett # between cells. 78fbec83cbSDavid Spickett self._cell_reset = False 79210a383eSDavid Spickett 80210a383eSDavid Spickett @property 81210a383eSDavid Spickett def banner(self): 82210a383eSDavid Spickett return "llvm-tblgen kernel %s" % __version__ 83210a383eSDavid Spickett 84fdd18e86SDavid Spickett def get_executable(self): 85210a383eSDavid Spickett """If this is the first run, search for llvm-tblgen. 86210a383eSDavid Spickett Otherwise return the cached path to it.""" 87210a383eSDavid Spickett if self._executable is None: 88210a383eSDavid Spickett path = os.environ.get("LLVM_TBLGEN_EXECUTABLE") 89210a383eSDavid Spickett if path is not None and os.path.isfile(path) and os.access(path, os.X_OK): 90210a383eSDavid Spickett self._executable = path 91210a383eSDavid Spickett else: 92210a383eSDavid Spickett path = shutil.which("llvm-tblgen") 93210a383eSDavid Spickett if path is None: 94fdd18e86SDavid Spickett raise OSError( 95fdd18e86SDavid Spickett "llvm-tblgen not found. Put it on your PATH or set the" 96fdd18e86SDavid Spickett " environment variable LLVM_TBLGEN_EXECUTABLE to point to it." 97fdd18e86SDavid Spickett ) 98210a383eSDavid Spickett self._executable = path 99210a383eSDavid Spickett 100210a383eSDavid Spickett return self._executable 101210a383eSDavid Spickett 102fbec83cbSDavid Spickett def parse_config_magic(self, config): 103fbec83cbSDavid Spickett """Config should be a list of parameters given to the %config command. 104fbec83cbSDavid Spickett We allow only one setting per %config line and that setting can only 105fbec83cbSDavid Spickett have one value. 106fbec83cbSDavid Spickett 107fbec83cbSDavid Spickett Assuming the parameters are valid, update the kernel's setting with 108fbec83cbSDavid Spickett the new value. 109fbec83cbSDavid Spickett 110fbec83cbSDavid Spickett If there is an error, raise a TableGenKernelException. 111fbec83cbSDavid Spickett 112fbec83cbSDavid Spickett >>> k.parse_config_magic([]) 113fbec83cbSDavid Spickett Traceback (most recent call last): 114fbec83cbSDavid Spickett ... 115fbec83cbSDavid Spickett TableGenKernelException: Incorrect number of parameters to %config. Expected %config <setting> <value>. 116fbec83cbSDavid Spickett >>> k._cell_reset 117fbec83cbSDavid Spickett False 118fbec83cbSDavid Spickett >>> k.parse_config_magic(["a", "b", "c"]) 119fbec83cbSDavid Spickett Traceback (most recent call last): 120fbec83cbSDavid Spickett ... 121fbec83cbSDavid Spickett TableGenKernelException: Incorrect number of parameters to %config. Expected %config <setting> <value>. 122fbec83cbSDavid Spickett >>> k.parse_config_magic(["notasetting", "..."]) 123fbec83cbSDavid Spickett Traceback (most recent call last): 124fbec83cbSDavid Spickett ... 125fbec83cbSDavid Spickett TableGenKernelException: Unknown kernel setting "notasetting". Possible settings are: "cellreset". 126fbec83cbSDavid Spickett >>> k.parse_config_magic(["cellreset", "food"]) 127fbec83cbSDavid Spickett Traceback (most recent call last): 128fbec83cbSDavid Spickett ... 129fbec83cbSDavid Spickett TableGenKernelException: Invalid value for setting "cellreset", expected "on" or "off". 130fbec83cbSDavid Spickett >>> k.parse_config_magic(["cellreset", "on"]) 131fbec83cbSDavid Spickett >>> k._cell_reset 132fbec83cbSDavid Spickett True 133fbec83cbSDavid Spickett >>> k.parse_config_magic(["cellreset", "off"]) 134fbec83cbSDavid Spickett >>> k._cell_reset 135fbec83cbSDavid Spickett False 136fbec83cbSDavid Spickett """ 137fbec83cbSDavid Spickett if len(config) != 2: 138fbec83cbSDavid Spickett raise TableGenKernelException( 139fbec83cbSDavid Spickett "Incorrect number of parameters to %config. Expected %config <setting> <value>." 140fbec83cbSDavid Spickett ) 141fbec83cbSDavid Spickett 142fbec83cbSDavid Spickett name, value = config 143fbec83cbSDavid Spickett if name != "cellreset": 144fbec83cbSDavid Spickett raise TableGenKernelException( 145fbec83cbSDavid Spickett 'Unknown kernel setting "{}". ' 146fbec83cbSDavid Spickett 'Possible settings are: "cellreset".'.format(name) 147fbec83cbSDavid Spickett ) 148fbec83cbSDavid Spickett 149fbec83cbSDavid Spickett try: 150fbec83cbSDavid Spickett self._cell_reset = {"on": True, "off": False}[value.lower()] 151fbec83cbSDavid Spickett except KeyError: 152fbec83cbSDavid Spickett raise TableGenKernelException( 153fbec83cbSDavid Spickett 'Invalid value for setting "{}", ' 154fbec83cbSDavid Spickett 'expected "on" or "off".'.format(name) 155fbec83cbSDavid Spickett ) 156fbec83cbSDavid Spickett 157210a383eSDavid Spickett def get_magic(self, code): 15892787e3eSDavid Spickett """Given a block of code remove the magic lines from it. 15992787e3eSDavid Spickett Returns a tuple of newline joined code lines and a dictionary of magic. 16092787e3eSDavid Spickett Where the key is the magic name (minus the %) and the values are lists 16192787e3eSDavid Spickett of the arguments to the magic. 162210a383eSDavid Spickett 163210a383eSDavid Spickett Currently we only look for "cell magic" which must be at the start of 164210a383eSDavid Spickett the cell. Meaning the first line, or a set of lines beginning with % 165210a383eSDavid Spickett that come before the first non-magic line. 166210a383eSDavid Spickett 167210a383eSDavid Spickett >>> k.get_magic("") 16892787e3eSDavid Spickett ('', {}) 169210a383eSDavid Spickett >>> k.get_magic("Hello World.\\nHello again.") 17092787e3eSDavid Spickett ('Hello World.\\nHello again.', {}) 171210a383eSDavid Spickett >>> k.get_magic(" %foo a b c") 17292787e3eSDavid Spickett ('', {'foo': ['a', 'b', 'c']}) 17392787e3eSDavid Spickett >>> k.get_magic("%foo\\n %foo a b c\\nFoo") 17492787e3eSDavid Spickett ('Foo', {'foo': ['a', 'b', 'c']}) 17592787e3eSDavid Spickett >>> k.get_magic("%foo\\n%bar\\nFoo") 17692787e3eSDavid Spickett ('Foo', {'foo': [], 'bar': []}) 177210a383eSDavid Spickett >>> k.get_magic("Foo\\n%foo\\nFoo") 17892787e3eSDavid Spickett ('Foo\\n%foo\\nFoo', {}) 17992787e3eSDavid Spickett >>> k.get_magic("%bar\\n\\n%foo") 18092787e3eSDavid Spickett ('\\n%foo', {'bar': []}) 18192787e3eSDavid Spickett >>> k.get_magic("%foo a b\\n Foo\\n%foo c d") 18292787e3eSDavid Spickett (' Foo\\n%foo c d', {'foo': ['a', 'b']}) 18392787e3eSDavid Spickett >>> k.get_magic("%foo a b\\n \\n%foo c d") 18492787e3eSDavid Spickett (' \\n%foo c d', {'foo': ['a', 'b']}) 185210a383eSDavid Spickett """ 18692787e3eSDavid Spickett magic = {} 187210a383eSDavid Spickett code_lines = [] 188210a383eSDavid Spickett 189210a383eSDavid Spickett lines = code.splitlines() 190210a383eSDavid Spickett while lines: 191210a383eSDavid Spickett line = lines.pop(0) 192210a383eSDavid Spickett possible_magic = line.lstrip() 193210a383eSDavid Spickett if possible_magic.startswith("%"): 19492787e3eSDavid Spickett magic_parts = possible_magic.split() 19592787e3eSDavid Spickett # Key has the % removed 19692787e3eSDavid Spickett magic[magic_parts[0][1:]] = magic_parts[1:] 197210a383eSDavid Spickett else: 198210a383eSDavid Spickett code_lines = [line, *lines] 199210a383eSDavid Spickett break 200210a383eSDavid Spickett 20192787e3eSDavid Spickett return "\n".join(code_lines), magic 20292787e3eSDavid Spickett 203fbec83cbSDavid Spickett def should_reset(self, magic): 204fbec83cbSDavid Spickett """Return true if we should reset the cache, based on the default 205fbec83cbSDavid Spickett setting and the current cell's magic %reset and/or %noreset. 206fbec83cbSDavid Spickett 207fbec83cbSDavid Spickett >>> k._cell_reset = False 208fbec83cbSDavid Spickett >>> k.should_reset({}) 209fbec83cbSDavid Spickett False 210fbec83cbSDavid Spickett >>> k.should_reset({'reset': [], 'noreset': []}) 211fbec83cbSDavid Spickett Traceback (most recent call last): 212fbec83cbSDavid Spickett ... 213fbec83cbSDavid Spickett TableGenKernelException: %reset and %noreset in the same cell is not allowed. Use only one, or neither. 214fbec83cbSDavid Spickett >>> k.should_reset({'reset': []}) 215fbec83cbSDavid Spickett True 216fbec83cbSDavid Spickett >>> k.should_reset({'noreset': []}) 217fbec83cbSDavid Spickett False 218fbec83cbSDavid Spickett >>> k._cell_reset = True 219fbec83cbSDavid Spickett >>> k.should_reset({}) 220fbec83cbSDavid Spickett True 221fbec83cbSDavid Spickett >>> k.should_reset({'reset': [], 'noreset': []}) 222fbec83cbSDavid Spickett Traceback (most recent call last): 223fbec83cbSDavid Spickett ... 224fbec83cbSDavid Spickett TableGenKernelException: %reset and %noreset in the same cell is not allowed. Use only one, or neither. 225fbec83cbSDavid Spickett >>> k.should_reset({'reset': []}) 226fbec83cbSDavid Spickett True 227fbec83cbSDavid Spickett >>> k.should_reset({'noreset': []}) 228fbec83cbSDavid Spickett False 229fbec83cbSDavid Spickett """ 230fbec83cbSDavid Spickett # Cell reset is the default unless told otherwise. 231fbec83cbSDavid Spickett should_reset = self._cell_reset 232fbec83cbSDavid Spickett # Magic reset commands always win if present. 233fbec83cbSDavid Spickett reset = magic.get("reset") is not None 234fbec83cbSDavid Spickett noreset = magic.get("noreset") is not None 235fbec83cbSDavid Spickett 236fbec83cbSDavid Spickett if reset and not noreset: 237fbec83cbSDavid Spickett should_reset = True 238fbec83cbSDavid Spickett elif noreset and not reset: 239fbec83cbSDavid Spickett should_reset = False 240fbec83cbSDavid Spickett elif noreset and reset: 241fbec83cbSDavid Spickett raise TableGenKernelException( 242fbec83cbSDavid Spickett "%reset and %noreset in the same cell is not allowed. Use only one, or neither." 243fbec83cbSDavid Spickett ) 244fbec83cbSDavid Spickett # else neither are set so use the default. 245fbec83cbSDavid Spickett 246fbec83cbSDavid Spickett return should_reset 247fbec83cbSDavid Spickett 24892787e3eSDavid Spickett def get_code_and_args(self, new_code): 24992787e3eSDavid Spickett """Get the code that do_execute should use, taking into account 25092787e3eSDavid Spickett the code from any cached cells. 25192787e3eSDavid Spickett 25292787e3eSDavid Spickett Returns the code to compile and the arguments to use to do so. 25392787e3eSDavid Spickett 25492787e3eSDavid Spickett >>> k._previous_code = "" 25592787e3eSDavid Spickett >>> k._previous_magic = {} 25692787e3eSDavid Spickett >>> k.get_code_and_args("") 25792787e3eSDavid Spickett ('', []) 25892787e3eSDavid Spickett >>> k.get_code_and_args("%args 1\\nSome code") 25992787e3eSDavid Spickett ('Some code', ['1']) 26092787e3eSDavid Spickett >>> k.get_code_and_args("%args 2\\nSome more code") 26192787e3eSDavid Spickett ('Some code\\nSome more code', ['2']) 26292787e3eSDavid Spickett >>> k.get_code_and_args("%reset\\n%args 3 4\\nSome new code") 26392787e3eSDavid Spickett ('Some new code', ['3', '4']) 26492787e3eSDavid Spickett >>> k.get_code_and_args("%reset\\nSome new code") 26592787e3eSDavid Spickett ('Some new code', []) 26692787e3eSDavid Spickett """ 26792787e3eSDavid Spickett new_code, new_magic = self.get_magic(new_code) 26892787e3eSDavid Spickett 269fbec83cbSDavid Spickett # Update kernel configuration first, if needed. 270fbec83cbSDavid Spickett config_magic = new_magic.get("config") 271fbec83cbSDavid Spickett if config_magic is not None: 272fbec83cbSDavid Spickett self.parse_config_magic(config_magic) 273fbec83cbSDavid Spickett 274fbec83cbSDavid Spickett if self.should_reset(new_magic): 27592787e3eSDavid Spickett self._previous_code = new_code 27692787e3eSDavid Spickett self._previous_magic = new_magic 27792787e3eSDavid Spickett else: 27892787e3eSDavid Spickett self._previous_code += ("\n" if self._previous_code else "") + new_code 27992787e3eSDavid Spickett self._previous_magic.update(new_magic) 28092787e3eSDavid Spickett 28192787e3eSDavid Spickett return self._previous_code, self._previous_magic.get("args", []) 282210a383eSDavid Spickett 283fdd18e86SDavid Spickett def make_status(self): 284fdd18e86SDavid Spickett return { 285fdd18e86SDavid Spickett "status": "ok", 286fdd18e86SDavid Spickett "execution_count": self.execution_count, 287fdd18e86SDavid Spickett "payload": [], 288fdd18e86SDavid Spickett "user_expressions": {}, 289fdd18e86SDavid Spickett } 290fdd18e86SDavid Spickett 291fdd18e86SDavid Spickett def send_stream(self, name, content): 292fdd18e86SDavid Spickett self.send_response(self.iopub_socket, "stream", {"name": name, "text": content}) 293fdd18e86SDavid Spickett 294fdd18e86SDavid Spickett return self.make_status() 295fdd18e86SDavid Spickett 296fdd18e86SDavid Spickett def send_stderr(self, stderr): 297fdd18e86SDavid Spickett return self.send_stream("stderr", stderr) 298fdd18e86SDavid Spickett 299fdd18e86SDavid Spickett def send_stdout(self, stdout): 300fdd18e86SDavid Spickett return self.send_stream("stdout", stdout) 301fdd18e86SDavid Spickett 302210a383eSDavid Spickett def do_execute( 303210a383eSDavid Spickett self, code, silent, store_history=True, user_expressions=None, allow_stdin=False 304210a383eSDavid Spickett ): 305210a383eSDavid Spickett """Execute user code using llvm-tblgen binary.""" 306fbec83cbSDavid Spickett try: 30792787e3eSDavid Spickett all_code, args = self.get_code_and_args(code) 308fbec83cbSDavid Spickett except TableGenKernelException as e: 309fbec83cbSDavid Spickett return self.send_stderr(str(e)) 310210a383eSDavid Spickett 311*f8559751SJay Foad # If we cannot find llvm-tblgen, propagate the error to the notebook. 312fdd18e86SDavid Spickett # (in case the user is not able to see the output from the Jupyter server) 313fdd18e86SDavid Spickett try: 314fdd18e86SDavid Spickett executable = self.get_executable() 315fdd18e86SDavid Spickett except Exception as e: 316fdd18e86SDavid Spickett return self.send_stderr(str(e)) 317fdd18e86SDavid Spickett 318210a383eSDavid Spickett with tempfile.TemporaryFile("w+") as f: 31992787e3eSDavid Spickett f.write(all_code) 320210a383eSDavid Spickett f.seek(0) 321210a383eSDavid Spickett got = subprocess.run( 322fdd18e86SDavid Spickett [executable, *args], 323210a383eSDavid Spickett stdin=f, 324210a383eSDavid Spickett stderr=subprocess.PIPE, 325210a383eSDavid Spickett stdout=subprocess.PIPE, 326210a383eSDavid Spickett universal_newlines=True, 327210a383eSDavid Spickett ) 328210a383eSDavid Spickett 329210a383eSDavid Spickett if not silent: 330210a383eSDavid Spickett if got.stderr: 331fdd18e86SDavid Spickett return self.send_stderr(got.stderr) 332210a383eSDavid Spickett else: 333fdd18e86SDavid Spickett return self.send_stdout(got.stdout) 334fdd18e86SDavid Spickett else: 335fdd18e86SDavid Spickett return self.make_status() 336210a383eSDavid Spickett 337210a383eSDavid Spickett 338210a383eSDavid Spickettif __name__ == "__main__": 339210a383eSDavid Spickett import doctest 340210a383eSDavid Spickett 341210a383eSDavid Spickett doctest.testmod(extraglobs={"k": TableGenKernel()}) 342