diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6e3d795 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +/build/*.out* +/.ipynb* +/__pycache__/ +.vscode +log.txt +/lib/host +/lib/tree \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..ba3e537 --- /dev/null +++ b/Makefile @@ -0,0 +1,14 @@ +REPO_DIR := $(shell pwd) +DATA_DIR := $(shell jupyter --data-dir) + +$(if $(wildcard $(DATA_DIR)),,$(error Jupyter data directory not found; is Jupyter installed)) +$(info Jupyter data directory found at '$(DATA_DIR)') + +JSON_TEMPLATE := $(shell cat sac/kernel.json) +JSON_CONTENTS := $(subst ,$(REPO_DIR),$(JSON_TEMPLATE)) + +install: + mkdir -p $(DATA_DIR)/kernels + cp -r sac $(DATA_DIR)/kernels + echo $(JSON_CONTENTS) > $(DATA_DIR)/kernels/sac/kernel.json + cd lib; sac2c Jupyter.sac \ No newline at end of file diff --git a/README.md b/README.md index 62662fe..5bf30e2 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,19 @@ -[![docker pulls](https://img.shields.io/docker/pulls/sacbase/sac-jupyter-notebook)](https://hub.docker.com/r/sacbase/sac-jupyter-notebook) - -# SaC Jupyter kernel +# Jupyter kernel for SaC This repository contains Jupyter-related tools for SaC. -We recommend using the pre-built [Docker image](https://hub.docker.com/r/sacbase/sac-jupyter-notebook). +## Prerequisites -# Manual installation +- [sac2c and the standard library](https://sac-home.org/download:main) +- [Jupyter notebook](https://jupyter.org/install) -## Prerequisites +## Automatic installation -- [sac2c and the standard library](https://sac-home.org/download:main). -- [Jupyter notebook](https://jupyter.org/install). +```bash +make install +``` -## Installation +## Manual installation 1. Get the Jupyter data directory path using `jupyter --data-dir`. 2. Within this director, create a new directory `kernels`. diff --git a/actions.py b/actions.py new file mode 100644 index 0000000..95b5cf3 --- /dev/null +++ b/actions.py @@ -0,0 +1,121 @@ +import re + +class Action: + """ + compiler check return codes: + 1 = expression + 2 = statement + 3 = function + 4 = typedef + 5 = import + 6 = use + """ + def __init__(self): + self.lines : list[str] = [] + self.entries : dict[str, str] = {} + + def push(self, code): + # this is overly general and I am not sure if the inheritance approach is good here + # -> this uses the dict as if it was a set, yet it can still be used as a dict by the subclasses + self.entries[code] = code + + def to_str(self): + return '\n'.join(self.entries.values()) + + +class Expression(Action): + # one time execution + def __init__(self): + super().__init__() + self.code = '' + + def push(self, code): + self.code = code + + def to_str(self): + return self.code + + def clear(self): + self.code = '' + + +class Statement(Action): + # keep cumulative + def __init__(self): + super().__init__() + self.assignments: set[str] = set() + self.code = '' + + def push(self, code): + self.code = code + self.assignments = extract_assigned_variables(code) + + def add_definition(self, variable, value): + self.entries[variable] = value + + def to_str(self): + return self.code + + def get_assignments_str(self): + return """printf(";");\n """.join([f"""printf("{i}="); print_serialized({i});""" for i in self.assignments]) + + def get_definitions_str(self): + return "\n".join([f"{i} = {self.entries[i]};" for i in self.entries]) + + def clear(self): + self.assignments = [] + self.code = '' + + +class Function(Action): + # keep overwrite + def __init__(self): + super().__init__() + + def push(self, code): + self.entries[extract_function_indentifier(code)] = code + +class Typedef(Action): + # keep overwrite + def __init__(self): + super().__init__() + +class Import(Action): + # keep overwrite + def __init__(self): + super().__init__() + +class Use(Action): + # keep overwrite + def __init__(self): + super().__init__() + + +def extract_function_indentifier(code: str) -> str: + """ + placeholder function! To be replaced with sac2c implementation + returns identifier of function(s) entered. assumes sac2c classifies code as function + """ + return code.split()[1] + + +def extract_assigned_variables(code: str) -> list[str]: + """ placeholder function! To be replaced with sac2c implementation """ + cleaned = re.sub(r"==+", "", extract_code(code)) + divisions = cleaned.split("=") + variables = [re.split(r"[/\W/]+", var.strip())[-1] for var in divisions[:-1]] + return variables + +def extract_code(code: str): + """ removes string literals """ + return "".join([s for i, s in enumerate(re.split(r""""|'""", code)) if i % 2 == 0]) + +sac_action_map = { + -1: None, + 1: Expression, + 2: Statement, + 3: Function, + 4: Typedef, + 5: Import, + 6: Use + } diff --git a/config.json b/config.json new file mode 100644 index 0000000..e090d21 --- /dev/null +++ b/config.json @@ -0,0 +1,4 @@ +{ + "allow-seperated-function-override": false, + "allow-multiple-function-identifiers": true +} \ No newline at end of file diff --git a/kernel.py b/kernel.py index fc3df06..be33cdd 100644 --- a/kernel.py +++ b/kernel.py @@ -1,30 +1,18 @@ from queue import Queue from threading import Thread -from enum import Enum - -from ipykernel.kernelbase import Kernel - -import IPython -import re +from ipykernel.kernelbase import Kernel as JupyterKernel import subprocess import tempfile import shutil from ctypes.util import find_library import os -import os.path as path import json -import shlex - import ctypes +from textwrap import dedent +from objects import Status, Result -def rm_nonempty_dir (d): - for root, dirs, files in os.walk (d, topdown=False): - for name in files: - os.remove (os.path.join(root, name)) - for name in dirs: - os.rmdir (os.path.join(root, name)) - os.rmdir (d) - +from actions import * +from magics import Magic class RealTimeSubprocess(subprocess.Popen): """ @@ -86,316 +74,10 @@ def read_all_from_queue(queue): if stderr_contents: self._write_to_stderr(stderr_contents) - -################################################################################ -# -# -# For every 'type' of input, we define its very own Action class. -# If you want to add a new one, define a new subclass of Action, -# provide the methods explained below, and add an instance of this -# Action into the field "actions" in SacKernel! -# -# Any action needs to provide the following methods: -# check_input(self, code) -# process_input(self, code) -# revert_input(self, code) -# -# check_input finds out whether the given action is applicable -# and it returns a record {'found', 'code'}, indicating -# if the action has been found, and providing the input for -# processing the action. -# -# process_input performs the action. It returns a record -# { 'failed', 'stdout', 'stderr' }; finally, -# -# revert_input resets the internal state tp the one before -# processsing the input. It does not return anything. -# -# We try to keep as much state as possible local to the actions. -# Everything that *needs* to be shared between actions, lives -# in the SaCKernel class, a pointer to which is stored in all -# Action instances. -# All actions that pertain to the actual Sac code need to be -# subclasses of the abstract action Sac! -# -class Action: - def __init__(self, kernel): - self.kernel = kernel - - def check_input(self, code): - return {'found': False, 'code': code} - - def process_input(self, code): - return {'failed': False, 'stdout':"", 'stderr':""} - - def revert_input (self, code): - pass - - def check_magic (self, magic, code): - code = code.strip () - if code.startswith (magic): - return {'found': True, 'code': code[len (magic):]} - else: - return {'found': False, 'code': code} - - - -# -# %help -# -class Help(Action): - def check_input(self, code): - return self.check_magic ('%help', code) - - def process_input(self, code): - return {'failed':False, 'stdout':"""\ -Currently the following commands are available: - %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':""} - - - -# -# %print -# -class Print(Action): - def check_input(self, code): - return self.check_magic ('%print', code) - - def process_input(self, code): - return {'failed':False, - 'stdout': self.kernel.mk_sacprg (" /* StdIO::print ( your expression here ); */\n"), - 'stderr': ""} - - - -# -# %flags -# -class Flags(Action): - def check_input(self, code): - return self.check_magic ('%flags', code) - - def process_input(self, code): - return {'failed':False, 'stdout':' '.join (self.kernel.sac2c_flags), - 'stderr':""} - - - - -# -# %setflags -# -class Setflags(Action): - def check_input(self, code): - return self.check_magic ('%setflags', code) - - def process_input(self, code): - self.kernel.sac2c_flags = shlex.split (code) - return {'failed':False, 'stdout':"", 'stderr':""} - - - - -# -# sac - this is a super class for all sac-related action classes -# -class Sac(Action): - def check_input(self, code): - if (self.kernel.sac_check == None): - self.kernel.sac_check = self.kernel.run_sac2c_parser (code) - - if (self.kernel.sac_check['status'] == 'fail'): - return {'found': False, 'code': ""} - else: - return {'found': self.check_sac_action (code), 'code': code} - - def update_state (self, code): - pass - - def revert_state (self, code): - pass - - def mk_sac_prg (): - return "" - - def process_input(self, code): - self.update_state (code) - prg = self.kernel.mk_sacprg ("") - res = self.kernel.create_binary (prg) - if (not (res['failed'])): - res = self.kernel.run_binary () - return res - - 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 -# -class SacExpr(Sac): - def __init__(self, kernel): - super().__init__ (kernel) - self.expr = None - - def check_sac_action (self, code): - return (self.kernel.sac_check['ret'] == 1) - - def update_state (self, code): - self.expr = code - - def revert_state (self, code): - self.expr = None - - def mk_sacprg (self, goal): - if (self.expr == None): - return goal - else: - return "\n StdIO::print ({});\n".format (self.expr) - - - -# -# Sac - statement -# -class SacStmt(Sac): - def __init__(self, kernel): - super().__init__ (kernel) - self.stmts = [] - - def check_sac_action (self, code): - return (self.kernel.sac_check['ret'] == 2) - - def update_state (self, code): - self.stmts.append (" "+code.replace ("\n", "\n ")+"\n") - - def revert_state (self, code): - self.stmts.pop () - - def mk_sacprg (self, goal): - return "\nint main () {\n" + "".join (self.stmts) - - -# -# Sac - function -# -class SacFun(Sac): - def __init__(self, kernel): - super().__init__ (kernel) - self.funs = dict () - self.old_def = None - - 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) - - def revert_state (self, code): - self.pop_symb_dict (self.funs, self.old_def) - - def mk_sacprg (self, goal): - return "\n// functions\n" + "\n".join (self.funs.values ()) +"\n" - - -# -# Sac - typedef -# -class SacType(Sac): - def __init__(self, kernel): - super().__init__ (kernel) - self.typedefs = dict () - self.old_def = None - - 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) - - def revert_state (self, code): - self.pop_symb_dict (self.typedefs, self.old_def) - - def mk_sacprg (self, goal): - return "\n// typedefs\n" + "\n".join (self.typedefs.values ()) +"\n" - - - -# -# Sac - import -# -class SacImport(Sac): - def __init__(self, kernel): - super().__init__ (kernel) - self.imports = dict () - self.old_def = None - - 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) - - def revert_state (self, code): - self.pop_symb_dict (self.imports, self.old_def) - - def mk_sacprg (self, goal): - return "\n// imports\n" + "\n".join (self.imports.values ()) +"\n" - - - -# -# Sac - use -# -class SacUse(Sac): - def __init__(self, kernel): - super().__init__ (kernel) - self.uses = dict () - self.old_def = None - - 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) - - def revert_state (self, code): - self.pop_symb_dict (self.uses, self.old_def) - - def mk_sacprg (self, goal): - return "\n// uses\n" + "\n".join (self.uses.values ()) +"\n" - - - - - # # Here, the actual kernel implementation starts # - -class SacKernel(Kernel): +class SacKernel(JupyterKernel): implementation = 'jupyter_sac_kernel' implementation_version = '0.3' language = 'sac' @@ -405,24 +87,31 @@ class SacKernel(Kernel): 'file_extension': '.sac'} banner = "SaC kernel.\n" \ "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), - SacUse (self), SacImport (self), SacType (self), - SacFun (self), SacStmt (self), SacExpr (self)] + + self.actions : dict[type[Action], Action] = {Action: Action() for Action in sac_action_map.values() if Action} + self.magics : dict[type[Action], Magic] = {(m := Magic(self)).prefix: m for Magic in Magic.__subclasses__()} + + # set available magics so that the %help magic can list their definitions + self.magics["%help"].set_available_magics(self.magics.values()) + self.files = [] self.stdout = "" self.stderr = "" self.binary = None self.sac_check = None + self.separator = "--- internal variables ---" + # Make sure to do checks on array bounds as well - self.sac2c_flags = ['-v0', '-O0', '-noprelude', '-noinl', '-maxspec', '0', '-check', 'ps', '-st-below', '-st-compact'] + self.sac2c_flags = ['-v0', '-O0', '-noprelude', '-noinl', '-maxspec', '0', '-check', 'tc'] # get sac2c_p binary os.environ["PATH"] += "/usr/local/bin" - self.sac2c_bin = shutil.which ('sac2c') + self.sac2c_bin = shutil.which('sac2c') if not self.sac2c_bin: - raise RuntimeError ("Unable to find sac2c binary!") + raise RuntimeError("Unable to find sac2c binary!") # find global lib directory (different depending on sac2c version) sac_path_proc = subprocess.run([self.sac2c_bin, "-plibsac2c"], capture_output=True, text=True) @@ -440,35 +129,34 @@ def __init__(self, *args, **kwargs): if not sac2c_so_name: sac2c_so_name = find_library('sac2c_d') if not sac2c_so_name: - raise RuntimeError ("Unable to load sac2c shared library!") + raise RuntimeError("Unable to load sac2c shared library!") self.sac2c_so = None for sac_lib_path in sac_lib_paths.split(':'): - sac2c_so = path.join(sac_lib_path, sac2c_so_name) - if path.exists(sac2c_so): + sac2c_so = os.path.join(sac_lib_path, sac2c_so_name) + if os.path.exists(sac2c_so): self.sac2c_so = sac2c_so break if self.sac2c_so is None: - raise RuntimeError ("Unable to load sac2c shared library!") + raise RuntimeError("Unable to load sac2c shared library!") # get shared object - self.sac2c_so_handle = ctypes.CDLL (self.sac2c_so, mode=(1|ctypes.RTLD_GLOBAL)) + self.sac2c_so_handle = ctypes.CDLL(self.sac2c_so, mode=(1|ctypes.RTLD_GLOBAL)) # init sac2c jupyter interface - self.sac2c_so_handle.jupyter_init () - self.sac2c_so_handle.CTFinitialize () + self.sac2c_so_handle.jupyter_init() + self.sac2c_so_handle.CTFinitialize() self.sac2c_so_handle.jupyter_parse_from_string.restype = ctypes.c_void_p 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. - self.tmpdir = tempfile.mkdtemp (prefix="jup-sac") + # Create the directory where all the compilation/execution will be happening + self.tmpdir = tempfile.mkdtemp(prefix="jup-sac") - # Array is included by default. We execute the `use` declaration here - # to ensure that the SaC module cache has been initialized. - self.do_execute("use Array: all;", False) + # Array is included by default + self.actions[Use].push("use Array: all;") def cleanup_files(self): """Remove all the temporary files created by the kernel""" @@ -476,19 +164,19 @@ def cleanup_files(self): os.remove(file) # Remove the directory - rm_nonempty_dir (self.tmpdir) + rm_nonempty_dir(self.tmpdir) # Call some cleanup functions in sac2c library. - self.sac2c_so_handle.jupyter_finalize () - - def run_sac2c_parser (self, prog): - s = ctypes.c_char_p (prog.encode ('utf-8')) - ret_ptr = self.sac2c_so_handle.jupyter_parse_from_string (s, -1) #len (self.imports)) - ret_s = ctypes.cast (ret_ptr, ctypes.c_char_p).value - self.sac2c_so_handle.jupyter_free (ret_ptr) - j = {"status": "fail", "stderr": "cannot parse json: {}".format (ret_s)} + self.sac2c_so_handle.jupyter_finalize() + + def run_sac2c_parser(self, prog): + s = ctypes.c_char_p(prog.encode('utf-8')) + ret_ptr = self.sac2c_so_handle.jupyter_parse_from_string(s, -1) #len(self.imports)) + ret_s = ctypes.cast(ret_ptr, ctypes.c_char_p).value + self.sac2c_so_handle.jupyter_free(ret_ptr) + j = {"status": "fail", "stderr": "cannot parse json: {}".format(ret_s)} try: - j = json.loads (ret_s) + j = json.loads(ret_s) except: pass return j @@ -509,99 +197,181 @@ def _write_to_stdout(self, contents): def _write_to_stderr(self, contents): self.send_response(self.iopub_socket, 'stream', {'name': 'stderr', 'text': contents}) - def append_stdout (self, txt): + def append_stdout(self, txt): self.stdout += txt - def append_stderr (self, txt): + def append_stderr(self, txt): self.stderr += txt def create_jupyter_subprocess(self, cmd): self.stdout = "" self.stderr = "" return RealTimeSubprocess(cmd, - lambda contents: self.append_stdout (contents.decode()), - lambda contents: self.append_stderr (contents.decode()), + lambda contents: self.append_stdout(contents.decode()), + lambda contents: self.append_stderr(contents.decode()), self.tmpdir) - def mk_sacprg (self, goal): - prg = "" - for action in self.actions: - if (issubclass (type(action), Sac)): - prg += action.mk_sacprg (goal) - prg += " return 0;\n}" - return prg; + def mk_sacprg(self): + """ + returns a string of the SaC code snippet. + + "StdIO: print" is included by default as it is used for printing expressions. + """ + program = f"""\ + // use + use Jupyter: all; + use StdIO: all; + {escape(self.actions[Use].to_str())} + + // imports + {escape(self.actions[Import].to_str())} + + // typedefs + {escape(self.actions[Typedef].to_str())} + + // functions + {escape(self.actions[Function].to_str())} + + int main() {{ + // definitions + {escape(indent_tail(self.actions[Statement].get_definitions_str()))} + + // statements + {escape(indent_tail(self.actions[Statement].to_str()))} + + // expression + {f"StdIO::print({self.actions[Expression].to_str()});" if len(self.actions[Expression].to_str()) else ''} + + // assignments + printf("{self.separator}"); + {escape(indent_tail(self.actions[Statement].get_assignments_str()))} + return 0; + }} + """ + + # expressions and statemets need to discard their cache + self.actions[Expression].clear() + self.actions[Statement].clear() + + return unescape(dedent(program)) def compile_with_sac2c(self, source_filename, binary_filename, extra_flags=[]): # Flags are of type list of strings. - sac2cflags = self.sac2c_flags + extra_flags - args = [self.sac2c_bin] + ['-o', binary_filename] + sac2cflags + [source_filename] + args = [self.sac2c_bin] \ + + ['-o', binary_filename] \ + + self.sac2c_flags \ + + extra_flags \ + + [source_filename] \ + return self.create_jupyter_subprocess(args) - def create_binary (self, prg): + def create_binary(self, prg) -> Result: with self.new_temp_file(suffix='.sac') as source_file: source_file.write(prg) source_file.flush() with self.new_temp_file(suffix='.exe') as binary_file: - p = self.compile_with_sac2c (source_file.name, binary_file.name) + p = self.compile_with_sac2c(source_file.name, binary_file.name, ["-L", f"{os.getcwd()}/lib", f"-T", f"{os.getcwd()}/lib"]) while p.poll() is None: - p.write_contents() + p.write_contents() p.write_contents() if (p.returncode != 0): # Compilation failed - return {'failed': True, 'stdout': self.stdout, - 'stderr': self.stderr + - "[SaC kernel] sac2c exited with code {}, the executable will not be executed".format( - p.returncode)} + return Result(self.stdout, f"{self.stderr}[SaC kernel] sac2c exited with code {p.returncode}, the executable will not be executed", True) else: self.binary = binary_file.name - return {'failed':False, 'stdout': self.stdout, 'stderr': self.stderr } + return Result(self.stdout, self.stderr) - def run_binary (self): - p = self.create_jupyter_subprocess([self.binary]) - while p.poll() is None: - p.write_contents() + def run_binary(self) -> Result: + proc = self.create_jupyter_subprocess([self.binary]) + while proc.poll() is None: + proc.write_contents() - p.wait_for_threads() - p.write_contents() + proc.wait_for_threads() + proc.write_contents() - if (p.returncode != 0): # Compilation failed - return {'failed': True, 'stdout': self.stdout, - 'stderr': self.stderr + - "[SaC kernel] Executable exited with code {}".format( - p.returncode)} + if (proc.returncode != 0): # Compilation failed + return Result(self.stdout, f"{self.stderr}[SaC kernel] Executable exited with code {proc.returncode}", True) else: - return {'failed':False, 'stdout': self.stdout, 'stderr': self.stderr } - - def do_execute(self, code, silent, store_history=True, - user_expressions=None, allow_stdin=False): - - if not silent: - for action in self.actions: - cres = action.check_input (code) - if (cres['found']): - status = 'ok' - res = action.process_input (cres['code']) - if (res['failed']): - action.revert_input (code) - status = 'error' - elif (type(action).__name__ == 'SacExpr'): - action.revert_input (code) - if (res['stdout'] != ""): - self._write_to_stdout (res['stdout']) - if (res['stderr'] != ""): - self._write_to_stderr (res['stderr']) - break - if ( not cres['found']): #we know that the Sac check has failed! - status = 'error' - self._write_to_stderr ("[SaC kernel] This is not an expression/statements/function or use/import/typedef\n" - + self.sac_check['stderr']) - self.sac_check = None - + return Result(self.stdout, self.stderr) + + def do_execute(self, code, silent, store_history=True, user_expressions=None, allow_stdin=False): + parser_response = self.run_sac2c_parser(code) + action_class = sac_action_map[parser_response['ret']] + magic = self.magics.get(code.split(" ")[0]) + + if action_class: + self.actions[action_class].push(code) + status = self.execute_sac() + elif magic: + status = self.execute_magic(code, magic) + else: + self._write_to_stderr(f"[SaC kernel] Code could not be classified!\n{parser_response['stderr']}") + status = Status.ERROR + return {'status': status, 'execution_count': self.execution_count, 'payload': [], 'user_expressions': {}} + + def execute_sac(self): + program = self.mk_sacprg() + create_result = self.create_binary(program) + stdout = create_result.stdout + stderr = create_result.stderr + + if not create_result.failed: + definitions = '' + status = Status.OK + run_result = self.run_binary() + + text_out = run_result.stdout.split(self.separator) + stdout = text_out[0] + stderr = run_result.stderr + + if len(text_out) == 2: + definitions = text_out[1] + if '=' in extract_code(definitions): # TODO: not pretty... fix + for definition in definitions.split(';'): + variable, value = definition.split('=', 1) + self.actions[Statement].add_definition(variable, value) + + elif len(text_out) != 1: + raise Exception('Invalid return string!') + + else: + status = Status.ERROR + + self.show_output(stdout, stderr) + return status + + def execute_magic(self, code, magic): + magic_result = magic.process_input(code) + self.show_output(magic_result.stdout, magic_result.stderr) + return Status.OK + + def show_output(self, stdout, stderr): + if stdout: + self._write_to_stdout(stdout) + if stderr: + self._write_to_stderr(stderr) def do_shutdown(self, restart): """Cleanup the created source code files and executables when shutting down the kernel""" self.cleanup_files() +def rm_nonempty_dir(d): + for root, dirs, files in os.walk(d, topdown=False): + for name in files: + os.remove(os.path.join(root, name)) + for name in dirs: + os.rmdir(os.path.join(root, name)) + os.rmdir(d) + +def indent_tail(text, depth=4): + return ("\n" + " " * depth).join(text.split("\n")) + +def escape(text): + return text.replace("\n", "\\n") + +def unescape(text): + return text.replace("\\n", "\n") + if __name__ == "__main__": from ipykernel.kernelapp import IPKernelApp diff --git a/lib/Jupyter.sac b/lib/Jupyter.sac new file mode 100644 index 0000000..a54f7b9 --- /dev/null +++ b/lib/Jupyter.sac @@ -0,0 +1,121 @@ +module Jupyter; + +provide all; +export all; + +use StdIO: all; +use Array: all; + +/* + * Module to print sac arrays in python format. + * Works for all SaC datatypes. + */ + + +void print_serialized(byte[*] a) { print_serialized(toll(a)); } +void print_serialized(short[*] a) { print_serialized(toll(a)); } +void print_serialized(int[*] a) { print_serialized(toll(a)); } +void print_serialized(long[*] a) { print_serialized(toll(a)); } + +void print_serialized(ubyte[*] a) { print_serialized(toull(a)); } +void print_serialized(ushort[*] a) { print_serialized(toull(a)); } +void print_serialized(uint[*] a) { print_serialized(toull(a)); } +void print_serialized(ulong[*] a) { print_serialized(toull(a)); } + +void print_serialized(float[*] a) { print_serialized(tod(a)); } + + +void print_serialized (longlong[*] a){ + StdIO::printf ("["); + + for(i=0; i< shape(a)[0]; i++) { + print_serialized (a[i]); + if (i bool: + return code.startswith(self.prefix) + + def process_input(self, _): + return Result() + + def revert_input(self, _): + # what was this method used for?? + pass + + def get_help(self) -> str: + return f'{f"{self.prefix} {self.help_args}".ljust(25)} -- {self.help_str}' + +# +# %help +# +class Help(Magic): + def __init__(self, kernel): + super().__init__(kernel) + self.prefix = '%help' + self.help_str = 'list available magics and their function.' + self.available_magics: list[Magic] = [] + + def set_available_magics(self, magics): + self.available_magics = magics + + def process_input(self, _): + return Result(f'Currently the following commands are available:\n {"\n ".join([magic.get_help() for magic in self.available_magics])}') + +# +# %print +# +class Print(Magic): + def __init__(self, kernel): + super().__init__(kernel) + self.prefix = '%print' + self.help_str = 'print the current program including imports, functions and statements.' + + def process_input(self, _): + return Result(self.kernel.mk_sacprg()) +# +# %flags +# +class Flags(Magic): + def __init__(self, kernel): + super().__init__(kernel) + self.prefix = '%flags' + self.help_str = 'print flags that are used when running sac2c.' + + def process_input(self, _): + return Result(' '.join(self.kernel.sac2c_flags)) + +# +# %setflags +# +class Setflags(Magic): + def __init__(self, kernel): + super().__init__(kernel) + self.prefix = '%setflags' + self.help_str = 'set sac2c falgs to .' + self.help_args = '' + + def process_input(self, code): + self.kernel.sac2c_flags = shlex.split(code) + return Result() + +def check_magics(code, magics) -> Magic | None: + for magic in magics: + if magic.is_magic(code): + return magic + +def execute_magic(code, magic): + magic_result = magic.process_input(code) + stdout = magic_result['stdout'] + stderr = magic_result['stderr'] + return stdout, stderr diff --git a/objects.py b/objects.py new file mode 100644 index 0000000..9178dfe --- /dev/null +++ b/objects.py @@ -0,0 +1,14 @@ +class Status: + OK = 'ok' + FAIL = 'fail' + ERROR = 'error' + +class Result: + def __init__(self, stdout: str = '', stderr: str = '', failed: bool = False, status: Status = Status.OK): + self.failed = failed + self.stdout = stdout + self.stderr = stderr + self.status = status + + def to_dict(self): + return {'failed': self.failed, 'stdout': self.stdout, 'stderr': self.stderr, 'status': self.status} \ No newline at end of file diff --git a/sac/kernel.json b/sac/kernel.json index 38485de..86eb6f6 100644 --- a/sac/kernel.json +++ b/sac/kernel.json @@ -1,5 +1,5 @@ { - "argv": ["python3", "/kernel.py", "-f", "{connection_file}"], - "display_name": "SaC", - "language": "sac" + \"argv\": [\"python3\", \"/kernel.py\", \"-f\", \"{connection_file}\"], + \"display_name\": \"SaC\", + \"language\": \"sac\" } diff --git a/testing.ipynb b/testing.ipynb new file mode 100644 index 0000000..7898c38 --- /dev/null +++ b/testing.ipynb @@ -0,0 +1,257 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "7d6c25c1-b62f-4c55-9334-e273a8f4e284", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Currently the following commands are available:\n", + " %help -- list available magics and their function.\n", + " %print -- print the current program including imports, functions and statements.\n", + " %flags -- print flags that are used when running sac2c.\n", + " %setflags -- set sac2c falgs to ." + ] + } + ], + "source": [ + "%help" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "68c4fa21-5ac9-4814-ae25-e6b2898f74bf", + "metadata": {}, + "outputs": [], + "source": [ + "x = 1;\n", + "if (x < 0) print(x);" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "507805a8-47f3-428b-808c-72dac4fa25ce", + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "x = 4;\n", + "y = [91, 85, 76];\n", + "z = \"abc\";" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "252a8fb7-7306-433b-8704-66c45206bfbc", + "metadata": {}, + "outputs": [], + "source": [ + "int test(int x) {\n", + " return x + 1;\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "d2178223-c5b0-4a8e-900b-4f40a2820f52", + "metadata": {}, + "outputs": [], + "source": [ + "int test(int x) {\n", + " return x + 100;\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "b59a51a6-3257-43fb-a9f2-147068070ca6", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Dimension: 0\n", + "Shape : < >\n", + " 110\n" + ] + } + ], + "source": [ + "test(10)" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "f1cb6467-734d-4792-8822-f5302979987a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "// use\n", + "use Jupyter: all;\n", + "use StdIO: all;\n", + "use Array: all;\n", + "\n", + "// imports\n", + "\n", + "\n", + "// typedefs\n", + "\n", + "\n", + "// functions\n", + "int test(int x) {\n", + " return x + 100;\n", + "}\n", + "\n", + "int main() {\n", + " // definitions\n", + " x = 4 ;\n", + " y = [91 , 85 , 76 ];\n", + " z = \"abc\";\n", + "\n", + " // statements\n", + "\n", + "\n", + " // expression\n", + "\n", + "\n", + " // assignments\n", + " printf(\"--- internal variables ---\");\n", + "\n", + " return 0;\n", + "}\n" + ] + } + ], + "source": [ + "%print" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "525dd423-3978-4c8d-a61e-31721845d689", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Dimension: 0\n", + "Shape : < >\n", + " 4\n" + ] + } + ], + "source": [ + "print(x);" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "c700a93b-8820-4d1f-aa77-69d4787dbbe1", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Dimension: 0\n", + "Shape : < >\n", + " 10\n" + ] + } + ], + "source": [ + "y = 10;\n", + "print(y);" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "b8e03a07-4d57-4eb2-a52f-dc176c268ac8", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Dimension: 0\n", + "Shape : < >\n", + " 10\n" + ] + } + ], + "source": [ + "y" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "46d68b80-b632-452a-91ad-c83046007f80", + "metadata": {}, + "outputs": [], + "source": [ + "x = x + 10;" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "575f0c78-fb44-4b5c-88ed-3ef6b6bc7d48", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Dimension: 0\n", + "Shape : < >\n", + " 14\n" + ] + } + ], + "source": [ + "x" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ae818a44-bb2b-4150-aa92-8868caf9d97d", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "SaC", + "language": "sac", + "name": "sac" + }, + "language_info": { + "file_extension": ".sac", + "mimetype": "text/plain", + "name": "sac" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +}