diff --git a/Python/Python.sac b/Python/Python.sac new file mode 100644 index 0000000..9f1e64c --- /dev/null +++ b/Python/Python.sac @@ -0,0 +1,89 @@ +module Python; + +provide all; +export all; + +use StdIO: all; +use Array: all; + +/* + * Module to print sac arrays in python format. + * Works for all SaC datatypes. + */ + + +void pyPrint(byte[*] a) { pyPrint(toll(a)); } +void pyPrint(short[*] a) { pyPrint(toll(a)); } +void pyPrint(int[*] a) { pyPrint(toll(a)); } +void pyPrint(long[*] a) { pyPrint(toll(a)); } + +void pyPrint(ubyte[*] a) { pyPrint(toull(a)); } +void pyPrint(ushort[*] a) { pyPrint(toull(a)); } +void pyPrint(uint[*] a) { pyPrint(toull(a)); } +void pyPrint(ulong[*] a) { pyPrint(toull(a)); } + +void pyPrint(float[*] a) { pyPrint(tod(a)); } + + +void pyPrint (longlong[*] a){ + StdIO::printf ("["); + + for(i=0; i< shape(a)[0]; i++) { + pyPrint (a[i]); + if (i - -- reset sac2c falgs to + %plot (){} -- plot SaC variables using matplotlib python syntax at + and SaC variable seperated by commas at + . These variables can be used inside + of as python lists. + - The python code should have the matplotlib figure + defined as fig to work. For example start with + . + - Because Python enforces indentation, the + part can not begin with indentation as normally + done in function definitions. Instead start at the + beginning of the newline after '{'. + - Python imports can be used inside of and + matplotlib and numpy are already predefined as plt + and np respectively. + %print -- print the current program including + imports, functions and statements in the main. + %flags -- print flags that are used when running sac2c. + %setflags -- reset sac2c falgs to . """, 'stderr':""} @@ -186,7 +209,6 @@ def process_input(self, code): - # # %setflags # @@ -200,6 +222,69 @@ def process_input(self, code): +# +# %plot +# +class Plot(Action): + def check_input(self, code): + return self.check_magic ('%plot', code) + + def process_input(self, code): + if importlib.util.find_spec('matplotlib') is None: + return {'failed': True, 'stdout':"", 'stderr':"[SaC kernel] Matplotlib library not found. Install library to enjoy fancy visualisations."} + + try: pltscrpt, variables = self.parse_input(code) + except: return {'failed':True, 'stdout':"", 'stderr':"[SaC kernel] Incorrect syntax for %plot"} + + sac_variables = self.get_sac_variables(variables) + if sac_variables == []: + return {'failed':True, 'stdout':"", 'stderr':f"[Sac kernel] Variables could not be compiled: {variables}."} + + for i in sac_variables: + if i['failed']: + return {'failed':True, 'stdout':"", 'stderr':f"{i['stderr']}"} + + ldict = {} + for i, var in enumerate(sac_variables): + ldict[variables[i]] = eval(var['stdout']) + try: + pltscrpt = pltscrpt + "\nfig_width, fig_height = plt.gcf().get_size_inches()" + exec(pltscrpt,globals(),ldict) + fig = ldict['fig'] + fig_width = ldict['fig_width']*fig.dpi + fig_height = ldict['fig_height']*fig.dpi + plot_figure = self.to_png(fig) + except Exception as e: + return {'failed':True, 'stdout':"", 'stderr':"[Python] " + repr(e)} + + self.kernel._write_png_to_stdout(plot_figure, fig_width, fig_height) + return {'failed':False, 'stdout':"", 'stderr':""} + + # Return a base64-encoded PNG from a matplotlib figure. + def to_png(self, fig): + imgdata = BytesIO() + fig.savefig(imgdata, format='png') + imgdata.seek(0) + return urllib.parse.quote(base64.b64encode(imgdata.getvalue())) + # get sac variables in python format using the SaC 'Python' module and pyPrint function + def get_sac_variables(self, variables): + sac_variables = [] + for v in variables: + prg = self.kernel.mk_sacprg("\n pyPrint({});\n".format(v)) + res = self.kernel.create_binary(prg) + if (not (res['failed'])): + res = self.kernel.run_binary() + sac_variables.append(res) + else: + sac_variables.append(res) + return sac_variables + def parse_input(self, code): + py_variables = re.search("\((.+)\)(.*)\{", code) # Search for (...){ + plot_script = (code.split(py_variables.group())[1])[:-1] # Remove '}' and the variables + variables = py_variables.group(1).replace(" ", "").split(",") # Convert to list + return plot_script, variables + + # # sac - this is a super class for all sac-related action classes @@ -234,24 +319,6 @@ def process_input(self, code): def revert_input (self, code): self.revert_state (code) - # generic helper functions for dictionaries: - - def push_symb_dict (self, mydict, code): - key = self.kernel.sac_check['symbol'] - if (key in mydict): - res = mydict[key] - else: - res = None - mydict[key] = code - return res - - def pop_symb_dict (self, mydict, code): - key = self.kernel.sac_check['symbol'] - if (code == None): - del mydict[key] - else: - mydict[key] = code - # # Sac - expression # @@ -275,8 +342,6 @@ def mk_sacprg (self, goal): else: return "\n StdIO::print ({});\n".format (self.expr) - - # # Sac - statement # @@ -297,7 +362,6 @@ def revert_state (self, code): def mk_sacprg (self, goal): return "\nint main () {\n" + "".join (self.stmts) - # # Sac - function # @@ -311,14 +375,15 @@ def check_sac_action (self, code): return (self.kernel.sac_check['ret'] == 3) def update_state(self, code): - self.old_def = self.push_symb_dict (self.funs, code) + if self.kernel.sac_check['symbol'] in self.funs: + self.old_def = self.funs[self.kernel.sac_check['symbol']] + self.funs[self.kernel.sac_check['symbol']] = code def revert_state (self, code): - self.pop_symb_dict (self.funs, self.old_def) + self.funs[self.kernel.sac_check['symbol']] = self.old_def def mk_sacprg (self, goal): - return "\n// functions\n" + "\n".join (self.funs.values ()) +"\n" - + return "\n// functions\n" + "".join (map(lambda x: str(x), self.funs.values ())) +"\n" # # Sac - typedef @@ -333,15 +398,15 @@ def check_sac_action (self, code): return (self.kernel.sac_check['ret'] == 4) def update_state(self, code): - self.old_def = self.push_symb_dict (self.typedefs, code) + if self.kernel.sac_check['symbol'] in self.typedefs: + self.old_def = self.typedefs[self.kernel.sac_check['symbol']] + self.typedefs[self.kernel.sac_check['symbol']] = code def revert_state (self, code): - self.pop_symb_dict (self.typedefs, self.old_def) + self.typedefs[self.kernel.sac_check['symbol']] = self.old_def def mk_sacprg (self, goal): - return "\n// typedefs\n" + "\n".join (self.typedefs.values ()) +"\n" - - + return "\n// typedefs\n" + "".join (self.typedefs.values ()) +"\n" # # Sac - import @@ -356,15 +421,15 @@ def check_sac_action (self, code): return (self.kernel.sac_check['ret'] == 5) def update_state(self, code): - self.old_def = self.push_symb_dict (self.imports, code) + if self.kernel.sac_check['symbol'] in self.imports: + self.old_def = self.imports[self.kernel.sac_check['symbol']] + self.imports[self.kernel.sac_check['symbol']] = code def revert_state (self, code): - self.pop_symb_dict (self.imports, self.old_def) + self.imports[self.kernel.sac_check['symbol']] = self.old_def def mk_sacprg (self, goal): - return "\n// imports\n" + "\n".join (self.imports.values ()) +"\n" - - + return "\n// imports\n" + "".join (self.imports.values ()) +"\n" # # Sac - use @@ -380,18 +445,21 @@ def check_sac_action (self, code): return (self.kernel.sac_check['ret'] == 6) def update_state(self, code): - self.old_def = self.push_symb_dict (self.uses, code) + if self.kernel.sac_check['symbol'] in self.uses: + self.old_def = self.uses[self.kernel.sac_check['symbol']] + self.uses[self.kernel.sac_check['symbol']] = code def revert_state (self, code): - self.pop_symb_dict (self.uses, self.old_def) + self.uses[self.kernel.sac_check['symbol']] = self.old_def def mk_sacprg (self, goal): - return "\n// uses\n" + "\n".join (self.uses.values ()) +"\n" + return "\n// uses\n" + "".join (self.uses.values ()) +"\n" + # # Here, the actual kernel implementation starts # @@ -408,7 +476,7 @@ class SacKernel(Kernel): "Uses sac2c, to incrementaly compile the notebook.\n" def __init__(self, *args, **kwargs): super(SacKernel, self).__init__(*args, **kwargs) - self.actions = [Help (self), Print (self), Flags (self), Setflags (self), + self.actions = [Help (self), Print (self), Flags (self), Setflags (self), Plot(self), SacUse (self), SacImport (self), SacType (self), SacFun (self), SacStmt (self), SacExpr (self)] self.files = [] @@ -441,7 +509,13 @@ def __init__(self, *args, **kwargs): sac2c_so_name = find_library ('sac2c_d') if not sac2c_so_name: raise RuntimeError ("Unable to load sac2c shared library!") + + # `find_library` does not return the library path on linux but only the libraries + # name and thus will not be able to work properly. Can be fixed temporarely by + # using the comment below and giving an absolute path to libsac2c_p.so + #self.sac2c_so = "/.../libexec/sac2c/1.3.3-MijasCosta-1085-g70801/libsac2c_p.so" self.sac2c_so = path.join (sac_lib_path, sac2c_so_name) + # get shared object self.sac2c_so_handle = ctypes.CDLL (self.sac2c_so, mode=(1|ctypes.RTLD_GLOBAL)) @@ -453,7 +527,7 @@ def __init__(self, *args, **kwargs): self.sac2c_so_handle.jupyter_free.argtypes = ctypes.c_void_p, self.sac2c_so_handle.jupyter_free.res_rtype = ctypes.c_void_p - # Creatae the directory where all the compilation/execution will be happening. + # Create the directory where all the compilation/execution will be happening. self.tmpdir = tempfile.mkdtemp (prefix="jup-sac") def cleanup_files(self): @@ -495,6 +569,13 @@ def _write_to_stdout(self, contents): def _write_to_stderr(self, contents): self.send_response(self.iopub_socket, 'stream', {'name': 'stderr', 'text': contents}) + def _write_png_to_stdout(self, png, width, height): + content = { + 'data': {'image/png': png}, + 'metadata' : { 'image/png' : {'width': width,'height': height}} + } + self.send_response(self.iopub_socket,'display_data', content) + def append_stdout (self, txt): self.stdout += txt @@ -588,7 +669,6 @@ def do_shutdown(self, restart): """Cleanup the created source code files and executables when shutting down the kernel""" self.cleanup_files() - if __name__ == "__main__": from ipykernel.kernelapp import IPKernelApp IPKernelApp.launch_instance(kernel_class=SacKernel)