├── Examples ├── hello-world.sch ├── newtons-method.sch └── sieve-of-eratosthenes.sch ├── README.md ├── Schlange ├── Schlange.bat ├── Schlange.py └── test.sch /Examples/hello-world.sch: -------------------------------------------------------------------------------- 1 | drucke("Grüßli Müsli! Alles cool im Pool?") -------------------------------------------------------------------------------- /Examples/newtons-method.sch: -------------------------------------------------------------------------------- 1 | importiere mathe 2 | 3 | def berechne_wurzel(n, genauigkeit=0.0001): 4 | schätzung = n / 2 # Starte mit einer Schätzung der Wurzel 5 | solange Wahr: 6 | nächste_schätzung = (schätzung + (n / schätzung)) / 2 7 | wenn abs(nächste_schätzung - schätzung) < genauigkeit: 8 | Rückkehr nächste_schätzung 9 | schätzung = nächste_schätzung 10 | 11 | # Zahl 12 | zahl = 2147483647 13 | 14 | # Berechnung der Wurzel 15 | wurzel = berechne_wurzel(zahl) 16 | 17 | # Ausgabe der Ergebnisse 18 | drucke("Die Wurzel von", zahl, "ist:", wurzel) 19 | vergleich = mathe.wurzel(zahl) 20 | drucke("Vergleich mit mathe.Wurzel:", vergleich) 21 | -------------------------------------------------------------------------------- /Examples/sieve-of-eratosthenes.sch: -------------------------------------------------------------------------------- 1 | def ist_primzahl(zahl): 2 | wenn zahl < 2: 3 | Rückkehr Falsch 4 | für teiler in reichweite(2, int(zahl ** 0.5) + 1): 5 | wenn zahl % teiler == 0: 6 | Rückkehr Falsch 7 | Rückkehr Wahr 8 | # upper bound 9 | obere_grenze = int(eingabe("Gib die obere Grenze ein: ")) 10 | 11 | # list of prime numbers 12 | primzahlen = [] 13 | 14 | # search for primes 15 | für zahl in reichweite(2, obere_grenze + 1): 16 | wenn ist_primzahl(zahl): 17 | primzahlen.append(zahl) 18 | 19 | # output 20 | drucke("Primzahlen von 2 bis", obere_grenze, ":") 21 | für i,primzahl in enumeriere(primzahlen): 22 | drucke(f"Die {i}. Primzahl ist ",primzahl) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Schlange, an interpreter for german python 2 | An alternative python interpreter that accepts german keywords 3 | ## Usage 4 | python Schlange.py test.sch 5 | or 6 | 7 | ./Schlange test.sch 8 | 9 | ## Example 10 | ![grafik](https://github.com/actopozipc/German-Python-Interpreter/assets/48481041/93bd66c2-1b2d-477d-8943-dc95d7ecc92f) 11 | 12 | ## Supported keywords and functions 13 | ### Keywords 14 | 15 | | Englisch | Deutsch | 16 | | ------------ | ------------ | 17 | | `and` | `und` | 18 | | `as` | `als` | 19 | | `assert` | `prüfe` | 20 | | `async` | `asynch` | 21 | | `await` | `erwarte` | 22 | | `break` | `brechen` | 23 | | `class` | `Klasse` | 24 | | `continue` | `fortsetze` | 25 | | `def` | `def` | 26 | | `del` | `lösche` | 27 | | `elif` | `andernfalls` | 28 | | `else` | `sonst` | 29 | | `enumerate` | `enumeriere` | 30 | | `except` | `Ausnahme`| 31 | | `False` | `Falsch` | 32 | | `finally` | `schlussendlich`| 33 | | `for` | `für` | 34 | | `from` | `von` | 35 | | `global` | `global` | 36 | | `if` | `wenn` | 37 | | `import` | `importiere` | 38 | | `in` | `in` | 39 | | `is` | `ist` | 40 | | `lambda` | `lambda` | 41 | | `None` | `Nichts` | 42 | | `nonlocal` | `nichtlokal` | 43 | | `not` | `nicht` | 44 | | `or` | `oder` | 45 | | `pass` | `passe`| 46 | | `raise` | `erhöhe` | 47 | | `return` | `Rückkehr` | 48 | | `slice` | `schneide` | 49 | | `True` | `Wahr` | 50 | | `try` | `versuche` | 51 | | `while` | `solange` | 52 | | `with` | `mit` | 53 | | `yield` | `erzeuge` | 54 | 55 | ### Exceptions 56 | | Englisch | Deutsch | 57 | | --- | --- | 58 | | `Exception` | `Ausnahme` | 59 | | `TypeError` | `Typfehler` | 60 | | `ValueError` | `Wertefehler` | 61 | | `NameError` | `Namensfehler` | 62 | | `IndexError` | `Indexfehler` | 63 | | `KeyError` | `Schlüsselfehler` | 64 | | `FileNotFoundError` | `DateiNichtGefundenFehler` | 65 | | `SyntaxError` | `Syntaxfehler` | 66 | | `IndentationError` | `Einrückungsfehler` | 67 | | `ImportError` | `Importfehler` | 68 | | `ModuleNotFoundError` | `ModulNichtGefundenFehler` | 69 | | `ZeroDivisionError` | `Nullteilungsfehler` | 70 | | `ArithmeticError` | `Rechenfehler` | 71 | | `OverflowError` | `Überlauffehler` | 72 | | `AssertionError` | `Behauptungsfehler` | 73 | | `AttributeError` | `Attributfehler` | 74 | | `RuntimeError` | `Laufzeitfehler` | 75 | | `KeyError` | `Schlüsselfehler` | 76 | | `StopIteration` | `IterationStoppen` | 77 | | `PermissionError` | `Berechtigungsfehler` | 78 | | `TypeError` | `Typfehler` | 79 | ### Built-in Functions 80 | | Englisch | Deutsch | 81 | |--------------|--------------| 82 | | all | alle | 83 | | any | irgendein | 84 | | breakpoint | brechpunkt | 85 | | callable | aufrufbar | 86 | | compile | kompiliere | 87 | | complex | komplex | 88 | | delattr | löschattr | 89 | | enumerate | enumerate (missing) | 90 | | getattr | bekommeattr | 91 | | globals | globale | 92 | | hasattr | hatattr | 93 | | help | hilfe | 94 | | input | eingabe | 95 | | isinstance | istinstanz | 96 | | issubclass | istsubklasse | 97 | | len | län | 98 | | list | liste | 99 | | locals | lokale | 100 | | map | karte | 101 | | next | nächstes | 102 | | object | objekt | 103 | | open | öffne | 104 | | property | eigenschaft | 105 | | range | reichweite | 106 | | reversed | rückwärts | 107 | | round | runde | 108 | | setattr | setzattr | 109 | | sorted | sortiere | 110 | | staticmethod | statischemethode | 111 | | slice | slice (missing) | 112 | | tuple | tupel | 113 | | type | typ | 114 | | print | drucke | 115 | | math | mathe | 116 | | sqrt | wurzel | 117 | 118 | 119 | 120 | ## TODO 121 | * Intellisense support 122 | * More function names (numpy, matplotlib) 123 | * Adding missing translations (enumerate, slice) 124 | * More german keywords (see discussions) 125 | -------------------------------------------------------------------------------- /Schlange: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | python3 Schlange.py $1 -------------------------------------------------------------------------------- /Schlange.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | python Schlange.py %1 -------------------------------------------------------------------------------- /Schlange.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import re 3 | import os 4 | import sys 5 | import importlib 6 | import types 7 | ''' 8 | Naming convention: 9 | Keywords: Written like in german (e.g. Klasse = class) besides Wahr and Falsch (to ensemble pythons True and False) 10 | Exceptions: PascalCase (e.g TypFehler for TypeError) 11 | Classes: snakeCase 12 | (built in) Functions: lowercase (e.g drucke for print) 13 | 14 | ''' 15 | class KeywordTranslator: 16 | translations = { 17 | 'und': 'and', 18 | 'als': 'as', 19 | 'prüfe': 'assert', 20 | 'asynch': 'async', 21 | 'erwarte': 'await', 22 | 'breche': 'break', 23 | 'Klasse': 'class', 24 | 'fortsetze': 'continue', 25 | 'def': 'def', 26 | 'lösche': 'del', 27 | 'andernfalls': 'elif', 28 | 'sonst': 'else', 29 | "enumeriere": "enumerate", 30 | 'Ausnahme': 'except', 31 | 'Falsch': 'False', 32 | 'schlussendlich': 'finally', 33 | 'für': 'for', 34 | 'von': 'from', 35 | 'global': 'global', 36 | 'wenn': 'if', 37 | 'importiere': 'import', 38 | 'in': 'in', 39 | 'ist': 'is', 40 | 'lambda': 'lambda', 41 | 'Nichts': 'None', 42 | 'nichtlokal': 'nonlocal', 43 | 'nicht': 'not', 44 | 'oder': 'or', 45 | 'passe': 'pass', 46 | 'erhöhe': 'raise', 47 | 'Rückkehr': 'return', 48 | 'Wahr': 'True', 49 | 'versuche': 'try', 50 | 'solange': 'while', 51 | 'mit': 'with', 52 | 'erzeuge': 'yield', 53 | "selbst": "self", 54 | "schneide": "slice" 55 | } 56 | 57 | translation_exceptions = { 58 | 'Ausnahme': 'Exception', 59 | 'TypFehler': 'TypeError', 60 | 'WerteFehler': 'ValueError', 61 | 'NamensFehler': 'NameError', 62 | 'IndexFehler': 'IndexError', 63 | 'SchlüsselFehler': 'KeyError', 64 | 'SyntaxFehler': 'SyntaxError', 65 | 'EinrückungsFehler': 'IndentationError', 66 | 'DateiNichtGefundenFehler': 'FileNotFoundError', 67 | 'NullteilungsFehler': 'ZeroDivisionError', 68 | 'überlaufFehler': 'OverflowError', 69 | 'ImportFehler': 'ImportError', 70 | 'ModulNichtGefundenFehler': 'ModuleNotFoundError', 71 | 'AttributFehler': 'AttributeError', 72 | 'BehauptungsFehler': 'AssertionError', 73 | 'LaufzeitFehler': 'RuntimeError', 74 | 'IterationStoppen': 'StopIteration', 75 | 'TastaturUnterbrechung': 'KeyboardInterrupt', 76 | } 77 | 78 | translation_functions = { 79 | "alle": "all", 80 | "irgendein": "any", 81 | "brechpunkt": "breakpoint", 82 | "aufrufbar": "callable", 83 | "kompiliere": "compile", 84 | "komplex": "complex", 85 | "löschattr": "delattr", 86 | "enumerate": "enumerate", 87 | "bekommeattr": "getattr", 88 | "globale": "globals", 89 | "hatattr": "hasattr", 90 | "hilfe": "help", 91 | "eingabe": "input", 92 | "istinstanz": "isinstance", 93 | "istsubklasse": "issubclass", 94 | "län": "len", 95 | "liste": "list", 96 | "lokale": "locals", 97 | "karte": "map", 98 | "nächstes": "next", 99 | 'objekt': 'object', 100 | "öffne": "open", 101 | "eigenschaft": "property", 102 | "reichweite": "range", 103 | "rückwärts": "reversed", 104 | "runde": "round", 105 | "setzattr": "setattr", 106 | "sortiere": "sorted", 107 | "statischemethode": "staticmethod", 108 | "slice": "slice", 109 | "tupel": "tuple", 110 | "typ": "type", 111 | "drucke": "print", 112 | "mathe": "math", 113 | "wurzel": "sqrt" 114 | } 115 | translation_array_methods = { 116 | "anhängen": "append", 117 | "aufräumen": "clear", 118 | "kopiere": "copy", 119 | "zähle": "count", 120 | "erweitere": "extend", 121 | "index": "index", 122 | "einsetzen": "insert", 123 | "platze": "pop", 124 | "entferne": "remove", 125 | "umdrehen": "reverse", 126 | "sortiere": "sort" 127 | } 128 | 129 | 130 | exception_translations = dict((val, key) for key, val in translation_exceptions.items()) 131 | 132 | errors_with_placeholders = { 133 | 'unsupported operand type': 'nicht unterstützter Operandentyp', 134 | 'division by zero': 'Division durch Null', 135 | 'name \'{}\' is not defined': 'Name \'{}\' ist nicht definiert', 136 | 'list index out of range': 'Listenindex außerhalb des gültigen Bereichs', 137 | 'tuple index out of range': 'Tupelindex außerhalb des gültigen Bereichs', 138 | 'invalid syntax': 'Ungültige Syntax', 139 | 'indentation error': 'Einrückungsfehler', 140 | 'file not found': 'Datei nicht gefunden', 141 | 'attribute \'{}\' not found': 'Attribut \'{}\' nicht gefunden', 142 | 'module \'{}\' not found': 'Modul \'{}\' nicht gefunden', 143 | 'division or modulo by zero': 'Division oder Modulo durch Null', 144 | 'key error': 'Schlüsselfehler', 145 | 'invalid literal for int() with base {}: \'{}\'': 'Ungültiges Literal für int() mit Basis {}: \'{}\'', 146 | 'unexpected indent': 'Unerwartete Einrückung', 147 | 'not a valid identifier': 'Kein gültiger Bezeichner', 148 | 'invalid syntax, unexpected {}: \'{}\'': 'Ungültige Syntax, unerwartetes {}: \'{}\'', 149 | 'unsupported operand type(s) for {}: \'{}\' and \'{}\'': 'Nicht unterstützte Operandentypen für {}: \'{}\' und \'{}\'', 150 | 'No module named \'{}\' ' : 'Kein Modul namens \'{}\'' 151 | } 152 | 153 | errors_without_placeholders = { 154 | 'unsupported operand type': 'nicht unterstützter Operandentyp', 155 | 'division by zero': 'Division durch Null', 156 | 'name \'\' is not defined': 'Name \'\' ist nicht definiert', 157 | 'list index out of range': 'Listenindex außerhalb des gültigen Bereichs', 158 | 'tuple index out of range': 'Tupelindex außerhalb des gültigen Bereichs', 159 | 'invalid syntax': 'Ungültige Syntax', 160 | 'indentation error': 'Einrückungsfehler', 161 | 'file not found': 'Datei nicht gefunden', 162 | 'attribute \'\' not found': 'Attribut \'{}\' nicht gefunden', 163 | 'module \'\' not found': 'Modul \'\' nicht gefunden', 164 | 'division or modulo by zero': 'Division oder Modulo durch Null', 165 | 'key error': 'Schlüsselfehler', 166 | 'invalid literal for int() with base {}: \'\'': 'Ungültiges Literal für int() mit Basis {}: \'\'', 167 | 'unexpected indent': 'Unerwartete Einrückung', 168 | 'not a valid identifier': 'Kein gültiger Bezeichner', 169 | 'invalid syntax, unexpected {}: \'\'': 'Ungültige Syntax, unerwartetes {}: \'\'', 170 | 'unsupported operand type(s) for {}: \'\' and \'\'': 'Nicht unterstützte Operandentypen für {}: \'\' und \'\'', 171 | 'No module named \'{}\' ' : 'Kein Modul namens \'{}\'' 172 | } 173 | 174 | def extract_operators(self, error_message): 175 | pattern = r'\'(.*?)\'|//?|/' 176 | operators = re.findall(pattern, error_message) 177 | operators = [op for op in operators if op != ''] 178 | return operators 179 | 180 | def remove_operators(self, string, operators): 181 | for operator in operators: 182 | pattern = re.escape(operator) 183 | string = re.sub(pattern, '', string) 184 | return string 185 | 186 | def translate_exception(self, exception): 187 | exception_type = type(exception).__name__ 188 | translated_type = self.exception_translations.get(str(exception_type), str(exception_type)) 189 | translated_output = None 190 | for trans in self.errors_with_placeholders: 191 | try: 192 | operators = self.extract_operators(str(exception)) 193 | t = trans.format(*self.extract_operators(str(exception))) 194 | if t == str(exception): 195 | e = self.remove_operators(t, operators) 196 | translated_output = self.errors_without_placeholders.get(e) 197 | for op in operators: 198 | translated_output = translated_output.replace("''", op) 199 | break 200 | except Exception: 201 | translated_output = self.errors_with_placeholders.get(str(exception)) 202 | 203 | if translated_output is None: 204 | translated_output = str(exception) 205 | return translated_output 206 | 207 | def __init__(self, file_path): 208 | self.file_path = file_path 209 | self.module_registry = {} 210 | with open(file_path, 'r', encoding='utf-8') as f: 211 | self.code = f.read() 212 | 213 | def split_formmated(self, code: str) -> str: 214 | string_pattern = re.compile(r'(f)(\"(.*?)\"|\'(.*?)\')') 215 | any_formatted_pattern = re.compile(r'{(.*?)}') 216 | 217 | strings = string_pattern.findall(code) 218 | original_strings = [string[1] for string in strings] 219 | 220 | for i, string in enumerate(strings): 221 | content = string[1] 222 | format_indicator = string[0] 223 | quark_type = content[0] 224 | 225 | if format_indicator: 226 | formatted = any_formatted_pattern.findall(content) 227 | formatted_fitted = [ 228 | f'{quark_type} + str({i}) + {quark_type}' for i in formatted] 229 | 230 | for original, fitted in zip(formatted, formatted_fitted): 231 | content = content.replace(f"{{{original}}}", fitted) 232 | 233 | strings[i] = content 234 | 235 | for original, split in zip(original_strings, strings): 236 | code = code.replace(original, split) 237 | 238 | return code 239 | def translate(self, part: str) -> str: 240 | for german_keyword, english_keyword in self.translations.items(): 241 | part = re.sub(r'\b{}\b'.format(german_keyword), english_keyword, part) 242 | for german_keyword, english_keyword in self.translation_exceptions.items(): 243 | part = re.sub(r'\b{}\b'.format(german_keyword), english_keyword, part) 244 | for german_keyword, english_keyword in self.translation_functions.items(): 245 | part = re.sub(r'\b{}\b'.format(german_keyword), english_keyword, part) 246 | for german_keyword, english_keyword in self.translation_array_methods.items(): 247 | part = re.sub(r'\b{}\b'.format(german_keyword), english_keyword, part) 248 | return part 249 | def translate_code(self): 250 | translated_code = self.split_formmated(self.code) 251 | for german_keyword, english_keyword in self.translations.items(): 252 | translated_code = re.sub(r'\b{}\b'.format(german_keyword), english_keyword, translated_code) 253 | for german_keyword, english_keyword in self.translation_exceptions.items(): 254 | translated_code = re.sub(r'\b{}\b'.format(german_keyword), english_keyword, translated_code) 255 | for german_keyword, english_keyword in self.translation_functions.items(): 256 | translated_code = re.sub(r'\b{}\b'.format(german_keyword), english_keyword, translated_code) 257 | for german_keyword, english_keyword in self.translation_array_methods.items(): 258 | translated_code = re.sub(r'\b{}\b'.format(german_keyword), english_keyword, translated_code) 259 | self.translated_code = translated_code 260 | 261 | def extract_imports(self): 262 | imports = re.findall(r'import\s+([a-zA-Z0-9_]+)', self.translated_code) 263 | return imports 264 | 265 | def dynamic_import(self, module_name): 266 | try: 267 | if module_name in self.module_registry: 268 | return self.module_registry[module_name] 269 | module_code = self.load_module_code(module_name) 270 | translated_module_code = self.translate(module_code) 271 | module = self.execute_module(module_name, translated_module_code) 272 | self.module_registry[module_name] = module 273 | return module 274 | except ImportError as e: 275 | pass #Exception will be handled by the other try except anyway 276 | 277 | def load_module_code(self, module_name): 278 | try: 279 | directory_path = self.file_path.split('/')[0] 280 | with open(f"{directory_path}/{module_name}.sch", "r", encoding='utf-8') as f: 281 | return f.read() 282 | except FileNotFoundError: 283 | raise ImportError(f"Modul {module_name} nicht gefunden ") 284 | 285 | def execute_module(self, module_name, code): 286 | module = types.ModuleType(module_name) 287 | exec(code, module.__dict__) 288 | sys.modules[module_name] = module 289 | return module 290 | 291 | def execute(self): 292 | exec(self.translated_code, globals()) 293 | 294 | 295 | 296 | 297 | if __name__ == '__main__': 298 | parser = argparse.ArgumentParser(description='Führt eine Schlange-Datei aus') 299 | parser.add_argument('file', type=str, help='Pfad zu der Schlange-Datei') 300 | args = parser.parse_args() 301 | if not args.file.endswith('.sch'): 302 | print('Dateiendung muss .sch sein!') 303 | exit(-1) 304 | 305 | if not os.path.exists(args.file): 306 | print('Datei existiert nicht!') 307 | exit(-1) 308 | translator = KeywordTranslator(args.file) 309 | translator.translate_code() 310 | imports = translator.extract_imports() 311 | for module in imports: 312 | translator.dynamic_import(module) 313 | try: 314 | translator.execute() 315 | except Exception as e: 316 | # Übersetze die Exception, wenn sie im Dictionary vorhanden ist 317 | 318 | translated_message = translator.translate_exception(e) 319 | print(f'Fehler aufgetreten: {translated_message}') 320 | -------------------------------------------------------------------------------- /test.sch: -------------------------------------------------------------------------------- 1 | importiere numpy als np 2 | von numpy importiere wurzel 3 | Klasse Person: 4 | def __init__(selbst, name, alter): 5 | selbst.name = name 6 | selbst.alter = alter 7 | 8 | def gruesse(selbst): 9 | drucke(f'Hallo, ich bin {selbst.name} und {selbst.alter} Jahre alt.') 10 | 11 | p = Person('Max', 25) 12 | p.gruesse() 13 | 14 | zahl = 10 15 | solange zahl > 0: 16 | wenn zahl % 2 == 0: 17 | drucke(f'{zahl} ist gerade.') 18 | sonst: 19 | drucke(f'{zahl} ist ungerade.') 20 | zahl -= 1 21 | 22 | liste = [1, 2, 3, 4, 5, 7, 8] 23 | für element in liste: 24 | wenn element == 1: 25 | fortsetze 26 | andernfalls element == 7: 27 | breche 28 | drucke(f'Die Wurzel von {element} ist {np.wurzel(element)}.') 29 | 30 | versuche: 31 | 1 / 0 32 | Ausnahme NullteilungsFehler als e: 33 | drucke(f'Division durch Null: {e}.') 34 | schlussendlich: 35 | drucke('Das Programm ist fertig') 36 | --------------------------------------------------------------------------------