Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion src/algolab/environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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:
Expand Down
21 changes: 21 additions & 0 deletions src/algolab/utils.py
Original file line number Diff line number Diff line change
@@ -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}' ?"
13 changes: 12 additions & 1 deletion tests/test_errors.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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)