140 lines
6.4 KiB
Prolog
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.
|