From 7df8f0da6b5170864eaffb0a20676a087c48614a Mon Sep 17 00:00:00 2001 From: tibvdm Date: Tue, 1 Oct 2024 08:08:30 +0200 Subject: [PATCH 1/5] Allow Heterogenous arguments in Haskell to allow Ambiguous variable judging --- tested/languages/haskell/config.py | 1 + tested/manual.py | 6 +-- .../haskell/evaluation/voorlaatste.yaml | 43 +++++++++++++++++++ .../exercises/haskell/solution/voorlaatste.hs | 2 + 4 files changed, 49 insertions(+), 3 deletions(-) create mode 100644 tests/exercises/haskell/evaluation/voorlaatste.yaml create mode 100644 tests/exercises/haskell/solution/voorlaatste.hs diff --git a/tested/languages/haskell/config.py b/tested/languages/haskell/config.py index 7b45168d9..1ec48433c 100644 --- a/tested/languages/haskell/config.py +++ b/tested/languages/haskell/config.py @@ -79,6 +79,7 @@ def supported_constructs(self) -> set[Construct]: Construct.ASSIGNMENTS, Construct.EVALUATION, Construct.GLOBAL_VARIABLES, + Construct.HETEROGENEOUS_ARGUMENTS } def compilation(self, files: list[str]) -> CallbackResult: diff --git a/tested/manual.py b/tested/manual.py index eab988a1a..7a355cb9c 100644 --- a/tested/manual.py +++ b/tested/manual.py @@ -13,7 +13,7 @@ from tested.main import run from tested.testsuite import SupportedLanguage -exercise_dir = "/home/niko/Ontwikkeling/universal-judge/tests/exercises/echo-function" +exercise_dir = "/Users/tibvdm/PycharmProjects/universal-judge/tests/exercises/haskell" def read_config() -> DodonaConfig: @@ -24,10 +24,10 @@ def read_config() -> DodonaConfig: programming_language=SupportedLanguage("haskell"), natural_language="nl", resources=Path(exercise_dir, "evaluation"), - source=Path(exercise_dir, "solution/correct.hs"), + source=Path(exercise_dir, "solution/voorlaatste.hs"), judge=Path("."), workdir=Path("workdir"), - test_suite="two-specific.tson", + test_suite="voorlaatste.yaml", options=Options( linter=False, ), diff --git a/tests/exercises/haskell/evaluation/voorlaatste.yaml b/tests/exercises/haskell/evaluation/voorlaatste.yaml new file mode 100644 index 000000000..38c362bb5 --- /dev/null +++ b/tests/exercises/haskell/evaluation/voorlaatste.yaml @@ -0,0 +1,43 @@ +tabs: + - tab: 'voorlaatste (Int)' + testcases: + - expression: voorlaatste([1, 2]) + return: 1 + - expression: voorlaatste([1, 2, 3]) + return: 2 + - expression: voorlaatste([1, 2, 3, 4]) + return: 3 + - expression: voorlaatste([1, 2, 3, 4, 5]) + return: 4 + - expression: voorlaatste([1, 0, 1, 0, 1]) + return: 0 + - expression: voorlaatste([9, 81, 1, 2, 4, 1, 42, 1]) + return: 42 + - tab: 'voorlaatste (Double)' + testcases: + - expression: voorlaatste([1.0, 2.0]) + return: 1.0 + - expression: voorlaatste([1.0, 2.0, 3.0]) + return: 2.0 + - expression: voorlaatste([1.0, 2.0, 3.0, 4.0]) + return: 3.0 + - expression: voorlaatste([1.0, 2.0, 3.0, 4.0, 5.0]) + return: 4.0 + - expression: voorlaatste([1.0, 0.0, 1.0, 0.0, 1.0]) + return: 0.0 + - expression: voorlaatste([9.0, 81.0, 1.0, 2.0, 4.0, 1.0, 42.0, 1.0]) + return: 42.0 + - tab: 'voorlaatste (Char)' + testcases: + - expression: voorlaatste(['a', 'b']) + return: 'a' + - expression: voorlaatste(['a', 'b', 'c']) + return: 'b' + - expression: voorlaatste(['a', 'b', 'c', 'd']) + return: 'c' + - expression: voorlaatste(['a', 'b', 'c', 'd', 'e']) + return: 'd' + - expression: voorlaatste(['a', 'b', 'a', 'b', 'a']) + return: 'b' + - expression: voorlaatste(['c', 'd', 'a', 'b', 'g']) + return: 'b' diff --git a/tests/exercises/haskell/solution/voorlaatste.hs b/tests/exercises/haskell/solution/voorlaatste.hs new file mode 100644 index 000000000..3ea94e054 --- /dev/null +++ b/tests/exercises/haskell/solution/voorlaatste.hs @@ -0,0 +1,2 @@ +voorlaatste :: [a] -> a +voorlaatste = last . init From ae51fae3e3df44b866502a17797bdf580e183a44 Mon Sep 17 00:00:00 2001 From: tibvdm Date: Wed, 2 Oct 2024 13:31:56 +0200 Subject: [PATCH 2/5] add parentheses to fix explicit typing + remove explicit typing in feedback --- .gitignore | 1 + tested/languages/haskell/config.py | 12 ++++++++--- tested/languages/haskell/generators.py | 21 +++++++++---------- tested/manual.py | 4 ++-- .../haskell/evaluation/ndeElement.yaml | 16 ++++++++++++++ .../exercises/haskell/solution/ndeElement.hs | 2 ++ 6 files changed, 40 insertions(+), 16 deletions(-) create mode 100644 tests/exercises/haskell/evaluation/ndeElement.yaml create mode 100644 tests/exercises/haskell/solution/ndeElement.hs diff --git a/.gitignore b/.gitignore index fca52d7cb..06e07f0bb 100644 --- a/.gitignore +++ b/.gitignore @@ -82,3 +82,4 @@ venv-*/ .venv/ node_modules/ result/ +.data/ diff --git a/tested/languages/haskell/config.py b/tested/languages/haskell/config.py index 1ec48433c..30cae8968 100644 --- a/tested/languages/haskell/config.py +++ b/tested/languages/haskell/config.py @@ -1,6 +1,6 @@ import re from pathlib import Path -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Optional from tested.datatypes import AllTypes from tested.dodona import AnnotateCode, Message @@ -28,6 +28,10 @@ class Haskell(Language): + def __init__(self, config: Optional["GlobalConfig"]): + super().__init__(config) + self.clean_types_regex = re.compile(r"\(([^:\s]*)\s*::\s*([A-Z][a-zA-Z0-9]*)\)") + def initial_dependencies(self) -> list[str]: return ["Values.hs", "EvaluationUtils.hs"] @@ -111,7 +115,7 @@ def linter(self, remaining: float) -> tuple[list[Message], list[AnnotateCode]]: return linter.run_hlint(self.config.dodona, remaining) def cleanup_description(self, statement: str) -> str: - return cleanup_description(self, statement) + return cleanup_description(self, self.clean_types_regex.sub(r'\1', statement)) def cleanup_stacktrace(self, stacktrace: str) -> str: filename = submission_file(self) @@ -165,11 +169,13 @@ def cleanup_stacktrace(self, stacktrace: str) -> str: def generate_statement(self, statement: Statement) -> str: from tested.languages.haskell import generators - return generators.convert_statement(statement) + return self.clean_types_regex.sub(r'\1', generators.convert_statement(statement)) def generate_execution_unit(self, execution_unit: "PreparedExecutionUnit") -> str: from tested.languages.haskell import generators + print(generators.convert_execution_unit(execution_unit)) + return generators.convert_execution_unit(execution_unit) def generate_selector(self, contexts: list[str]) -> str: diff --git a/tested/languages/haskell/generators.py b/tested/languages/haskell/generators.py index c2897db7b..c6d8df9b8 100644 --- a/tested/languages/haskell/generators.py +++ b/tested/languages/haskell/generators.py @@ -40,7 +40,6 @@ def convert_arguments(arguments: list[Expression]) -> str: return ", ".join(convert_statement(arg) for arg in arguments) - def convert_value(value: Value) -> str: # Handle some advanced types. if value.type == AdvancedSequenceTypes.TUPLE: @@ -48,37 +47,37 @@ def convert_value(value: Value) -> str: return f"({convert_arguments(value.data)})" elif isinstance(value.type, AdvancedNumericTypes): if not isinstance(value.data, SpecialNumbers): - return f"{value.data} :: {convert_declaration(value.type)}" + return f"({value.data} :: {convert_declaration(value.type)})" elif value.data == SpecialNumbers.NOT_A_NUMBER: - return f"(0/0) :: {convert_declaration(value.type)}" + return f"((0/0) :: {convert_declaration(value.type)})" elif value.data == SpecialNumbers.POS_INFINITY: - return f"(1/0) :: {convert_declaration(value.type)}" + return f"((1/0) :: {convert_declaration(value.type)})" else: assert value.data == SpecialNumbers.NEG_INFINITY - return f"(-1/0) :: {convert_declaration(value.type)}" + return f"((-1/0) :: {convert_declaration(value.type)})" elif value.type == AdvancedStringTypes.CHAR: assert isinstance(value, StringType) return "'" + value.data.replace("'", "\\'") + "'" # Handle basic types value = as_basic_type(value) if value.type == BasicNumericTypes.INTEGER: - return f"{value.data} :: Int" + return f"({value.data} :: Int)" elif value.type == BasicNumericTypes.REAL: if not isinstance(value.data, SpecialNumbers): - return f"{value.data} :: Double" + return f"({value.data} :: Double)" elif value.data == SpecialNumbers.NOT_A_NUMBER: - return "(0/0) :: Double" + return "((0/0) :: Double)" elif value.data == SpecialNumbers.POS_INFINITY: - return "(1/0) :: Double" + return "((1/0) :: Double)" else: assert SpecialNumbers.NEG_INFINITY - return "(-1/0) :: Double" + return "((-1/0) :: Double)" elif value.type == BasicStringTypes.TEXT: return json.dumps(value.data) elif value.type == BasicBooleanTypes.BOOLEAN: return str(value.data) elif value.type == BasicNothingTypes.NOTHING: - return "Nothing :: Maybe Integer" + return "(Nothing :: Maybe Integer)" elif value.type == BasicSequenceTypes.SEQUENCE: assert isinstance(value, SequenceType) return f"[{convert_arguments(value.data)}]" diff --git a/tested/manual.py b/tested/manual.py index 7a355cb9c..d7d64e6dd 100644 --- a/tested/manual.py +++ b/tested/manual.py @@ -24,10 +24,10 @@ def read_config() -> DodonaConfig: programming_language=SupportedLanguage("haskell"), natural_language="nl", resources=Path(exercise_dir, "evaluation"), - source=Path(exercise_dir, "solution/voorlaatste.hs"), + source=Path(exercise_dir, "solution/ndeElement.hs"), judge=Path("."), workdir=Path("workdir"), - test_suite="voorlaatste.yaml", + test_suite="ndeElement.yaml", options=Options( linter=False, ), diff --git a/tests/exercises/haskell/evaluation/ndeElement.yaml b/tests/exercises/haskell/evaluation/ndeElement.yaml new file mode 100644 index 000000000..7a045c3fd --- /dev/null +++ b/tests/exercises/haskell/evaluation/ndeElement.yaml @@ -0,0 +1,16 @@ +tabs: + - tab: geefNde + testcases: + - expression: geefNde(0, [1, 2, 3, 4]) + description: + description: geefNde 0 [1, 2, 3, 4] + format: haskell + return: 1 + - expression: geefNde(0, [1.0, 2.0, 3.0, 4.0]) + return: 1.0 + - expression: geefNde(0, ['a', 'b', 'c', 'd']) + return: a + - expression: geefNde(1, [(1, 2), (3, 4), (5, 6), (7, 8)]) + return: !tuple [3, 4] + - expression: geefNde(1, [(1.0, 2.0), (3.0, 4.0), (5.0, 6.0), (7.0, 8.0)]) + return: !tuple [3.0, 4.0] diff --git a/tests/exercises/haskell/solution/ndeElement.hs b/tests/exercises/haskell/solution/ndeElement.hs new file mode 100644 index 000000000..f7072810f --- /dev/null +++ b/tests/exercises/haskell/solution/ndeElement.hs @@ -0,0 +1,2 @@ +geefNde :: Int -> [a] -> a +geefNde n l = l !! n From 41bade55fe02bfa37654dcce1af3d64fe9e11552 Mon Sep 17 00:00:00 2001 From: tibvdm Date: Wed, 2 Oct 2024 14:20:09 +0200 Subject: [PATCH 3/5] fix linting and typing errors --- tested/languages/haskell/config.py | 9 ++++++--- tested/languages/haskell/generators.py | 1 + 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/tested/languages/haskell/config.py b/tested/languages/haskell/config.py index 30cae8968..503f0ec0f 100644 --- a/tested/languages/haskell/config.py +++ b/tested/languages/haskell/config.py @@ -2,6 +2,7 @@ from pathlib import Path from typing import TYPE_CHECKING, Optional +from tested.configs import GlobalConfig from tested.datatypes import AllTypes from tested.dodona import AnnotateCode, Message from tested.features import Construct, TypeSupport @@ -83,7 +84,7 @@ def supported_constructs(self) -> set[Construct]: Construct.ASSIGNMENTS, Construct.EVALUATION, Construct.GLOBAL_VARIABLES, - Construct.HETEROGENEOUS_ARGUMENTS + Construct.HETEROGENEOUS_ARGUMENTS, } def compilation(self, files: list[str]) -> CallbackResult: @@ -115,7 +116,7 @@ def linter(self, remaining: float) -> tuple[list[Message], list[AnnotateCode]]: return linter.run_hlint(self.config.dodona, remaining) def cleanup_description(self, statement: str) -> str: - return cleanup_description(self, self.clean_types_regex.sub(r'\1', statement)) + return cleanup_description(self, self.clean_types_regex.sub(r"\1", statement)) def cleanup_stacktrace(self, stacktrace: str) -> str: filename = submission_file(self) @@ -169,7 +170,9 @@ def cleanup_stacktrace(self, stacktrace: str) -> str: def generate_statement(self, statement: Statement) -> str: from tested.languages.haskell import generators - return self.clean_types_regex.sub(r'\1', generators.convert_statement(statement)) + return self.clean_types_regex.sub( + r"\1", generators.convert_statement(statement) + ) def generate_execution_unit(self, execution_unit: "PreparedExecutionUnit") -> str: from tested.languages.haskell import generators diff --git a/tested/languages/haskell/generators.py b/tested/languages/haskell/generators.py index c6d8df9b8..672b5a632 100644 --- a/tested/languages/haskell/generators.py +++ b/tested/languages/haskell/generators.py @@ -40,6 +40,7 @@ def convert_arguments(arguments: list[Expression]) -> str: return ", ".join(convert_statement(arg) for arg in arguments) + def convert_value(value: Value) -> str: # Handle some advanced types. if value.type == AdvancedSequenceTypes.TUPLE: From b33c1d32dc19e442e7b35ee7bf0eaa5ac83959b8 Mon Sep 17 00:00:00 2001 From: tibvdm Date: Wed, 2 Oct 2024 14:35:17 +0200 Subject: [PATCH 4/5] remove print statement from config + fix haskell test --- tested/languages/haskell/config.py | 2 - .../haskell/evaluation/ndeElement.yaml | 58 +++++++++++++++---- tests/test_language_quircks.py | 2 +- 3 files changed, 48 insertions(+), 14 deletions(-) diff --git a/tested/languages/haskell/config.py b/tested/languages/haskell/config.py index 503f0ec0f..fdf9f5af6 100644 --- a/tested/languages/haskell/config.py +++ b/tested/languages/haskell/config.py @@ -177,8 +177,6 @@ def generate_statement(self, statement: Statement) -> str: def generate_execution_unit(self, execution_unit: "PreparedExecutionUnit") -> str: from tested.languages.haskell import generators - print(generators.convert_execution_unit(execution_unit)) - return generators.convert_execution_unit(execution_unit) def generate_selector(self, contexts: list[str]) -> str: diff --git a/tests/exercises/haskell/evaluation/ndeElement.yaml b/tests/exercises/haskell/evaluation/ndeElement.yaml index 7a045c3fd..b4ec836f2 100644 --- a/tests/exercises/haskell/evaluation/ndeElement.yaml +++ b/tests/exercises/haskell/evaluation/ndeElement.yaml @@ -1,16 +1,52 @@ tabs: - - tab: geefNde + - tab: geefNde (Int) testcases: - - expression: geefNde(0, [1, 2, 3, 4]) - description: - description: geefNde 0 [1, 2, 3, 4] - format: haskell + - expression: geefNde(0, [1, 2, 3, 4, 5]) return: 1 - - expression: geefNde(0, [1.0, 2.0, 3.0, 4.0]) + - expression: geefNde(1, [1, 2, 3, 4, 5]) + return: 2 + - expression: geefNde(2, [1, 2, 3, 4, 5]) + return: 3 + - expression: geefNde(3, [1, 2, 3, 4, 5]) + return: 4 + - expression: geefNde(4, [1, 2, 3, 4, 5]) + return: 5 + + - tab: geefNde (Double) + testcases: + - expression: geefNde(0, [1.0, 2.0, 3.0, 4.0, 5.0]) return: 1.0 - - expression: geefNde(0, ['a', 'b', 'c', 'd']) + - expression: geefNde(1, [1.0, 2.0, 3.0, 4.0, 5.0]) + return: 2.0 + - expression: geefNde(2, [1.0, 2.0, 3.0, 4.0, 5.0]) + return: 3.0 + - expression: geefNde(3, [1.0, 2.0, 3.0, 4.0, 5.0]) + return: 4.0 + - expression: geefNde(4, [1.0, 2.0, 3.0, 4.0, 5.0]) + return: 5.0 + + - tab: geefNde (Char) + testcases: + - expression: geefNde(0, ['a', 'b', 'c', 'd', 'e']) return: a - - expression: geefNde(1, [(1, 2), (3, 4), (5, 6), (7, 8)]) - return: !tuple [3, 4] - - expression: geefNde(1, [(1.0, 2.0), (3.0, 4.0), (5.0, 6.0), (7.0, 8.0)]) - return: !tuple [3.0, 4.0] + - expression: geefNde(1, ['a', 'b', 'c', 'd', 'e']) + return: b + - expression: geefNde(2, ['a', 'b', 'c', 'd', 'e']) + return: c + - expression: geefNde(3, ['a', 'b', 'c', 'd', 'e']) + return: d + - expression: geefNde(4, ['a', 'b', 'c', 'd', 'e']) + return: e + + - tab: geefNde (Tuple) + testcases: + - expression: geefNde(0, [(1, 5), (2, 4), (3, 3), (4, 2), (5, 1)]) + return: [1, 5] + - expression: geefNde(1, [(1, 5), (2, 4), (3, 3), (4, 2), (5, 1)]) + return: [2, 4] + - expression: geefNde(2, [(1, 5), (2, 4), (3, 3), (4, 2), (5, 1)]) + return: [3, 3] + - expression: geefNde(3, [(1, 5), (2, 4), (3, 3), (4, 2), (5, 1)]) + return: [4, 2] + - expression: geefNde(4, [(1, 5), (2, 4), (3, 3), (4, 2), (5, 1)]) + return: [5, 1] diff --git a/tests/test_language_quircks.py b/tests/test_language_quircks.py index 9af65f889..3156b2823 100644 --- a/tests/test_language_quircks.py +++ b/tests/test_language_quircks.py @@ -72,7 +72,7 @@ def test_haskell_function_arguments_without_brackets( result = generate_statement(bundle, statement) assert ( - result == f'{submission_name(bundle.language)}.test 5.5 :: Double "hallo" True' + result == f'{submission_name(bundle.language)}.test 5.5 "hallo" True' ) From b20c4c1f4948a12ada7e602f376f59d26ae491b4 Mon Sep 17 00:00:00 2001 From: tibvdm Date: Wed, 2 Oct 2024 16:07:26 +0200 Subject: [PATCH 5/5] fix linter --- tests/test_language_quircks.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/test_language_quircks.py b/tests/test_language_quircks.py index 3156b2823..9401bae30 100644 --- a/tests/test_language_quircks.py +++ b/tests/test_language_quircks.py @@ -71,9 +71,7 @@ def test_haskell_function_arguments_without_brackets( ) result = generate_statement(bundle, statement) - assert ( - result == f'{submission_name(bundle.language)}.test 5.5 "hallo" True' - ) + assert result == f'{submission_name(bundle.language)}.test 5.5 "hallo" True' def test_javascript_exception_correct(tmp_path: Path, pytestconfig: pytest.Config):