diff --git a/src/algolab/environment.py b/src/algolab/environment.py index 29c1f7f..5acb3b0 100644 --- a/src/algolab/environment.py +++ b/src/algolab/environment.py @@ -6,6 +6,7 @@ from typing import Any, Optional from .errors import RuntimeErrorAlgoLab, SemanticErrorAlgoLab +from .utils import build_suggestion_string, suggest_var @dataclass(frozen=True) @@ -88,7 +89,9 @@ def _resolve(self, name: str) -> Variable: return self._values[name] if self._parent is not None: return self._parent._resolve(name) - raise SemanticErrorAlgoLab(f"Variable non declaree: {name}") + suggestion = suggest_var(name, self._values.keys()) + suggestion_message = build_suggestion_string(suggestion) + raise SemanticErrorAlgoLab(f"Variable non declaree: {name}.{suggestion_message}") def _coerce_value(self, type_spec: TypeSpec, value: Any) -> Any: if type_spec.is_array: diff --git a/src/algolab/utils.py b/src/algolab/utils.py new file mode 100644 index 0000000..8377ccb --- /dev/null +++ b/src/algolab/utils.py @@ -0,0 +1,21 @@ +""" Utility functions for AlgoLab.""" + +from difflib import SequenceMatcher, get_close_matches + + +def suggest_var(typo: str, names: list[str]) -> str: + if not names: + return "" + + matches = get_close_matches(typo, names, n=1) + if matches: + return matches[0] + + ranked = sorted(names, key=lambda n: SequenceMatcher(None, typo, n).ratio(), reverse=True) + return ranked[0] + + +def build_suggestion_string(suggestion: str): + if not suggestion: + return "" + return f" Vouliez-vous dire '{suggestion}' ?" diff --git a/tests/test_errors.py b/tests/test_errors.py index a18336f..2af77f1 100644 --- a/tests/test_errors.py +++ b/tests/test_errors.py @@ -1,7 +1,6 @@ from __future__ import annotations import pytest - from algolab.errors import SemanticErrorAlgoLab, SyntaxErrorAlgoLab from algolab.interpreter import Interpreter from algolab.parser import parse_source @@ -36,3 +35,15 @@ def test_semantic_error_on_undeclared_variable() -> None: with pytest.raises(SemanticErrorAlgoLab) as exc_info: Interpreter().run(source) assert "non declaree" in str(exc_info.value) + + +def test_semantic_error_on_typo_variable() -> None: + source = """ + Variable x : Entier + Debut + y <- 1 + Fin + """.strip() + with pytest.raises(SemanticErrorAlgoLab) as exc_info: + Interpreter().run(source) + assert "Vouliez-vous dire 'x' ?" in str(exc_info.value)