From 7fe0db6914e025023627c1f84e76b42d69bb6cd2 Mon Sep 17 00:00:00 2001 From: Ole Siepmann <57126764+oleting@users.noreply.github.com> Date: Tue, 22 Dec 2020 19:42:54 +0100 Subject: [PATCH 1/2] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ee43f6a..3f9f6bc 100644 --- a/README.md +++ b/README.md @@ -3,4 +3,4 @@ This python code generates a Nassi Shneiderman Diagramm from Java Source Code How it works: -In the final Version, you will just have to execute the nassi.exe and choose your Code, it will display the Nassi-Shneiderman-Diagramm and give you the path of the created picture. \ No newline at end of file +In the final version, you will just have to execute the nassi.exe and choose your Code, it will display the Nassi-Shneiderman-Diagramm and give you the path of the created picture. From 35a5c8116edc3f0cadb2a8ac8a1c28d026824d4d Mon Sep 17 00:00:00 2001 From: weckyy702 Date: Tue, 22 Dec 2020 19:46:25 +0100 Subject: [PATCH 2/2] implemented NSD loading from files --- .vscode/launch.json | 2 +- NassiShneidermann.py | 17 ++++---- interpet_source.py | 90 ------------------------------------------- interpret_source.py | 77 ++++++++++++++++++++++++++++++++++++ res/input/input.java | 49 ++++++++++++++--------- res/output/Nina.png | Bin 3587 -> 38518 bytes 6 files changed, 119 insertions(+), 116 deletions(-) delete mode 100644 interpet_source.py create mode 100644 interpret_source.py diff --git a/.vscode/launch.json b/.vscode/launch.json index 6e0fa0a..eaba26a 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -8,7 +8,7 @@ "name": "Python: Aktuelle Datei", "type": "python", "request": "launch", - "program": "gui.py", + "program": "interpret_source.py", "console": "integratedTerminal" } ] diff --git a/NassiShneidermann.py b/NassiShneidermann.py index ac2a632..c2b1a7d 100644 --- a/NassiShneidermann.py +++ b/NassiShneidermann.py @@ -1,8 +1,7 @@ -from os import cpu_count -from code_to_image import NSD_save from Iinstruction import Iinstruction -from interpet_source import load_src, get_scoped_instructions +import interpret_source as itp import logging +from typing import List class NassiShneidermanDiagram: @@ -24,16 +23,20 @@ class NassiShneidermanDiagram: def convert_to_image(self, filename: str, x_size=200): logging.info(f"Saving NSD to {filename}.png") - cti.NSD_init(x_size, 1000) + cti.NSD_init(x_size, 5000) x, y, x_sz = 0, 0, x_size for _k, instruction in self.instructions.items(): - x, y = instruction.to_image(x, y, x_sz, 200) + x, y = instruction.to_image(x, y, x_sz, 750) cti.NSD_save(filename) def load_from_file(self, filepath:str): - source_code = load_src(filepath) - instructions = get_scoped_instructions(filepath) + src_code = itp.load_src(filepath) + global_scope = itp.get_instructions_in_scope(src_code)[0] + self.add_instructions_from_scope(global_scope) + def add_instructions_from_scope(self, scope: List[Iinstruction]): + for inst in scope: + self.add_instruction(inst) diff --git a/interpet_source.py b/interpet_source.py deleted file mode 100644 index 0dca182..0000000 --- a/interpet_source.py +++ /dev/null @@ -1,90 +0,0 @@ -from Iinstruction import * -import logging -import re -from typing import Any, List, Union - -class Scope(): - - def __init__(self, enclosing_scope) -> None: - self.enclosing_scope = enclosing_scope - self.contents: List = [] - - def add_instruction(self, instruction) -> None: - self.contents.append(instruction) - - def add_subscope(self, subscope) -> None: - self.contents.append(subscope) - -def load_src(filepath: str) -> List[str]: - lines: List[str] = [] - try: - with open(filepath) as file: - for _line in file: - line = _line.strip() - if line and not re.match(r"""^//|^#|^COMMENT|^--""", line): - lines.append(line) - except: - logging.error(f"Failed to open input file {filepath}!") - - return lines - -#TODO: remove debugging-only str -scope_contents = Union[str, Iinstruction, Scope] - -def get_scopes(src: List[str]): - global_scope = Scope(None) - current_scope = global_scope - - for line in src: - logging.debug(line) - if line.__contains__('}'): - current_scope.add_instruction("scope exit") - current_scope = current_scope.enclosing_scope - if line.__contains__('{'): - current_scope.add_instruction("scope enter") - subscope = Scope(current_scope) - current_scope.add_subscope(subscope) - current_scope = subscope - - elif not line.__contains__('}'): - current_scope.add_instruction("generic instruction") - - return global_scope - -def get_instructions(scope: Scope) -> List[scope_contents]: - instructions = [] - - for item in scope.contents: - if isinstance(item, Scope): - instructions.extend(get_instructions(item)) - else: - instructions.append(item) - - return instructions - - - -def get_scoped_instructions(filepath:str) -> List[scope_contents]: - source_code = load_src(filepath) - global_scope = get_scopes(source_code) - - instructions = get_instructions(global_scope) - return instructions - -if __name__ == "__main__": - """debuging""" - - def print_scope(scope: Scope): - print('{') - for item in scope.contents: - if isinstance(item, Scope): - print_scope(item) - else: - print(item, end=";\n") - print('}') - - logging.basicConfig(level=logging.DEBUG) - #inst = get_scoped_instructions("res/input/input.java") - lines = load_src("res/input/input.java") - global_scope = get_scopes(lines) - print_scope(global_scope) \ No newline at end of file diff --git a/interpret_source.py b/interpret_source.py new file mode 100644 index 0000000..582b10b --- /dev/null +++ b/interpret_source.py @@ -0,0 +1,77 @@ +from Iinstruction import * +import logging +import re +from typing import List, Tuple + +class Scope(): + + def __init__(self, enclosing_scope) -> None: + self.enclosing_scope = enclosing_scope + self.contents: List = [] + + def add_instruction(self, instruction: Iinstruction) -> None: + self.contents.append(instruction) + +def load_src(filepath: str) -> List[str]: + lines: List[str] = [] + brace_open_count, brace_closed_count = 0,0 + try: + with open(filepath) as file: + for _line in file: + line = _line.strip() + if line and not re.match(r"""^//|^#|^COMMENT|^--""", line): + lines.append(line) + if line.__contains__('{'): + brace_open_count += 1 + if line.__contains__('}'): + brace_closed_count += 1 + except: + logging.error(f"Failed to open input file {filepath}!") + if brace_open_count != brace_closed_count: + raise Exception("Number of opened braces does not match number of closed ones. Program is illformed!") + + return lines + +def get_instructions_in_scope(src: List[str], start_idx: int = 0) -> Tuple[List[Iinstruction], int]: + outer_scope = Scope(None) + i = start_idx + while i < len(src): + line = src[i] + try: + if line.__contains__('}'): #We exited this scope, return it + return outer_scope.contents, i + + if line.startswith("while("): + logging.debug("Found while instruction in line: %i", i+1) + bracket_idx = line.rindex(')') # throws if the while is illformed + instruction_txt = line[6:bracket_idx] + child_instructions, i = get_instructions_in_scope(src, i+1) + outer_scope.add_instruction(while_instruction_front(instruction_txt, child_instructions)) + + elif line.startswith("if("): + logging.debug("Found if instruction in line: %i", i+1) + bracket_idx = line.rindex(')') # throws if the contruct is illformed + instruction_txt = line[3:bracket_idx] + true_instructions, i = get_instructions_in_scope(src, i+1) + false_instructions = None + if src[i].__contains__("else"): #if there is an else statement, check it + false_instructions, i = get_instructions_in_scope(src, i+1) + outer_scope.add_instruction(if_instruction(instruction_txt, true_instructions, false_instructions)) + + else: + logging.debug("Found generic instruction in line: %i", i+1) + outer_scope.add_instruction(generic_instruction(line)) + except: + logging.error("Encountered error in line: %i", i) + raise # rethrow + i += 1 + return outer_scope.contents, 0 + +if __name__ == "__main__": + """debuging""" + + logging.basicConfig(level=logging.DEBUG) + #inst = get_scoped_instructions("res/input/input.java") + lines = load_src("res/input/input.java") + global_scope = get_instructions_in_scope(lines) + print(global_scope) \ No newline at end of file diff --git a/res/input/input.java b/res/input/input.java index d3a1020..c74e76e 100644 --- a/res/input/input.java +++ b/res/input/input.java @@ -1,23 +1,36 @@ -#comment -//comment -COMMENT this is a comment ---comment - -fahre(); -fahre(); - -while(shouldNiet()) -{ - niet(); - niet(); - if(true) - { - niet(); - niet(); +fahre1(); +fahre2(); +while(shouldNiet()) { + niet4(); + niet5(); + if(if6) { + niet7(); + niet8(); + } else { + niet10(); + niet11(); } + if(if13) { + niet14(); + niet15(); + if(if16) { + niet17(); + niet18(); + } else { + niet20(); + niet21(); + if(if22) { + niet23() + } + } + } else { + niet27(); + niet28(); + } + niet30(); } -niet(); -niet(); +niet32(); +niet33(); // drehe("links"); // while(huegelVorhanden("rechts")) diff --git a/res/output/Nina.png b/res/output/Nina.png index 987d273523234db0407bfad4ee6b7cf75c386756..d089719249d6c5b8a22a0e24228461a7eb1bb823 100644 GIT binary patch literal 38518 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 Cez}?e literal 3587 zcmeAS@N?(olHy`uVBq!ia0y~yVEn?s!2E)P2`IAMezpiu{FJAQV@SoEw^t5wGAIZf zFev>$_q;00LKWdJS8a{C8>W4>W@z|+Mpog(9mb|nYBUH&Q^9CP7%dA%i^I_xVYD_J qtqn(O!_nGsv^E^A4F^MQsJC5@>+Ecv*&4vk9fPN