elipl/tests.pl
Kacper Marzecki 4f13a98189 asd
2025-05-29 16:53:36 +02:00

140 lines
6.4 KiB
Prolog

:- module(tests, [run_tests/0]).
:- use_module(parser).
:- use_module(types).
:- use_module(log).
run_test(TestName, Env, Code, ExpectedTypeResult) :-
format('--- Test: ~w ---~n', [TestName]),
( parse(Code, AST) ->
format('Parsed AST: ~w~n', [AST]),
log(test_setup, env(Env)),
( catch(infer_type(AST, Env, ActualType), Error, (
log(error, caught_error(Error)),
explain_error(Error, Explanation),
format('Type Error: ~w~n', [Explanation]),
ActualType = error(Error) % Represent error for comparison
))
-> true
; ActualType = 'inference_failed_silently' % Should not happen if catch works
),
format('Inferred Type: ~w~n', [ActualType]),
( (ActualType == never, ExpectedTypeResult \== never) -> % Explicitly fail if 'never' is inferred unexpectedly
Pass = false, SubMatch = 'unexpected_never'
; (ExpectedTypeResult = error(_), ActualType = error(_)) -> % Both are errors
Pass = true, SubMatch = 'error_expected_and_received'
; ExpectedTypeResult == ActualType ->
Pass = true, SubMatch = 'exact_match'
; unify_types(ExpectedTypeResult, ActualType, ExpectedTypeResult) -> % Actual is subtype of Expected
Pass = true, SubMatch = 'subtype_match'
; unify_types(ExpectedTypeResult, ActualType, ActualType) -> % Expected is subtype of Actual (and Actual is not 'never' unless Expected is also 'never')
Pass = true, SubMatch = 'supertype_match'
; Pass = false, SubMatch = 'mismatch'
),
( Pass == true ->
format('Status: PASS (~w)~n~n', [SubMatch])
; format('Status: FAIL (~w) - Expected: ~w~n~n', [SubMatch, ExpectedTypeResult])
)
; format('Parse FAILED for code: ~s~n~n', [Code])
).
run_tests :-
set_verbosity(1), % Set verbosity: 0 (errors), 1 (log), 2 (debug)
log(tests, 'Starting test suite...'),
initial_env(EmptyEnv),
run_test('Conditional with is_number/1 (x is number)',
[x:union(number,string)],
"(if (is_number x) x 0)", % x is union(number,string), then branch x is number. else branch x is string.
% 0 is number. So then branch is number, else branch is number.
% Result should be number.
number), % If x is number, then x (number). Else 0 (number). Unified: number.
run_test('Conditional with is_number/1 (x is string)',
[x:union(number,string)],
"(if (is_number x) 1 \"not num\")", % x:union(number,string).
% Cond: (is_number x)
% Then: x refined to number. Body `1` is number.
% Else: x refined to string. Body `"not num"` is string.
% Result: union(number, string)
union(number, string)),
run_test('Pattern match list',
[my_list:list(number)],
"(match my_list (((list a b) a)))", % my_list:list(number). a,b become number. returns a (number).
% Pattern (list a b), body a
number),
run_test('Pattern match tuple',
[my_tuple:tuple([number, string])],
"(match my_tuple (((tuple x y) y)))", % my_tuple:tuple([number, string]). x is number, y is string. returns y (string).
% Pattern (tuple x y), body y
string),
run_test('Let binding', % z:number, trying to assign string. Current 'let' rebinds.
[z:number],
"(let z \"text\" z)",
string), % With current 'let' semantics (rebinding), z will be string. Env [z:number] is shadowed.
run_test('Unification in conditional branches',
EmptyEnv,
"(if true 10 \"text\")", % ThenType=number, ElseType=string. unify_types(number,string) -> union(number,string)
union(number,string)
),
run_test('Successful refinement (simulated validate_user)',
[user_data:any],
% Using is_number to simulate a predicate that refines type.
% Env: [user_data:any]
% (if (is_number user_data) user_data "not a number")
% Cond: (is_number user_data) -> boolean
% Then branch: user_data refined to number. Body: user_data -> number
% Else branch: user_data refined to not(number). Body: "not a number" -> string
% Result: union(number, string)
"(if (is_number user_data) user_data \"not a number\")",
union(number, string)
),
run_test('Lambda expression (syntax check, type not deeply inferred yet)',
EmptyEnv,
"(lambda (x y) (add x y))", % `add` is not a defined function, so type of body is an issue.
% For now, this tests parsing of lambda.
% Expected type depends on how `add` and lambdas are typed.
% Let's expect 'any' or a placeholder function type if types.pl is not updated for lambdas.
% For now, let's assume it's 'any' as `add` is unknown.
any), % Placeholder: Actual type depends on full function type inference.
run_test('Function application of lambda (syntax check)',
EmptyEnv,
"((lambda (x) x) 10)",
any), % Placeholder: Actual type depends on lambda type inference and application rules.
% If lambda is (T->T) and arg is T, result is T. Here, (any->any) and number -> any.
% If (lambda (x) x) is typed as fun_type([any],any), then apply to int(10) (number) -> any.
run_test('Empty list literal',
EmptyEnv,
"()",
list(never) % Or some polymorphic list type list(T) if supported. list(never) is common for empty.
),
run_test('Boolean true literal',
EmptyEnv,
"true",
boolean
),
run_test('Boolean false literal',
EmptyEnv,
"false",
boolean
),
log(tests, 'Test suite finished.').
% To run:
% ?- consult('log.pl').
% ?- consult('parser.pl').
% ?- consult('types.pl').
% ?- consult('tests.pl').
% ?- run_tests.