From e2e058c8ac26729ba5929c7c4a8d7cd9363ecfad Mon Sep 17 00:00:00 2001 From: Anthony Debucquoy Date: Mon, 28 Apr 2025 15:38:42 +0200 Subject: [PATCH] Adding error handling support --- examples/errors.spf | 14 +++++++++++ modules/Variables.py | 23 ++++++++++++------ modules/errors.py | 44 ++++++++++++++++++++++++++++++++++ spf.py | 56 +++++++++++++++++++++++++++++++++++--------- 4 files changed, 119 insertions(+), 18 deletions(-) create mode 100644 examples/errors.spf create mode 100644 modules/errors.py diff --git a/examples/errors.spf b/examples/errors.spf new file mode 100644 index 0000000..b9bcb0d --- /dev/null +++ b/examples/errors.spf @@ -0,0 +1,14 @@ +blop x = blop #SyntaxError + +# afficher y; #UnknownVariable + +# texte z; +# afficher z; #UninitializedVariable + +# texte a; +# texte a; #AlreadyDefined + +# entier b = "test"; #IncompatibleType + +# liste c = [0, 1, 2]; +# afficher c[10]; #IndexError diff --git a/modules/Variables.py b/modules/Variables.py index a284c91..db9824a 100644 --- a/modules/Variables.py +++ b/modules/Variables.py @@ -1,5 +1,7 @@ import sys +from modules.errors import SPFUnknownVariable, SPFUninitializedVariable, SPFAlreadyDefined, SPFIncompatibleType, SPFIndexError + trace_format = '\033[1m -> ' reset_format = '\033[0m' @@ -11,14 +13,16 @@ class Variables: "booléen": bool, "liste": list } - def __init__(self, typ, value = None): + def __init__(self, typ, value=None): assert typ in self.types.keys(), "Ce type de variable est inconnu" self.type = typ - assert self.checkType(value, typ), f"Le type n'est pas équivalent: { value } n'est pas {typ}" - self.value = value if (value is not None) else self.default(typ) + if not self.checkType(value, typ): + raise SPFIncompatibleType(value, self.type) + self.value = value def set(self, value): - assert self.checkType(value, self.type), f"Le type n'est pas équivalent: {value} n'est pas {self.type}" + if not self.checkType(value, self.type): + raise SPFIncompatibleType(value, self.type) self.value = value def __str__(self): @@ -49,19 +53,24 @@ class Variables: self.trace = trace def get(self, name): - assert name in self.variables, f"la variable {name} n'éxiste pas" + if name not in self.variables: + raise SPFUnknownVariable(name) + if self.variables[name].value == None: + raise SPFUninitializedVariable(name) if self.trace: print(f"{trace_format}accède {name}{reset_format}", file=sys.stderr) return self.variables[name].value def declare(self, typ, name, value=None): - assert name not in self.variables, f"la variable {name} existe déjà" + if name in self.variables: + raise SPFAlreadyDefined(name) self.variables[name] = self.Variable(typ, value) if self.trace: print(f"{trace_format}déclare {name} = {value}{reset_format}", file=sys.stderr) def assign(self, name, value): - assert name in self.variables, f"la variable {name} n'éxiste pas" + if name not in self.variables: + raise SPFUnknownVariable(name) self.variables[name].set(value) if self.trace: print(f"{trace_format}modifie {name} = {value}{reset_format}", file=sys.stderr) diff --git a/modules/errors.py b/modules/errors.py new file mode 100644 index 0000000..511e14b --- /dev/null +++ b/modules/errors.py @@ -0,0 +1,44 @@ +# args +# 0) variable name +# 1) list of lines of the stack trace + +class SPFException(Exception): + def __init__(self, *args): + super().__init__(*args) + self.msg = "Une erreur est survenue" + self.errorline = None + + def __str__(self): + return (f"[ligne {self.errorline}] " if self.errorline else "") + f"{self.msg}" + +class SPFSyntaxError(SPFException): + def __init__(self, *args): + super().__init__(*args) + self.msg = "Une erreur de syntaxe est survenue" + +class SPFUnknownVariable(SPFException): + def __init__(self, *args): + super().__init__(*args) + self.msg = f"la variable `{args[0]}` n'est pas déclarée" + +class SPFUninitializedVariable(SPFException): + def __init__(self, *args): + super().__init__(*args) + self.msg = f"la variable `{args[0]}` n'est pas initialisée" + +class SPFAlreadyDefined(SPFException): + def __init__(self, *args): + super().__init__(*args) + self.msg = f"la variable `{args[0]}` est déjà déclarée" + +class SPFIncompatibleType(SPFException): + def __init__(self, *args): + super().__init__(*args) + self.msg = f"`{args[0]}` n'est pas de type `{args[1]}`" + +class SPFIndexError(SPFException): + def __init__(self, *args): + super().__init__(*args) + self.msg = f"La liste `{args[0]}` ne posède pas d'élèment d'indexe {args[1]}" + + diff --git a/spf.py b/spf.py index 18c8e74..7601c2e 100755 --- a/spf.py +++ b/spf.py @@ -8,6 +8,7 @@ import lark import sys from enum import Enum from modules.Variables import Variables +from modules.errors import * class SPFInterpreter(lark.visitors.Interpreter): def __init__(self, trace=False): @@ -24,11 +25,19 @@ class SPFInterpreter(lark.visitors.Interpreter): type = el.children[0].value name = el.children[1].value old = self.variables.variables.copy() - self.variables.declare(type, name) + try: + self.variables.declare(type, name) + except SPFException as e: + e.errorline = el.meta.line + raise e target = self.visit_children(el.children[2])[0] for i in target: - self.variables.assign(name, i) + try: + self.variables.assign(name, i) + except SPFException as e: + e.errorline = el.meta.line + raise e self.visit_children(el.children[3]) self.variables.variables = old.copy() @@ -40,20 +49,33 @@ class SPFInterpreter(lark.visitors.Interpreter): def append(self, el): (_, toadd, var) = self.visit_children(el); - var_val = self.variables.get(var.value) + try: + var_val = self.variables.get(var.value) + except SPFException as e: + e.errorline = el.meta.line + raise e + var_val.append(toadd) def declaration(self, el): type = el.children[0].value name = el.children[1].value value = self.visit_children(el.children[3])[0] if len(el.children) >= 3 else None - self.variables.declare(type, name, value) + try: + self.variables.declare(type, name, value) + except SPFException as e: + e.errorline = el.meta.line + raise e def assignation(self, el): name = el.children[0].value assert el.children[1].value == "=" and el.children[2].data == "expression", "Unexpected" value = self.visit_children(el.children[2])[0] - self.variables.assign(name, value) + try: + self.variables.assign(name, value) + except SPFException as e: + e.errorline = el.meta.line + raise e def expression(self, el): return self.visit_children(el)[0] @@ -108,7 +130,6 @@ class SPFInterpreter(lark.visitors.Interpreter): def priority(self, el): result = self.visit_children(el) - print(result) if len(result) < 2: return result[0] elif result[0].type == "SIZE_OP": @@ -120,13 +141,22 @@ class SPFInterpreter(lark.visitors.Interpreter): def list_get(self, el): result = self.visit_children(el) - return result[0][result[1] - 1] # Index start at 1 (like lua) + try: + return result[0][result[1] - 1] # Index start at 1 (like lua) + except IndexError: + e = SPFIndexError(result[0], result[1]) + e.errorline = el.meta.line + raise e def finalterm(self, el): return self.visit_children(el)[0] def variable(self, el): - return self.variables.get(el.children[0].value) + try: + return self.variables.get(el.children[0].value) + except SPFException as e: + e.errorline = el.meta.line + raise e def test(self,el): old = self.variables.variables.copy() @@ -167,11 +197,16 @@ def main(): args = arg_parser.parse_args() with open("spf.lark") as grammar: - spf_parser = lark.Lark(grammar, parser="lalr", strict=True, debug=True) + spf_parser = lark.Lark(grammar, parser="lalr", strict=True, debug=True, propagate_positions=True) with open(args.spf_file) as spf_input: program = spf_input.read() - parsed = spf_parser.parse(program) + try: + parsed = spf_parser.parse(program) + except lark.UnexpectedInput as u: + e = SPFSyntaxError() + e.errorline = u.line + raise e if args.pretty: print(parsed.pretty()) @@ -183,7 +218,6 @@ def main(): if args.dump: interpreter.dump() - if __name__ == "__main__": main()