/*
this is part of (H)MUARC https://logicmoo.org/xwiki/bin/view/Main/ARC/
This work may not be copied and used by anyone other than the author Douglas Miles
unless permission or license is granted (contact at business@logicmoo.org)
*/
%:- encoding(iso_latin_1).
%*********************************************************************************************
% PROGRAM FUNCTION: provides a framework for executing, tracking, and logging Prolog
% unit tests with structured reporting and ANSI-formatted output for readability.
%*********************************************************************************************
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% IMPORTANT: DO NOT DELETE COMMENTED-OUT CODE AS IT MAY BE UN-COMMENTED AND USED
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
:- ensure_loaded(library(occurs)).
% Ensure that the `metta_interp` library is loaded.
% This loads all the predicates called from this file.
:- ensure_loaded(metta_interp).
:- ensure_loaded(metta_utils).
% Reset loonit counters
%! loonit_reset is det.
%
% Resets all counters related to testing outcomes. Specifically, it clears
% any previous test results by resetting both the success and failure flags
% to zero.
%
% This predicate is useful for initializing or reinitializing test metrics
% before starting a new set of tests.
loonit_reset :-
% Ensures any pending output is written before resetting.
flush_output,
% Calls a report function.
loonit_report,
% Flushes output again after reporting.
flush_output,
% Resets the failure counter to zero.
flag(loonit_failure, _, 0),
% Resets the success counter to zero.
flag(loonit_success, _, 0).
%! has_loonit_results is nondet.
%
% Succeeds if there are any test results (either successes or failures).
%
% This predicate is used to check if tests have been run by examining the
% cumulative count of results. If the count (FS) is greater than 1, it
% indicates results exist.
has_loonit_results :-
% Retrieves the current test count.
loonit_number(FS),
% Succeeds if there are results (count > 1).
FS > 1.
%! loonit_number(-FS) is det.
%
% Retrieves the total number of tests executed so far, including both
% successes and failures. The result (FS) unifies with this count.
%
% This predicate first tries to retrieve the test number flag. If no test
% number flag exists or has a zero value, it calculates the count based on
% success and failure flags, adding one as a base count.
%
% @arg FS The unified test count.
loonit_number(FS) :-
% Tries to read the test number flag.
flag(loonit_test_number, FS, FS),
% If the flag exists and is non-zero, succeeds.
FS > 0,!.
loonit_number(FS) :-
% Retrieves the success count.
flag(loonit_success, Successes, Successes),
% Retrieves the failure count.
flag(loonit_failure, Failures, Failures),
% Calculates total count with a base of 1.
FS is Successes + Failures + 1.
%! string_replace(+Original, +Search, +Replace, -Replaced) is det.
%
% Replaces all occurrences of `Search` in `Original` with `Replace`,
% producing `Replaced`. This predicate breaks `Original` into segments
% split by `Search`, then concatenates these segments with `Replace`
% between each.
%
% @arg Original The original string to search within.
% @arg Search The substring to search for and replace.
% @arg Replace The substring to insert in place of `Search`.
% @arg Replaced The resulting string after all replacements.
%
% @example
% % Replace all occurrences of "foo" with "bar" in the string:
% ?- string_replace("foo_test_foo", "foo", "bar", Replaced).
% Replaced = "bar_test_bar".
%
string_replace(Original, Search, Replace, Replaced) :-
symbolic_list_concat(Split, Search, Original),
symbolic_list_concat(Split, Replace, Replaced),!.
%! get_test_name(+Number, -TestName) is det.
%
% Generates a test name by appending a formatted `Number` to the base
% path or filename currently loaded. If `loading_file` is defined
% with a non-empty path, it is used as the base; otherwise, a default
% path ('SOME/UNIT-TEST') is used.
%
% @arg Number The test number to include in the name.
% @arg TestName The generated test name, combining the base path with
% the test number in a standardized format.
%
% @example
% % Generate a test name for test number 5:
% ?- get_test_name(5, TestName).
% TestName = 'SOME/UNIT-TEST.05'.
%
get_test_name(Number, TestName) :-
((nb_current(loading_file, FilePath), FilePath \== []) -> true ; FilePath = 'SOME/UNIT-TEST'),
make_test_name(FilePath, Number, TestName).
%! ensure_basename(+FilePath0, -FilePath) is det.
%
% Ensures that FilePath is a base name without directory components.
% If FilePath0 already lacks directory components, it is returned as-is.
% Otherwise, it is converted to an absolute path.
%
% @arg FilePath0 Initial file path, potentially with directories.
% @arg FilePath Resulting base name or absolute path.
%
% @example
% % Convert an absolute file path with directories:
% ?- ensure_basename('/home/user/test.pl', FilePath).
% FilePath = '/home/user/test.pl'.
%
ensure_basename(FilePath, FilePath) :-
% Succeeds if FilePath is already a simple file name without directory components.
\+ directory_file_path(('.'), _, FilePath), !.
ensure_basename(FilePath0, FilePath) :-
% Converts FilePath0 to an absolute path, stored in FilePath.
absolute_file_name(FilePath0, FilePath), !.
ensure_basename(FilePath, FilePath).
%! make_test_name(+FilePath0, +Number, -TestName) is det.
%
% Generates a standardized test name based on a given file path and a test number.
% The test name combines the uppercase parent directory and base file name,
% with underscores replaced by hyphens, followed by a zero-padded test number.
%
% @arg FilePath0 Initial file path used to create the test name.
% @arg Number Numeric identifier for the test, formatted as two digits.
% @arg TestName Resulting standardized test name.
%
% @example
% % Generate a test name from a file path and test number:
% ?- make_test_name('/home/user/test_file.pl', 5, TestName).
% TestName = 'HOME.TEST-FILE.05'.
make_test_name(FilePath0, Number, TestName) :-
% Normalize the file path to a base name.
ensure_basename(FilePath0, FilePath),
% Extracts the base file name from the path.
file_base_name(FilePath, FileName),
% Retrieves the parent directory of the file.
directory_file_path(ParentDir, FileName, FilePath),
% Extracts the base name of the parent directory.
file_base_name(ParentDir, ParentDirBase),
% Removes the file extension from the base file name.
file_name_extension(Base, _, FileName),
% Converts the parent directory base name to uppercase.
string_upper(ParentDirBase, UpperParentDirBase),
% Converts the base file name to uppercase.
string_upper(Base, UpperBase),
% Replaces underscores with hyphens in the base name.
string_replace(UpperBase, "_MW", "", NOMW),
string_replace(NOMW, "_", "-", NoUnderscore),
% Replaces underscores with hyphens in the parent directory name.
string_replace(UpperParentDirBase, "_", "-", NoUnderscoreParent),
% Formats the test number as a zero-padded two-digit string.
wots(NS, format('~`0t~d~2|', [Number])),
% Combines parent directory, file base, and test number to form the test name.
format(string(TestName), "~w.~w.~w", [NoUnderscoreParent, NoUnderscore, NS]).
%! color_g_mesg(+Color, :Goal) is det.
%
% Executes the given Goal with color formatting, if conditions allow.
% This predicate evaluates whether to display a message in color based on
% system settings (e.g., compatibility or silent loading modes). If conditions
% permit, it applies the specified color to the Goal's output.
%
% @arg Color The color to apply if messages are displayed.
% @arg Goal The goal to execute and display, formatted with the specified color.
%
% @example
% % Display a message in cyan if not in silent mode:
% ?- color_g_mesg(cyan, writeln('Test message')).
% color_g_mesg(_, _) :- is_compatio, !.
% color_g_mesg(_, _) :- silent_loading, !.
color_g_mesg(C, G) :-
% Checks if output is allowed, skipping if silent mode is active.
notrace((
% Determines if silent loading mode should suppress output.
nop(check_silent_loading),
% Executes the color message output if permissible.
color_g_mesg_ok(C, G)
)).
%! color_g_mesg_ok(+Color, :Goal) is det.
%
% Internal helper to apply color formatting conditionally and execute Goal.
% This predicate first checks compatibility mode and, if active, simply calls
% the Goal without formatting. If compatibility mode is inactive, it formats
% the output in the specified color using `our_ansi_format/3`.
%
% @arg Color The color in which the Goal output is formatted.
% @arg Goal The goal to execute with or without color formatting.
%
% @example
% % Execute a message with color formatting if allowed:
% ?- color_g_mesg_ok(red, writeln('Important message')).
color_g_mesg_ok(_, G) :-
% In compatibility mode, simply execute the Goal without formatting.
is_compatio, !, call(G).
color_g_mesg_ok(C, G) :-
% Quietly executes Goal and formats output, suppressing if empty.
quietly((
% Writes the output of Goal to S, if any.
wots(S, must_det_ll(user:G)),
% Applies color formatting if there is non-empty output in S.
(S == "" -> true ; our_ansi_format(C, '~w~n', [S]))
)), !.
%! our_ansi_format(+Color, +Format, +Args) is det.
%
% Formats and outputs text in the specified color if Color is an atom,
% otherwise uses `ansi_format/3` directly. This predicate allows for conditional
% colorized output, with fallback formatting if color settings are not provided.
%
% @arg Color The color to apply to the text. If non-atomic, falls back to default formatting.
% @arg Format The format string.
% @arg Args The arguments for the format string.
%
% @example
% % Apply color formatting directly:
% ?- our_ansi_format(green, 'Success: ~w', ['All tests passed']).
our_ansi_format(C, Fmt, Args) :-
% If Color is not an atom, apply ansi_format directly.
\+ atom(C), % set_stream(current_output,encoding(utf8)),
ansi_format(C, Fmt, Args).
our_ansi_format(C, Fmt, Args) :-
% If Color is atomic, set the foreground color and format the output.
our_ansi_format([fg(C)], Fmt, Args).
%! print_current_test is det.
%
% Prints the current test's name in an HTML heading format. The test name is
% retrieved based on the current `loonit_test_number`.
%
% @example
% % Display the current test in an HTML format:
% ?- print_current_test.
print_current_test :-
% Retrieves the current test number.
loonit_number(Number),
% Generates the test name based on the current number.
get_test_name(Number, TestName),
% Prints the test name in an HTML heading format.
format('~N~n;
;; ~w
~n', [TestName, TestName]).
%! ensure_increments(:Goal) is det.
%
% Executes a Goal and increments the failure counter if it does not alter the
% pass/fail counts. This predicate is useful for tracking test failures during
% a Goal's execution.
%
% @arg Goal The goal whose outcome is tracked for increments.
%
% @example
% % Track a goal and increment failure count if it fails:
% ?- ensure_increments(writeln('Running test goal...')).
ensure_increments(Goal) :-
% Sets up initial conditions and executes Goal, adjusting counters afterward.
setup_call_cleanup(
% Retrieves initial test result counts.
get_pass_fail(_, _, TotalStart),
% Executes the given Goal.
Goal,
% After execution, increments failure counter if pass/fail counts remain unchanged.
((get_pass_fail(_, _, TotalEnd),
if_t(TotalEnd == TotalStart,
flag(loonit_failure, Failures, Failures + 1))))).
%! get_pass_fail(-Successes, -Failures, -Total) is det.
%
% Retrieves the current counts of successful and failed tests.
%
% @arg Successes Unified with the current success count.
% @arg Failures Unified with the current failure count.
% @arg Total Unified with the sum of Successes and Failures.
%
% @example
% % Query the current pass and fail counts:
% ?- get_pass_fail(S, F, T).
% S = 10, F = 2, T = 12.
get_pass_fail(Successes, Failures, Total) :-
% Retrieves the success count.
flag(loonit_success, Successes, Successes),
% Retrieves the failure count.
flag(loonit_failure, Failures, Failures),
% Sums successes and failures to get the total count.
!,
Total is Successes + Failures.
%! loonit_asserts(+Src, +Precondition, +Goal) is det.
%
% Ensures that assertions for a Goal are properly tracked and increments the
% counters based on the result. It calls `loonit_asserts0/3` as the core logic.
%
% @arg Src The source identifier for the assertion.
% @arg Precondition The precondition to be met before running the Goal.
% @arg Goal The goal to assert.
%
% @example
% % Assert a goal with a precondition and track the result:
% ?- loonit_asserts('test_source', true, writeln('Testing...')).
loonit_asserts(S, Pre, G) :-
% Wraps `loonit_asserts0/3` with increment tracking.
ensure_increments(loonit_asserts0(S, Pre, G)).
%! loonit_asserts0(+Src, +Precondition, +Goal) is det.
%
% Asserts a Goal if its preconditions are met and increments the test counter.
% Displays the current test in a structured format and tracks the Goal’s execution.
%
% @arg Src The source identifier for the assertion.
% @arg Precondition The precondition to be met before running the Goal.
% @arg Goal The goal to assert.
%
% @example
% % Perform assertion with tracking, assuming precondition holds:
% ?- loonit_asserts0('source', true, writeln('Executing goal')).
loonit_asserts0(S, Pre, G) :-
% Increments the test number.
flag(loonit_test_number, X, X + 1),
% Copies the precondition term to avoid modifications.
copy_term(Pre, Pro),
% Displays the current test name.
print_current_test,
% Evaluates the precondition once.
once(Pre), !,
% Sets the execution source if available.
((nb_current(exec_src, Exec), Exec \== []) -> true ; S = Exec),
% Writes the execution source for logging.
write_src(exec(Exec)), nl, nl,
% wots(S, ((((nb_current(exec_src, WS), WS \== []) -> writeln(WS); write_src(exec(TestSrc)))))),
% Evaluates the main assertion goal.
once(loonit_asserts1(Exec, Pro, G)).
%! give_pass_credit(+TestSrc, +Precondition, +Goal) is det.
%
% Marks a test as passed and updates the success counter if conditions are met.
% This predicate either fails if assertions do not hold or logs the test as passed,
% updating the `loonit_success` counter.
%
% @arg TestSrc The source identifier of the test.
% @arg Precondition A placeholder for the precondition, not actively used here.
% @arg Goal The goal representing the test to be evaluated.
%
% @example
% % Increment success counter and log a test as passed:
% ?- give_pass_credit('test_source', true, writeln('Goal succeeded')).
give_pass_credit(TestSrc, _Pre, _G) :-
% This branch fails immediately, used if assertions don't hold.
fail,
% Retrieves base evaluation from within assertion context.
inside_assert(TestSrc, BaseEval),
% Executes the base evaluation.
always_exec(BaseEval), !.
give_pass_credit(TestSrc, _Pre, G) :-
% Logs the test as passed with 'PASS' status.
write_pass_fail(TestSrc, 'PASS', G),
% Increments the success counter.
flag(loonit_success, X, X + 1), !,
% Displays a success message in cyan color.
color_g_mesg(cyan, write_src(loonit_success(G))), !.
%! write_pass_fail(+TestDetails, +Status, +Goal) is det.
%
% Logs the test's result, including the test name, status, and key arguments
% of the goal. This predicate structures test details and formats them for output.
%
% @arg TestDetails A list containing information about the test source, category, and type.
% @arg Status A string representing the result status (e.g., 'PASS' or 'FAIL').
% @arg Goal The goal being evaluated, with selected arguments logged.
%
% @example
% % Log the details of a test with a passing status:
% ?- write_pass_fail(['source', 'category', 'type'], 'PASS', some_goal(arg1, arg2)).
write_pass_fail([P, C, _], PASS_FAIL, G) :-
% Ensures deterministic logging of the test result.
must_det_ll((
% Retrieves the current test number.
loonit_number(Number),
% Generates the test name.
get_test_name(Number, TestName),
% Extracts arguments from the goal for structured output.
arg(1, G, G1),
arg(2, G, G2),
% Logs the formatted test name, source, category, status, and arguments.
write_pass_fail(TestName, P, C, PASS_FAIL, G1, G2))).
%! write_pass_fail(+TestName, +Source, +Category, +Status, +GoalArg1, +GoalArg2) is det.
%
% Formats and appends test results to a shared log file, including metadata like
% source, category, status, and duration. It generates a detailed, linkable log entry
% for each test, facilitating comprehensive result tracking and HTML output per test.
%
% @arg TestName The name of the test being logged.
% @arg Source The test source identifier.
% @arg Category The category identifier for the test.
% @arg Status The result status (e.g., 'PASS' or 'FAIL').
% @arg GoalArg1 The first argument of the goal being logged.
% @arg GoalArg2 The second argument of the goal being logged.
%
% @example
% % Write formatted test results to the log file:
% ?- write_pass_fail('Test1', 'source', 'category', 'PASS', arg1, arg2).
write_pass_fail(TestName, P, C, PASS_FAIL, G1, G2) :-
% Check if the current file path is loaded; if not, set a default.
ignore(((
(nb_current(loading_file, FilePath), FilePath \== []) -> true ; FilePath = 'SOME/UNIT-TEST.metta'),
% Concatenate for the file base.
symbolic_list_concat([_, R], 'tests/', FilePath),
file_name_extension(Base, _, R))),
% Optional format output for HTML log entry.
nop(format(';; ~w
', [TestName, TestName])),
% Log test details deterministically.
must_det_ll((
(tee_file(TEE_FILE) -> true ; 'TEE.ansi' = TEE_FILE),
((
% Optional shared units for organized logging.
shared_units(UNITS),
open(UNITS, append, Stream, [encoding(utf8)]),
% Retrieve or create HTML file name.
once(getenv('HTML_FILE', HTML_OUT) ; sformat(HTML_OUT, '~w.metta.html', [Base])),
% Compute and store a per-test HTML output.
compute_html_out_per_test(HTML_OUT, TEE_FILE, TestName, HTML_OUT_PerTest),
% Measure and format the duration of the last call.
get_last_call_duration(Duration),
DurationX1000 is Duration * 1000,
% Write the detailed formatted log entry.
format(Stream,'| ~w | ~w |[~w](https://trueagi-io.github.io/metta-wam/~w#~w) | ~@ | ~@ | ~@ | ~w | ~w |~n',
[TestName,PASS_FAIL,TestName,HTML_OUT,TestName,
trim_gstring_bar_I(write_src_woi([P,C]),600),
trim_gstring_bar_I(write_src_woi(G1),600),
trim_gstring_bar_I(write_src_woi(G2),600),
DurationX1000,
HTML_OUT_PerTest]),!,
% Close the log stream
close(Stream))))).
% Needs not to be absolute and not relative to CWD (since tests like all .metta files change their local CWD at least while "loading")
%! output_directory(-OutputDir) is det.
%
% Retrieves the output directory from environment variables. First, it checks
% for the `METTALOG_OUTPUT` variable; if unset, it falls back to `OUTPUT_DIR`.
%
% @arg OutputDir The directory path for output logs.
%
% @example
% % Query the output directory for logging:
% ?- output_directory(Dir).
% Dir = '/path/to/output'.
output_directory(OUTPUT_DIR) :- getenv('METTALOG_OUTPUT', OUTPUT_DIR), !.
output_directory(OUTPUT_DIR) :- getenv('OUTPUT_DIR', OUTPUT_DIR), !.
%! shared_units(-UnitsFile) is det.
%
% Retrieves the file path for shared units logging. This checks for the
% `SHARED_UNITS` environment variable; if unset, it defaults to a standard
% file within the output directory or `/tmp/SHARED.UNITS`.
%
% @arg UnitsFile The path to the shared units file.
%
% @example
% % Get the shared units file path:
% ?- shared_units(Units).
% Units = '/path/to/SHARED.UNITS'.
shared_units(UNITS) :-
% Needs not to be relative to CWD
getenv('SHARED_UNITS', UNITS), !.
shared_units(UNITS) :-
output_directory(OUTPUT_DIR), !,
directory_file_path(OUTPUT_DIR, 'SHARED.UNITS', UNITS).
shared_units(UNITS) :-
UNITS = '/tmp/SHARED.UNITS'.
% currently in a shared file per TestCase class..
% but we might make each test dump its stuff to its own HTML file for easier spotting why test failed
%! compute_html_out_per_test(+HTML_OUT, +TeeFile, +TestName, -HTML_OUT_PerTest) is det.
%
% Sets the HTML output path for a specific test based on the given HTML output directory.
%
% @arg HTML_OUT The main HTML output directory path.
% @arg TeeFile A file used for tee-style output (not actively used).
% @arg TestName The name of the test for which output is being generated.
% @arg HTML_OUT_PerTest The computed output path for the individual test's HTML.
%
% @example
% % Compute the HTML output for an individual test:
% ?- compute_html_out_per_test('/output/main.html', _, 'Test1', HTMLPerTest).
% HTMLPerTest = '/output/main.html'.
compute_html_out_per_test(HTML_OUT, _TEE_FILE, _TestName, HTML_OUT_PerTest) :-
HTML_OUT = HTML_OUT_PerTest.
%! record_call_duration(:Goal) is det.
%
% Executes the given Goal and records the execution duration in milliseconds
% in a global variable. This duration is updated regardless of whether the
% Goal succeeds or fails.
%
% @arg Goal The goal whose execution duration is to be measured.
%
% @example
% % Record the duration of a sample goal:
% ?- record_call_duration((writeln('Sample goal'), sleep(1))).
record_call_duration(Goal) :-
nb_setval('$last_call_duration', 120),
statistics(cputime, Start), % Get the start CPU time
( call(Goal) % Call the Goal
*-> EndResult = true % If Goal succeeds, proceed
; EndResult = false % If Goal fails, record it but proceed
),
statistics(cputime, End), % Get the end CPU time
Duration is End - Start, % Calculate the CPU duration
nb_setval('$last_call_duration', Duration), % Set the global variable non-backtrackably
EndResult. % Preserve the result of the Goal
%! get_last_call_duration(-Duration) is det.
%
% Retrieves the duration of the last executed goal, which is stored in a global
% variable by `record_call_duration/1`.
%
% @arg Duration The duration of the last goal in milliseconds.
%
% @example
% % Retrieve the duration of the last recorded call:
% ?- get_last_call_duration(Dur).
% Dur = 1.234.
get_last_call_duration(Duration) :-
nb_getval('$last_call_duration', Duration), !.
%! trim_gstring_bar_I(:Goal, +MaxLen) is det.
%
% Executes a goal to generate a string, replaces certain characters, and trims
% the result to a maximum length. This is primarily used for logging formatted
% results within a limited display space.
%
% @arg Goal The goal that produces the string output.
% @arg MaxLen The maximum allowable length for the output.
%
% @example
% % Trim and modify the output of a goal for display:
% ?- trim_gstring_bar_I(writeln('Example|output\nNewline'), 10).
trim_gstring_bar_I(Goal, MaxLen) :-
wots(String0, Goal),
string_replace(String0, '|', 'I', String1),
string_replace(String1, '\n', '\\n', String),
atom_length(String, Len),
( Len =< MaxLen
-> Trimmed = String
; (sub_string(String, 0, MaxLen, LeftOver, SubStr),
format(string(Trimmed), '~w...(~w)', [SubStr, LeftOver]))
),
write(Trimmed).
%! tst_cwdl(+Goal, +MaxDepth) is det.
%
% Call Goal with a depth limit of MaxDepth. If the depth limit is exceeded,
% an exception `over_test_resource_limit(depth_limit, MaxDepth, 1)` is thrown.
%
% @arg Goal The Prolog goal to be called.
% @arg MaxDepth The maximum depth allowed for the call.
%
% @throws over_test_resource_limit(depth_limit, MaxDepth, 1)
% If the depth limit is exceeded.
%
% @example
% % Successful call within depth limit
% ?- tst_cwdl(member(X, [1,2,3]), 3).
% X = 1 ;
% X = 2 ;
% X = 3.
%
% @example
% % Call exceeding depth limit
% ?- tst_cwdl(member(X, [1,2,3]), 0).
% ERROR: Unhandled exception: over_test_resource_limit(depth_limit, 0, 1)
%
tst_cwdl(Goal, _MaxDepth) :- !, call(Goal).
tst_cwdl(Goal, MaxDepth) :-
call_with_depth_limit(Goal, MaxDepth, Result),
cwdl_handle_result(Result, MaxDepth).
%! cwdl_handle_result(+Result, +MaxDepth) is det.
%
% Processes the result of a depth-limited call. If the result indicates that
% the depth limit was exceeded, it throws an exception with details of the
% maximum allowed depth. For other results, it succeeds without action.
%
% @arg Result The outcome of the depth-limited call (e.g., `depth_limit_exceeded`).
% @arg MaxDepth The maximum allowable depth for the call.
%
% @example
% % Handle a depth limit exceeded result:
% ?- cwdl_handle_result(depth_limit_exceeded, 5).
% ERROR: over_test_resource_limit(depth_limit, 5, 1).
%
% % Handle a successful result without exceeding the limit:
% ?- cwdl_handle_result(success, 5).
% true.
cwdl_handle_result(depth_limit_exceeded, MaxDepth) :-
% If depth limit is exceeded, throw an exception with max depth details.
!,
throw(over_test_resource_limit(depth_limit, MaxDepth, 1)).
cwdl_handle_result(_, _).
%! tst_cwil(+Goal, +MaxInference) is det.
%
% Call Goal with a inference limit of MaxInference. If the inference limit is exceeded,
% an exception `over_test_resource_limit(inference_limit, MaxInference, 1)` is thrown.
%
% @arg Goal The Prolog goal to be called.
% @arg MaxInference The maximum inference allowed for the call.
%
% @throws over_test_resource_limit(inference_limit, MaxInference, 1)
% If the inference limit is exceeded.
%
% @example
% % Successful call within inference limit
% ?- tst_cwil(member(X, [1,2,3]), 3).
% X = 1 ;
% X = 2 ;
% X = 3.
%
% @example
% % Call exceeding inference limit
% ?- tst_cwil(member(X, [1,2,3]), 0).
% ERROR: Unhandled exception: over_test_resource_limit(inference_limit, 0, 1)
%
tst_cwil(Goal, _MaxInference) :- !, call(Goal).
tst_cwil(Goal, MaxInference) :-
call_with_inference_limit(Goal, MaxInference, Result),
cwil_handle_result(Result, MaxInference).
%! cwil_handle_result(+Result, +MaxInference) is det.
%
% Processes the result of an inference-limited call. If the inference limit
% is exceeded, it throws an exception with details of the maximum inferences allowed.
%
% @arg Result The result from `call_with_inference_limit/3`, indicating success or limit exceeded.
% @arg MaxInference The maximum inference count allowed for the call.
%
% @example
% % Handle an inference limit exceeded result:
% ?- cwil_handle_result(inference_limit_exceeded, 5).
% ERROR: Unhandled exception: over_test_resource_limit(inference_limit, 5, 1).
%
% % Handle a result within inference limits:
% ?- cwil_handle_result(success, 5).
% true.
cwil_handle_result(inference_limit_exceeded, MaxInference) :-
% If inference limit is exceeded, throw an exception with max inference details.
!,
throw(over_test_resource_limit(inference_limit, MaxInference, 1)).
cwil_handle_result(_, _).
%! tst_cwtl(+Goal, +TimeLimit) is det.
%
% Call Goal with a time limit of TimeLimit seconds. If the time limit is exceeded,
% an exception `over_test_resource_limit(time_limit, TimeLimit, exceeded)` is thrown.
%
% @arg Goal The Prolog goal to be called.
% @arg TimeLimit The maximum time allowed for the call in seconds.
%
% @throws over_test_resource_limit(time_limit, TimeLimit, exceeded)
% If the time limit is exceeded.
%
% @example
% % Successful call within time limit
% ?- tst_cwtl((sleep(1), writeln('Completed')), 2).
% Completed
% true.
%
% @example
% % Call exceeding time limit
% ?- tst_cwtl((sleep(2), writeln('Completed')), 1).
% ERROR: Unhandled exception: over_test_resource_limit(time_limit, 1, exceeded)
%
tst_cwtl(Goal, TimeLimit) :-
catch(call_with_time_limit(TimeLimit,Goal),time_limit_exceeded,throw(over_test_resource_limit(time_limit, TimeLimit, exceeded))).
%! test_alarm is det.
%
% Tests for alarm-triggered time limits. Sets a time limit of 0.5 seconds for
% a goal. If the limit is exceeded, the exception is caught, and a "passed"
% message is displayed; otherwise, it displays "failed."
%
% @example
% % Run the alarm test:
% ?- test_alarm.
% % Output depends on whether the time limit is exceeded.
test_alarm :-
!.
test_alarm :-
% Set time limit and attempt goal within 0.5 seconds.
time(catch(
% Run goal with time limit; fail if it exceeds limit.
(call_with_time_limit(0.5,
(forall(between(1, 15, _), sleep(0.1)),
write_src_uo(failed_test_alarm)))),
% If limit is exceeded, catch exception and display "passed."
time_limit_exceeded,
write_src_uo(passed_test_alarm)
)).
%! loonit_divisor(-TestNumber) is det.
%
% Retrieves the current test number, defaulting to 1 if no tests have run.
% This ensures a minimum divisor of 1.
%
% @arg TestNumber The divisor, based on the current test number or 1 if unset.
%
% @example
% % Get the test divisor:
% ?- loonit_divisor(Divisor).
% Divisor = 1.
loonit_divisor(TestNumber) :-
% Get current test number or default to 1 if unset.
loonit_number(TN),
(TN > 0 -> TestNumber = TN ; TestNumber = 1),
!.
%! compute_available_time(-ActualTimeout) is det.
%
% Computes the actual timeout based on the available timeout and test number,
% ensuring it is at least 4 seconds.
%
% @arg ActualTimeout The computed timeout value, ensuring a minimum of 4 seconds.
%
% @example
% % Assuming `loonit_number/1` returns 3 and available timeout is 120 seconds:
% ?- compute_available_time(ActualTimeout).
% ActualTimeout = 38.0.
%
% @example
% % With a smaller available timeout:
% ?- option_else(timeout, "20", _), compute_available_time(ActualTimeout).
% ActualTimeout = 4.0.
%
compute_available_time(ActualTimeout) :-
option_else(timeout, Was, fake), Was == fake,!,ActualTimeout=3600.
compute_available_time(ActualTimeout) :-
loonit_divisor(TestNumber),
option_else(timeout, AvailableTimeoutStr, "120"),
into_number(AvailableTimeoutStr, AvailableTimeout),
ComputedTimeout is (AvailableTimeout / TestNumber) - 2,
max_min(4, ComputedTimeout, ActualTimeout, _), !.
%! tst_call_limited(+Goal) is det.
%
% Calls the Goal with a depth limit of 1000 and a time limit calculated based on
% the available timeout divided by the test number, ensuring the timeout is at least 4 seconds.
%
% @arg Goal The Prolog goal to be called.
%
% @throws over_test_resource_limit(time_limit, ActualTimeout, exceeded)
% if the time limit is exceeded.
% @throws over_test_resource_limit(depth_limit, 1000, 1)
% if the depth limit is exceeded.
%
% @example
% % Run a goal within the calculated limits:
% ?- tst_call_limited(member(X, [1,2,3])).
% X = 1 ;
% X = 2 ;
% X = 3.
%
% @example
% % Run a goal that exceeds the calculated time limit:
% ?- tst_call_limited(sleep(20)).
% red: Exception: over_test_resource_limit(sleep(20), time_limit, 4, exceeded)
% false.
%
tst_call_limited(Goal) :-
% notrace(write_src_uo(tst_call_limited(Goal))),
compute_available_time(ActualTimeout),
catch(
% Apply the time limit, depth limit, inference limit
tst_cwtl(tst_cwil(tst_cwdl(Goal, 300), 1_000_000_000), ActualTimeout),
Exception,
(ansi_format([fg(red)],'~n~n~q~n~n',[failing(Goal, Exception)]), !, fail)
).
%! loonit_asserts1(+TestSrc, +Precondition, +Goal) is det.
%
% Executes the Goal with a given precondition and records its duration.
% If the Goal succeeds, it gives pass credit; if it fails, it logs the failure.
%
% @arg TestSrc The source identifier for the assertion.
% @arg Precondition The precondition to be met before running the Goal.
% @arg Goal The goal to be asserted.
%
% @example
% % Run an assertion and handle pass/fail logging:
% ?- loonit_asserts1('source', true, writeln('Goal executed successfully')).
loonit_asserts1(TestSrc, Pre, G) :-
% Run precondition and record duration of Goal execution.
_ = nop(Pre),
record_call_duration((G)),
% Log as passed if Goal succeeds.
give_pass_credit(TestSrc, Pre, G),
!.
/*
loonit_asserts1(TestSrc,Pre,G) :- fail,
sub_var('BadType',TestSrc), \+ check_type,!,
write('\n!check_type (not considering this a failure)\n'),
color_g_mesg('#D8BFD8',write_src(loonit_failureR(G))),!,
ignore(((
option_value('on-fail','trace'),
setup_call_cleanup(debug(metta(eval)),call((Pre,G)),nodebug(metta(eval)))))).
*/
loonit_asserts1(TestSrc, Pre, G) :-
% Handle failed Goal by logging, flagging failure, and optionally tracing.
must_det_ll((
color_g_mesg(red, write_src(loonit_failureR(G))),
write_pass_fail(TestSrc, 'FAIL', G),
flag(loonit_failure, X, X + 1),
% Optional trace or REPL on failure based on settings.
if_t(option_value('on-fail', 'repl'), repl),
if_t(option_value('on-fail', 'trace'),
setup_call_cleanup(debug(metta(eval)), call((Pre, G)), nodebug(metta(eval)))
)
)).
% (thread_self(main)->trace;sleep(0.3)
% Generate loonit report with colorized output
:- dynamic(gave_loonit_report/0).
%! loonit_report is det.
%
% Generates a colorized report of test successes and failures, ensuring the
% report is only generated once. If there are no successes or any failures,
% optionally launches a REPL based on settings.
%
% @example
% % Generate a loonit report:
% ?- loonit_report.
loonit_report :-
% Skip if report already generated in this session.
gave_loonit_report,
!.
loonit_report :-
% Mark the report as generated.
assert(gave_loonit_report),
% Retrieve current counts of successes and failures.
flag(loonit_success, Successes, Successes),
flag(loonit_failure, Failures, Failures),
% Display the report based on these counts.
loonit_report(Successes, Failures),
% If no successes or any failures, open REPL if settings permit.
if_t((Successes == 0 ; Failures > 0),
if_t(option_value(repl, failures) ; option_value(frepl, true), repl)).
% Ensure loonit report runs at program halt.
:- at_halt(loonit_report).
%! loonit_report(+Successes, +Failures) is det.
%
% Outputs a formatted report of test results with colorized success and failure counts.
%
% @arg Successes The count of successful tests.
% @arg Failures The count of failed tests.
%
% @example
% % Display the report with specified counts:
% ?- loonit_report(5, 2).
loonit_report(0, 0) :-
!. % Skip report if no successes or failures.
loonit_report(Successes, Failures) :-
% Display report header in bold.
ansi_format([bold], 'LoonIt Report~n', []),
format('------------~n'),
% Display success count in green.
ansi_format([fg(green)], 'Successes: ~w~n', [Successes]),
% Display failure count in red if any; otherwise, green.
((integer(Failures), Failures > 0)
-> ansi_format([fg(red)], 'Failures: ~w~n', [Failures])
; ansi_format([fg(green)], 'Failures: ~w~n', [Failures])).
%! loon_metta(+File) is det.
%
% Resets test counters, loads a specified file, and generates a status report.
% This is useful for reinitializing test metrics before loading files.
%
% @arg File The file to load and evaluate.
%
% @example
% % Load a file and generate a status report:
% ?- loon_metta('test_file.pl').
loon_metta(File) :-
% Save current success and failure counts, then reset counters.
flag(loonit_success, WasSuccesses, 0),
flag(loonit_failure, WasFailures, 0),
% Load the specified file.
load_metta(File),
% Generate report after loading.
loonit_report,
% Restore previous success and failure counts.
flag(loonit_success, _, WasSuccesses),
flag(loonit_failure, _, WasFailures),
!.
% dynamic means that the predicate can be modified at runtime.
:- dynamic(file_answers/3).
:- dynamic(file_exec_num/2).
%! set_exec_num(+SFileName, +Val) is det.
%
% Updates or asserts the execution number for the specified file. If an execution
% number already exists for the file, it is replaced with the new value; otherwise,
% the value is asserted as a new entry.
%
% @arg SFileName The source file name, which is converted to an absolute path.
% @arg Val The execution number to set for the file.
%
% @example
% % Set the execution number for a file:
% ?- set_exec_num('test_file.pl', 3).
set_exec_num(SFileName, Val) :-
% Convert to absolute file path.
absolute_file_name(SFileName, FileName),
% If an entry exists for FileName, retract it; otherwise, do nothing.
( retract(file_exec_num(FileName, _))
-> true
; true
),
% Assert the new execution number for FileName.
asserta(file_exec_num(FileName, Val)).
%! get_exec_num(-Val) is det.
%
% Retrieves the execution number for the current file. If no execution number
% is set for the current file, it returns 0.
%
% @arg Val The current execution number for the file or 0 if not set.
%
% @example
% % Retrieve the execution number for the current file, defaulting to 0:
% ?- get_exec_num(Val).
% Val = 3.
get_exec_num(Val) :-
% Get the absolute path of the current file.
current_exec_file_abs(FileName),
% Retrieve the execution number for FileName, stopping after one result.
file_exec_num(FileName, Val),
!.
%! get_exec_num(+FileName, -Val) is det.
%
% Retrieves the execution number for the specified file. If none exists, it returns 0.
%
% @arg FileName The file name for which to retrieve the execution number.
% @arg Val The current execution number or 0 if not set.
%
% @example
% % Retrieve the execution number for a file, defaulting to 0 if not set:
% ?- get_exec_num('test_file.pl', Val).
% Val = 3.
get_exec_num(FileName, Val) :-
% If an entry exists for FileName, retrieve its value; otherwise, return 0.
( file_exec_num(FileName, CurrentVal)
-> Val = CurrentVal
; Val = 0
).
%! current_exec_file_abs(-FileName) is det.
%
% Retrieves the absolute path of the currently executing file.
%
% @arg FileName The absolute file path of the current execution file.
%
% @example
% % Get the absolute path of the current execution file:
% ?- current_exec_file_abs(FileName).
current_exec_file_abs(FileName) :-
% Obtain the file name of the current execution file and convert it to absolute path.
current_exec_file(SFileName),
absolute_file_name(SFileName, FileName),
!.
%! get_expected_result(-Ans) is det.
%
% Retrieves the expected result (answer) for the current file and execution number,
% if it is available.
%
% @arg Ans The expected answer for the current file execution.
%
% @example
% % Retrieve the expected answer for the current file and execution:
% ?- get_expected_result(Ans).
get_expected_result(Ans) :-
ignore((
% Get absolute file name, execution number, and retrieve answer.
current_exec_file_abs(FileName),
file_exec_num(FileName, Nth),
file_answers(FileName, Nth, Ans)
)),
!.
%! got_exec_result(+Val) is det.
%
% Records the actual result (`Val`) of the current execution and compares it with
% the expected result. If the result matches, it logs a pass; otherwise, it logs a fail.
%
% @arg Val The actual result produced during the current execution.
%
% @example
% % Record and evaluate an execution result:
% ?- got_exec_result(Result).
got_exec_result(Val) :-
ignore((
% Get file name, execution number, expected answer, and evaluate result.
current_exec_file_abs(FileName),
file_exec_num(FileName, Nth),
file_answers(FileName, Nth, Ans),
got_exec_result(Val, Ans)
)).
%! got_exec_result(+Val, +Ans) is det.
%
% Compares the actual result (`Val`) with the expected result (`Ans`). If the results
% match, it logs a pass; otherwise, it logs a fail.
%
% @arg Val The actual result produced.
% @arg Ans The expected result.
%
% @example
% % Compare an actual result with the expected answer:
% ?- got_exec_result(actual_val, expected_ans).
got_exec_result(Val, Ans) :-
must_det_ll((
% Get file name, execution number, and test name for logging.
current_exec_file_abs(FileName),
file_exec_num(FileName, Nth),
Nth100 is Nth + 100,
get_test_name(Nth100, TestName),
% Retrieve execution context and compare actual vs. expected result.
nb_current(exec_src, Exec),
(equal_enough_for_test(Val, Ans)
-> write_pass_fail_result(TestName, exec, Exec, 'PASS', Ans, Val)
; write_pass_fail_result(TestName, exec, Exec, 'FAIL', Ans, Val)
)
)).
%! write_pass_fail_result(+TestName, +Exec, +ExecContext, +PassFail, +Ans, +Val) is det.
%
% Logs the result of a test, specifying whether it passed or failed.
%
% @arg TestName The name of the test.
% @arg Exec Execution identifier (e.g., 'exec').
% @arg ExecContext The context of the execution.
% @arg PassFail Result status ('PASS' or 'FAIL').
% @arg Ans The expected result.
% @arg Val The actual result produced.
%
% @example
% % Log the result of a test as pass or fail:
% ?- write_pass_fail_result('Test1', exec, context, 'PASS', expected_ans, actual_val).
write_pass_fail_result(TestName, exec, Exec, PASS_FAIL, Ans, Val) :-
% Output and log the result as a pass or fail.
nl, writeq(write_pass_fail_result(TestName, exec, Exec, PASS_FAIL, Ans, Val)), nl,
write_pass_fail(TestName, exec, Exec, PASS_FAIL, Ans, Val).
%! current_exec_file(-FileName) is det.
%
% Retrieves the current execution file name if set.
%
% @arg FileName The current file name in execution.
%
% @example
% % Get the current file in execution:
% ?- current_exec_file(FileName).
current_exec_file(FileName) :-
nb_current(loading_file, FileName).
%! inc_exec_num(+FileName) is det.
%
% Increments the execution number for the given file. If no entry exists for
% the file, it initializes the execution number to 1.
%
% @arg FileName The name of the file for which to increment the execution number.
%
% @example
% % Increment the execution number for the current file:
% ?- inc_exec_num('test_file.pl').
inc_exec_num :-
% Get the absolute path of the current execution file.
current_exec_file_abs(FileName),
!,
inc_exec_num(FileName).
inc_exec_num(FileName) :-
% If an entry exists, increment its value; otherwise, set it to 1.
( retract(file_exec_num(FileName, CurrentVal))
-> NewVal is CurrentVal + 1
; NewVal = 1
),
% Assert the updated execution number.
asserta(file_exec_num(FileName, NewVal)).
%! load_answer_file(+File) is det.
%
% Loads the answer file specified by `File`, handling path resolution and
% initialization of execution tracking. If the file does not exist or is not
% specified as an absolute path, it attempts to resolve it.
%
% @arg File The path to the answer file to load.
%
% @example
% % Load an answer file with automatic path resolution:
% ?- load_answer_file('answers_file.ans').
load_answer_file(File) :-
% Resolve to an absolute file path if necessary.
( \+ atom(File); \+ is_absolute_file_name(File); \+ exists_file(File)),
absolute_file_name(File, AbsFile), File\=@=AbsFile,
load_answer_file_now(AbsFile),
!.
load_answer_file(File) :-
load_answer_file_now(File),
!.
%! load_answer_file_now(+File) is det.
%
% Processes and loads the specified answer file, initializing or updating
% execution tracking for the file.
%
% @arg File The file path of the answer file to load.
%
% @example
% % Begin loading an answer file, initializing execution tracking:
% ?- load_answer_file_now('/path/to/answers_file.ans').
load_answer_file_now(File) :-
ignore((
% Ensure correct file extension for answer files.
ensure_extension(File, answers, AnsFile),
remove_specific_extension(AnsFile, answers, StoredAs),
% Initialize execution count and start loading.
set_exec_num(StoredAs, 1),
fbug(load_answer_file(AnsFile, StoredAs)),
load_answer_file(AnsFile, StoredAs)
)).
%! load_answer_file(+AnsFile, +StoredAs) is det.
%
% Loads answers from `AnsFile` into the system under the identifier `StoredAs`.
% If answers are already loaded or the file does not exist, it skips loading.
%
% @arg AnsFile The path of the answer file to load.
% @arg StoredAs The identifier under which the answers are stored.
%
% @example
% % Load answers from a file into the system under an identifier:
% ?- load_answer_file('answers_file.ans', 'stored_as').
load_answer_file(AnsFile, StoredAs) :-
% If answers are already loaded or file is absent, skip loading.
( file_answers(StoredAs, _, _)
-> true
; ( \+ exists_file(AnsFile)
-> true
; % Open file and load answers from stream.
(setup_call_cleanup(
open(AnsFile, read, Stream, [encoding(utf8)]),
(load_answer_stream(1, StoredAs, Stream)),
close(Stream))
)
)
),
% Initialize execution number after loading.
set_exec_num(StoredAs, 1),
!.
% This allows Prolog to print debug information related to the metta(answers) topic
:- debug(metta(answers)).
%! load_answer_stream(+Nth, +StoredAs, +Stream) is det.
%
% Reads and loads answers from a given stream, associating each answer with the
% identifier `StoredAs` and an index `Nth`. If the end of the stream is reached,
% it lists all loaded answers for debugging if `answers` tracing is enabled.
%
% @arg Nth The index of the current answer being read.
% @arg StoredAs The identifier under which the answers are stored.
% @arg Stream The input stream from which answers are read.
%
% @example
% % Load answers from a stream and associate them with an identifier:
% ?- open('answers.txt', read, Stream), load_answer_stream(1, 'stored_as', Stream).
load_answer_stream(_Nth, StoredAs, Stream) :-
% Stop if end of the stream is reached, optionally listing loaded answers.
at_end_of_stream(Stream),
!,
if_trace((answers), prolog_only(listing(file_answers(StoredAs, _, _)))).
load_answer_stream(Nth, StoredAs, Stream) :-
% Read a line from the stream and process it recursively.
read_line_to_string(Stream, String),
load_answer_stream(Nth, StoredAs, String, Stream).
%! load_answer_stream(+Nth, +StoredAs, +String, +Stream) is det.
%
% Processes a string read from the stream, parsing it as an answer, storing
% it with `StoredAs`, and continuing to the next line. If parsing is successful,
% it stores the answer using `pfcAdd_Now/1` with an incremented index.
%
% @arg Nth The index of the current answer being processed.
% @arg StoredAs The identifier under which the answer is stored.
% @arg String The string representation of the answer.
% @arg Stream The input stream for reading additional lines if needed.
%
% @example
% % Process an answer string from the stream and store it:
% ?- load_answer_stream(1, 'stored_as', "[Answer]", Stream).
/*
load_answer_stream(Nth, StoredAs, String, Stream) :- fail,
atom_chars(String, Chars),
count_brackets(Chars, 0, 0, Balance),
( Balance =< 0
-> StoredAs = String
; read_line_to_string(Stream, NextString),
string_concat(String, "\n", StringWithNewLine),
string_concat(StringWithNewLine, NextString, CombinedString),
load_answer_stream(Nth, StoredAs, CombinedString, Stream)
).
*/
load_answer_stream(Nth, StoredAs, String, Stream) :- % string_concat("[", _, String), !
% Debugging statement to show the answer being processed.
fbug(Nth = String),
% Parse answer string into a Prolog term.
parse_answer_string(String, Metta),
!,
% Store the parsed answer.
pfcAdd_Now(file_answers(StoredAs, Nth, Metta)),
% Skip if the answer contains a comma.
skip(must_det_ll(\+ sub_var(',', Metta))),
% Increment index and continue processing next line.
Nth2 is Nth + 1,
load_answer_stream(Nth2, StoredAs, Stream).
load_answer_stream(Nth, StoredAs, _, Stream) :-
% Fall back to reading the next line if no answer is processed.
load_answer_stream(Nth, StoredAs, Stream).
/*
count_brackets([], Open, Close, Balance) :- !,
Balance is Open - Close.
count_brackets([Char|Rest], Open, Close, Balance) :-
(((( Char == '['
-> NewOpen is Open + 1
; (Char == ']'
-> NewClose is Close + 1
; (NewOpen = Open,
NewClose = Close)))))),
count_brackets(Rest, NewOpen, NewClose, Balance).
*/
%! parse_answer_string(+String, -Metta) is nondet.
%
% Parses a given String and converts it into a Prolog term `Metta`.
% This predicate handles various formats of input strings, performing
% specific parsing based on the format. If the String matches certain
% error patterns, the predicate will fail.
%
% @arg String The input string that represents some answer or assertion result.
% @arg Metta The output variable where the parsed result will be unified, if parsing is successful.
%
% @example Parsing an empty list:
% ?- parse_answer_string("[]", Result).
% Result = [].
%
% @example Handling an error assertion:
% ?- parse_answer_string("[(Error (assert ...))]", Result).
% false.
%
% Parse an empty list, unifying with an empty list if the string is "[]".
parse_answer_string("[]", []) :- !.
% parse_answer_string(String, Metta) :- string_concat("(", _, String), !, parse_sexpr_metta(String, Metta), !.
% Fail if the string starts with an assertion error pattern.
parse_answer_string(String, _Metta) :- string_concat("[(Error (assert", _, String), !, fail.
% Fail if the string begins with "Expected: [" and contains an expected inner pattern.
parse_answer_string(String, _Metta) :- string_concat("Expected: [", Mid, String), string_concat(_Expected_Inner, "]", Mid), !, fail.
% Parse a `Got` response by extracting the inner content from "Got: [ ... ]".
parse_answer_string(String, Metta) :- string_concat("Got: [", Mid, String), string_concat(Got_Inner, "]", Mid), !, parse_answer_inner(Got_Inner, Metta).
% Parse generic bracketed content by extracting the inner part from "[ ... ]".
parse_answer_string(String, Metta) :- string_concat("[", Mid, String), string_concat(Inner0, "]", Mid), !, parse_answer_inner(Inner0, Metta).
%! parse_answer_inner(+Inner0, -Metta) is det.
%
% Converts the content of `Inner0` into a Prolog term `Metta` by replacing specific patterns,
% parsing the modified string, and conditionally skipping processing if certain variables are detected.
%
% @arg Inner0 The input string to parse.
% @arg Metta The resulting parsed Prolog term.
%
% @example
% ?- parse_answer_inner("some,content", Result).
% Result = parsed_term.
%
parse_answer_inner(Inner0, Metta) :-
must_det_ll((
% Replace specific character patterns in Inner0 to create Inner.
replace_in_string([', '=' , '], Inner0, Inner),
% Parse modified string Inner into Metta.
parse_answer_str(Inner, Metta),
% Skip processing if Metta meets the specified condition.
skip((\+ sub_var(',', rc(Metta))))
)).
%! parse_answer_str(+Inner0, -Metta) is det.
%
% Parses the content of `Inner0` into a structured Prolog term `Metta` by handling various formats.
% Depending on the format, it may apply transformations, handle comma removal, and check for
% certain variable conditions.
%
% @arg Inner0 The input string to parse.
% @arg Metta The resulting parsed Prolog term.
%
% @example
% ?- parse_answer_str("some content", Result).
% Result = parsed_term.
%
% Parse a string with specific formatting, building the term as a list starting with C.
parse_answer_str(Inner, [C|Metta]) :-
atomics_to_string(["(", Inner, ")"], Str),parse_sexpr_metta(Str, CMettaC), CMettaC = [C|MettaC],
% Remove commas from MettaC to create Metta, if conditions are met.
((remove_m_commas(MettaC, Metta),\+ sub_var(',', rc(Metta)))).
% Handle concatenated symbols in Inner0 by converting them into a list and parsing each element.
parse_answer_str(Inner0, Metta) :- symbolic_list_concat(InnerL, ' , ', Inner0),
maplist(atom_string, InnerL, Inner),maplist(parse_sexpr_metta, Inner, Metta),
skip((must_det_ll(( \+ sub_var(',', rc2(Metta)))))), !.
% Apply replacements in Inner0 and parse as a single expression.
parse_answer_str(Inner0, Metta) :-
((replace_in_string([' , '=' '], Inner0, Inner),atomics_to_string(["(", Inner, ")"], Str), !,
parse_sexpr_metta(Str, Metta), !,skip((must_det_ll(\+ sub_var(',', rc3(Metta))))),
skip((\+ sub_var(',', rc(Metta)))))).
%parse_answer_string(String,Metta):- String=Metta,!,fail.
%! remove_m_commas(+InputList, -OutputList) is det.
%
% Removes specific elements (such as commas or "and") from `InputList`, creating `OutputList`.
% If `InputList` does not contain any commas as variables, `OutputList` is identical to `InputList`.
%
% @arg InputList The list to process, potentially containing unwanted elements.
% @arg OutputList The resulting list with specific elements removed.
%
% @example
% ?- remove_m_commas([and, item1, ',', item2, and, item3], Result).
% Result = [item1, item2, item3].
%
% Return the list as-is if it contains no commas as variables.
remove_m_commas(Metta, Metta) :- \+ sub_var(',', Metta), !.
% Remove 'and' from the beginning of the list and continue processing.
remove_m_commas([C, H | T], [H | TT]) :- C == 'and', !, remove_m_commas(T, TT).
% Remove ',' from the beginning of the list and continue processing.
remove_m_commas([C, H | T], [H | TT]) :- C == ',', !, remove_m_commas(T, TT).
% Process remaining elements recursively.
remove_m_commas([H | T], [H | TT]) :- !, remove_m_commas(T, TT).
%! change_extension(+OriginalFileName, +NewExtension, -NewBaseName) is det.
%
% Changes the file extension of `OriginalFileName` to `NewExtension`, producing `NewBaseName`.
% This predicate extracts the base name without the original extension and appends the
% specified `NewExtension` to create the new file name.
%
% @arg OriginalFileName The original file path with an extension to be changed.
% @arg NewExtension The new extension to use for the file.
% @arg NewBaseName The resulting file name with the new extension.
%
% @example
% ?- change_extension('path/to/myfile.txt', 'pdf', NewFileName).
% NewFileName = 'path/to/myfile.pdf'.
%
change_extension(OriginalFileName, NewExtension, NewBaseName) :-
% Split the original file name to extract the base without its extension.
file_name_extension(BaseWithoutExt, _, OriginalFileName),
% Create a new file name by appending the new extension to the base.
file_name_extension(BaseWithoutExt, NewExtension, NewBaseName), !.
%! ensure_extension(+OriginalFileName, +Extension, -NewFileName) is det.
%
% Ensures that `OriginalFileName` has the specified `Extension`. If it already has the extension,
% `NewFileName` is identical to `OriginalFileName`. Otherwise, the `Extension` is appended.
%
% @arg OriginalFileName The original file path, potentially with or without the desired extension.
% @arg Extension The required extension for the file.
% @arg NewFileName The resulting file name, with the ensured extension.
%
% @example
% ?- ensure_extension('path/to/myfile', 'txt', NewFileName).
% NewFileName = 'path/to/myfile.txt'.
%
ensure_extension(OriginalFileName, Extension, NewFileName) :-
% Extract the current extension of the file, if any.
file_name_extension(_, CurrentExt, OriginalFileName),
% If the current extension matches the desired one, keep the original file name.
( CurrentExt = Extension
-> NewFileName = OriginalFileName
% Otherwise, append the new extension to create NewFileName.
; atom_concat(OriginalFileName, '.', TempFileName),
atom_concat(TempFileName, Extension, NewFileName)
).
% Example usage:
% ?- remove_specific_extension('path/to/myfile.txt', 'txt', NewFileName).
% NewFileName = 'path/to/myfile'.
%! remove_specific_extension(+OriginalFileName, +Extension, -FileNameWithoutExtension) is det.
%
% Removes a specific extension from `OriginalFileName` if it matches `Extension`.
% If `OriginalFileName` does not have the specified `Extension`, it is returned unchanged.
%
% @arg OriginalFileName The original file path, possibly with an extension.
% @arg Extension The specific extension to remove.
% @arg FileNameWithoutExtension The resulting file name without the specified extension.
%
% @example
% ?- remove_specific_extension('path/to/myfile.txt', 'pdf', NewFileName).
% NewFileName = 'path/to/myfile.txt'.
%
remove_specific_extension(OriginalFileName, Extension, FileNameWithoutExtension) :-
% Extract the extension of the file, if any.
file_name_extension(FileNameWithoutExtension, Ext, OriginalFileName),
% If the extracted extension matches the specified one, return the base name;
% otherwise, retain the original file name.
( Ext = Extension -> true ; FileNameWithoutExtension = OriginalFileName ).
%! quick_test is det.
%
% Runs a quick test by executing each test case in `quick_test/1` and loading it
% into the system via `load_metta_stream/2`. Each test case is opened as a string
% stream and processed with a predefined entity identifier `&self`.
%
% This predicate is intended for streamlined testing by iterating over all
% available quick test cases.
%
% @example
% ?- quick_test.
% % Runs all quick tests in quick_test/1 through load_metta_stream.
%
quick_test :-
% For each test in quick_test/1, open the test as a stream and load it.
forall(quick_test(Test),
forall(open_string(Test, Stream),
load_metta_stream('&self', Stream))).
/*
tests for term expander
*/
% :- debug(term_expansion).
% Enable conditional compilation if debugging for term expansion is active.
:- if((false, debugging(term_expansion))).
% Enable ARC-specific term expansions if the condition is met.
:- enable_arc_expansion.
% Suppress warnings about singleton variables in this section.
:- style_check(-singleton).
% Define various test cases for deterministic term expansion.
% Each `dte` clause represents a different expansion or assertion pattern.
% Set a local variable.
dte :- set(_X.local) = val.
% Set a global variable.
dte :- gset(_X.global) = gval.
% Assert that setting `_X.a` to `b` must succeed deterministically.
dte :- must_det_ll((set(_X.a) = b)).
% Use `must_det_ll` to ensure that `nb_setval/2` runs locally and call `dte` recursively
% with a modified term involving `X.tail`.
dte :- must_det_ll(locally(nb_setval(e, X.locally), dte([foo | set(X.tail)]))).
% Check if `set(V.element)` is a member of `set(V.list)`.
dte :- member(set(V.element), set(V.list)).
% Define a specific expansion for `dte/1` with input `set(E.v)` when `set(E.that)` equals `v`.
dte(set(E.v)) :- set(E.that) = v.
% Restore the default singleton variable warnings.
:- style_check(+singleton).
% Disable ARC-specific term expansions after this section.
:- disable_arc_expansion.
% List all clauses of `dte/1` for inspection.
:- listing(dte).
% End the conditional compilation.
:- endif.
%! factorial_recursive(+N, -Result) is det.
%
% Computes the factorial of N using a simple recursive approach.
% This predicate multiplies N by the factorial of (N-1) until it reaches 0.
%
% @arg N The non-negative integer for which to calculate the factorial.
% @arg Result The resulting factorial value of N.
%
% @example
% ?- factorial_recursive(5, Result).
% Result = 120.
%
factorial_recursive(0, 1).
factorial_recursive(N, Result) :-
N > 0,N1 is N - 1,factorial_recursive(N1, Result1),Result is N * Result1.
%! factorial_tail_recursive(+N, -Result) is det.
%
% Computes the factorial of N using a tail-recursive approach with an accumulator.
% This method is optimized for large values of N due to tail-call optimization.
%
% @arg N The non-negative integer for which to calculate the factorial.
% @arg Result The resulting factorial value of N.
%
% @example
% ?- factorial_tail_recursive(5, Result).
% Result = 120.
%
factorial_tail_recursive(N, Result) :- factorial_tail_helper(N, 1, Result).
%! factorial_tail_helper(+N, +Acc, -Result) is det.
%
% Helper predicate for factorial_tail_recursive/2 that accumulates the result
% in `Acc`, allowing the computation to proceed in a tail-recursive manner.
%
% @arg N The current value being processed in the factorial calculation.
% @arg Acc The accumulator holding the intermediate factorial result.
% @arg Result The final factorial value.
%
factorial_tail_helper(0, Acc, Acc).
factorial_tail_helper(N, Acc, Result) :-
N > 0,NewAcc is Acc * N,N1 is N - 1,factorial_tail_helper(N1, NewAcc, Result).
%! factorial_accumulator(+N, -Result) is det.
%
% Computes the factorial of N using an accumulator-based approach,
% accumulating the result in a helper predicate.
%
% @arg N The non-negative integer for which to calculate the factorial.
% @arg Result The resulting factorial value of N.
%
% @example
% ?- factorial_accumulator(5, Result).
% Result = 120.
%
factorial_accumulator(N, Result) :- factorial_acc(N, 1, Result).
%! factorial_acc(+N, +Acc, -Result) is det.
%
% Helper predicate for factorial_accumulator/2 that uses an accumulator
% to store intermediate results, iterating until N reaches 0.
%
% @arg N The current value being processed in the factorial calculation.
% @arg Acc The accumulator holding the intermediate factorial result.
% @arg Result The final factorial value.
%
factorial_acc(0, Result, Result).
factorial_acc(N, Acc, Result) :- N > 0,NewAcc is Acc * N,N1 is N - 1,factorial_acc(N1, NewAcc, Result).
% You can test each one by querying, for example:
% ?- factorial_recursive(5, X
% The following code defines several test cases and example usages for manipulating spaces, or knowledge bases, using
% predicates such as `add-atom`, `remove-atom`, `replace-atom`, and `get-atoms`. These predicates
% simulate basic CRUD (Create, Read, Update, Delete) operations on a conceptual data structure, `Space`,
% with operations applied to atoms within the space.
%
% The code includes:
%
% 1. **Basic Operations (`example_usages`)**:
% Demonstrates initialization, addition, replacement, and retrieval of atoms in a space. Each operation's result
% is output to show changes in the space's state after each operation.
%
% 2. **Test Cases for Clearing and Modifying Spaces**:
% - `test_clear_space`: Initializes a space, adds atoms, verifies the count and content, clears it, and confirms
% that the atoms and count reset.
% - `test_operations`: Tests sequential additions, removals, and replacements of atoms in a space.
%
% 3. **Multiple Operations in a Shared Space (`test_my_space`)**:
% Initializes a space and performs various atomic operations in sequence to verify the changes at each step.
% This includes ensuring atoms can be added, counted, replaced, removed, and that the original space name is retained.
%
% 4. **Isolated Space Manipulation**:
% Additional tests on separate spaces, such as `&kb22` and `&kb2`, demonstrate similar operations, providing
% results to confirm that each operation succeeds independently.
%
% 5. **Run All Test Cases (`run_tests`)**:
% A convenience predicate `run_tests` that executes `test_clear_space` and `test_operations` to validate
% all defined atomic operations.
%
% The code serves as a comprehensive suite for verifying that atom manipulation functions behave as expected across
% various spaces, with each test printing intermediate results to facilitate debugging and validation of functionality.
% Example-usage
example_usages :-
fetch_or_create_space(newSpace,Space), % Assuming fetch_or_create_space/1 is defined to initialize a space
'add-atom'(Space, a),
'add-atom'(Space, b),
'add-atom'(Space, c),
'match'(Space, a, Template),
write('Matched template: '), writeln(Template),
write('Initial space: '), writeln(Space),
'add-atom'(Space, a),
write('Space after adding "a": '), writeln(Space),
'add-atom'(Space, b),
write('Space after adding "b": '), writeln(Space),
'replace-atom'(Space, a, c),
write('Space after replacing "a" with "c": '), writeln(Space),
'get-atoms'(Space, Atoms),
write('Atoms in space: '), writeln(Atoms),
'atom-count'(Space, Count),
write('Number of atoms in space: '), writeln(Count).
% Test case for clearing a space
test_clear_space :-
writeln('Test: Clearing a space'),
init_space('&kb1'),
'add-atom'('&kb1', a),
'add-atom'('&kb1', b),
writeln('Expected Count Before Clearing: 2'),
'atom-count'('&kb1', CountBefore), writeln('Actual Count:'), writeln(CountBefore),
writeln('Expected Atoms Before Clearing: [b, a]'),
'get-atoms'('&kb1', AtomsBefore), writeln('Actual Atoms:'), writeln(AtomsBefore),
'clear-atoms'('&kb1'),
writeln('Expected Count After Clearing: 0'),
'atom-count'('&kb1', CountAfter), writeln('Actual Count:'), writeln(CountAfter),
writeln('Expected Atoms After Clearing: []'),
'get-atoms'('&kb1', AtomsAfter), writeln('Actual Atoms:'), writeln(AtomsAfter).
% Test case for various operations on a space
test_operations :-
writeln('Test: Various Operations on a Space'),
init_space('&kb2'),
'add-atom'('&kb2', a),
'add-atom'('&kb2', b),
writeln('Expected Count After Adding: 2'),
'atom-count'('&kb2', Count1), writeln('Actual Count:'), writeln(Count1),
writeln('Expected Atoms After Adding: [b, a]'),
'get-atoms'('&kb2', Atoms1), writeln('Actual Atoms:'), writeln(Atoms1),
'remove-atom'('&kb2', a),
writeln('Expected Atoms After Removing a: [b]'),
'get-atoms'('&kb2', Atoms2), writeln('Actual Atoms:'), writeln(Atoms2),
'replace-atom'('&kb2', b, c),
writeln('Expected Atoms After Replacing b with c: [c]'),
'get-atoms'('&kb2', Atoms3), writeln('Actual Atoms:'), writeln(Atoms3).
% Run the test cases
run_tests :-
writeln('Running test_clear_space:'),
test_clear_space,
writeln('---'),
writeln('Running test_operations:'),
test_operations.
% Test case for various operations on a space
test_my_space :-
fetch_or_create_space('&KB', InstanceOfKB),
'clear-atoms'('&KB'),
'add-atom'(InstanceOfKB, a),
'add-atom'(InstanceOfKB, b),
'atom-count'(InstanceOfKB, Count1),
writeln('Should print 2: ' : Count1),
'get-atoms'(InstanceOfKB, Atoms1),
writeln('Should print [b, a]: ' : Atoms1),
'remove-atom'(InstanceOfKB, a),
'get-atoms'(InstanceOfKB, Atoms2),
writeln('Should print [b]: ' : Atoms2),
'replace-atom'(InstanceOfKB, b, c),
'get-atoms'(InstanceOfKB, Atoms3),
writeln('Should print [c]: ' : Atoms3),
space_original_name(InstanceOfKB, OriginalName),
writeln('Should print &KB':OriginalName),
fetch_or_create_space('&KB'),
'add-atom'('&KB', x),
'add-atom'('&KB', y),
'atom-count'('&KB', Count2),
writeln('Should print 3: ' : Count2),
'get-atoms'('&KB', Atoms4),
writeln('Should print [c, y, x]: ' : Atoms4),
'remove-atom'('&KB', x),
'get-atoms'('&KB', Atoms5),
writeln('Should print [c,y]: ' : Atoms5),
'replace-atom'('&KB', y, z),
'get-atoms'(InstanceOfKB, Atoms6),
writeln('Should print [c,z]: ' : Atoms6).
% Test the code
test_clr_my_kb22 :-
fetch_or_create_space('&kb22'),
'add-atom'('&kb22', a),
'add-atom'('&kb22', b),
'atom-count'('&kb22', Count1), writeln(Count1),
'get-atoms'('&kb22', Atoms1), writeln(Atoms1),
'clear-atoms'('&kb22'),
'atom-count'('&kb22', Count2), writeln(Count2),
'get-atoms'('&kb22', Atoms2), writeln(Atoms2).
%a:- !, be(B), (iF(A,B) -> tHEN(A) ).
%a:- !, be(B), (iF(A,B) *-> tHEN(A) ; eLSE(B) ).
% Test the code
test_my_kb2:-
fetch_or_create_space('&kb1', InstanceOfKB),
\+ \+ ('add-atom'('&kb1', a, Out), writeln(Out)),
\+ \+ ('add-atom'('&kb1', b, Out), writeln(Out)),
\+ \+ ('atom-count'('&kb1', Count), writeln(Count)),
\+ \+ ('get-atoms'('&kb1', Atoms), writeln(Atoms)),
\+ \+ ('remove-atom'(InstanceOfKB, a, Out), writeln(Out)),
\+ \+ ('get-atoms'('&kb1', NewAtoms), writeln(NewAtoms)),
\+ \+ ('replace-atom'('&kb1', b, c, Out), writeln(Out)),
\+ \+ ('get-atoms'('&kb1', FinalAtoms), writeln(FinalAtoms)),
\+ \+ (space_original_name(InstanceOfKB, OriginalName), writeln(OriginalName)),
\+ \+ (fetch_or_create_space('&kb2',_)), % Creating a new space with a different name
\+ \+ ('add-atom'('&kb2', a, Out), writeln(Out)),
\+ \+ ('add-atom'('&kb2', b, Out), writeln(Out)),
\+ \+ ('atom-count'('&kb2', Count), writeln(Count)),
\+ \+ ('get-atoms'('&kb2', Atoms), writeln(Atoms)),
\+ \+ ('remove-atom'('&kb2', a, Out), writeln(Out)),
\+ \+ ('get-atoms'('&kb2', NewAtoms), writeln(NewAtoms)),
\+ \+ ('replace-atom'('&kb2', b, c, Out), writeln(Out)),
\+ \+ ('get-atoms'('&kb2', FinalAtoms), writeln(FinalAtoms)).
% This code loads a collection of `.metta` files across various directories and contexts, adding each to the read history.
% Each file path is specified via `mf/1`, and `add_history1(load_metta(H))` loads the file while logging it for quick
% access and debugging.
% Comment `end_of_file` out once to get these files in your readline history
end_of_file. %
mf('./1-VSpaceTest.metta').
mf('./2-VSpaceTest.metta').
mf('./3-Learn-Rules.metta').
mf('./4-VSpaceTest.metta').
mf('./5-Learn-Flybase.metta').
mf('./6-Learn-Flybase-Full.metta').
mf('./8-VSpaceTest.metta').
mf('./autoexec.metta').
mf('./data/OBO-Metta/export/Alliance_of_Genome_Resources.metta').
mf('./data/OBO-Metta/export/biosapiens.metta').
mf('./data/OBO-Metta/export/chebi_fb_2023_04.metta').
mf('./data/OBO-Metta/export/DBVAR.metta').
mf('./data/OBO-Metta/export/doid.metta').
mf('./data/OBO-Metta/export/flybase_controlled_vocabulary.metta').
mf('./data/OBO-Metta/export/flybase_stock_vocabulary.metta').
mf('./data/OBO-Metta/export/fly_anatomy.metta').
mf('./data/OBO-Metta/export/fly_development.metta').
mf('./data/OBO-Metta/export/gene_group_FB2023_04.metta').
mf('./data/OBO-Metta/export/go-basic.metta').
mf('./data/OBO-Metta/export/image.metta').
mf('./data/OBO-Metta/export/psi-mi.metta').
mf('./data/OBO-Metta/export/slice.chebi.metta').
mf('./data/OBO-Metta/export/so-simple.metta').
mf('./data/OBO-Metta/export/so.metta').
mf('./data/OBO-Metta/export/SOFA.metta').
mf('./examples/compat/common/BelieveMe.metta').
mf('./examples/compat/common/EqualityType.metta').
mf('./examples/compat/common/EqualityTypeTest.metta').
mf('./examples/compat/common/formula/DeductionFormula.metta').
mf('./examples/compat/common/formula/DeductionFormulaTest.metta').
mf('./examples/compat/common/formula/ImplicationDirectIntroductionFormula.metta').
mf('./examples/compat/common/formula/ModusPonensFormula.metta').
mf('./examples/compat/common/In.metta').
mf('./examples/compat/common/InTest.metta').
mf('./examples/compat/common/List.metta').
mf('./examples/compat/common/ListTest.metta').
mf('./examples/compat/common/Maybe.metta').
mf('./examples/compat/common/MaybeTest.metta').
mf('./examples/compat/common/Num.metta').
mf('./examples/compat/common/NumTest.metta').
mf('./examples/compat/common/OrderedSet.metta').
mf('./examples/compat/common/OrderedSetTest.metta').
mf('./examples/compat/common/Record.metta').
mf('./examples/compat/common/truthvalue/EvidentialTruthValue.metta').
mf('./examples/compat/common/truthvalue/EvidentialTruthValueTest.metta').
mf('./examples/compat/common/truthvalue/MeasEq.metta').
mf('./examples/compat/common/truthvalue/TemporalTruthValue.metta').
mf('./examples/compat/common/truthvalue/TruthValue.metta').
mf('./examples/compat/common/truthvalue/TruthValueTest.metta').
mf('./examples/compat/dependent-types/DeductionDTL.metta').
mf('./examples/compat/dependent-types/DeductionDTLTest.metta').
mf('./examples/compat/dependent-types/DeductionImplicationDirectIntroductionDTLTest.metta').
mf('./examples/compat/dependent-types/ImplicationDirectIntroductionDTL.metta').
mf('./examples/compat/dependent-types/ImplicationDirectIntroductionDTLTest.metta').
mf('./examples/compat/dependent-types/ModusPonensDTL.metta').
mf('./examples/compat/dependent-types/ModusPonensDTLTest.metta').
mf('./examples/compat/entail/DeductionEntail.metta').
mf('./examples/compat/entail/DeductionEntailTest.metta').
mf('./examples/compat/entail/ImplicationDirectIntroductionEntail.metta').
mf('./examples/compat/entail/ImplicationDirectIntroductionEntailTest.metta').
mf('./examples/compat/equal/DeductionEqual.metta').
mf('./examples/compat/equal/DeductionEqualTest.metta').
mf('./examples/compat/equal/ImplicationDirectIntroductionEqual.metta').
mf('./examples/compat/equal/ImplicationDirectIntroductionEqualTest.metta').
mf('./examples/compat/match/DeductionImplicationDirectIntroductionMatchTest.metta').
mf('./examples/compat/match/DeductionMatch.metta').
mf('./examples/compat/match/DeductionMatchTest.metta').
mf('./examples/compat/match/ImplicationDirectIntroductionMatch.metta').
mf('./examples/compat/match/ImplicationDirectIntroductionMatchTest.metta').
mf('./examples/compat/prob-dep-types/inf_order_probs.metta').
mf('./examples/compat/prob-dep-types/prob_dep_types.metta').
mf('./examples/compat/recursion-schemes/src/base.metta').
mf('./examples/compat/recursion-schemes/src/examples/benchmark.metta').
mf('./examples/compat/recursion-schemes/src/examples/expression.metta').
mf('./examples/compat/recursion-schemes/src/schemes.metta').
mf('./examples/compat/synthesis/experiments/non-determinism.metta').
mf('./examples/compat/synthesis/experiments/self-contained-synthesize.metta').
mf('./examples/compat/synthesis/experiments/synthesize-via-case-test.metta').
mf('./examples/compat/synthesis/experiments/synthesize-via-case.metta').
mf('./examples/compat/synthesis/experiments/synthesize-via-let-test.metta').
mf('./examples/compat/synthesis/experiments/synthesize-via-let.metta').
mf('./examples/compat/synthesis/experiments/synthesize-via-superpose.metta').
mf('./examples/compat/synthesis/experiments/synthesize-via-type-checking.metta').
mf('./examples/compat/synthesis/experiments/synthesize-via-unify-test.metta').
mf('./examples/compat/synthesis/experiments/synthesize-via-unify.metta').
mf('./examples/compat/synthesis/experiments/unify-via-case.metta').
mf('./examples/compat/synthesis/experiments/unify-via-let.metta').
mf('./examples/compat/synthesis/Synthesize.metta').
mf('./examples/compat/synthesis/SynthesizeTest.metta').
mf('./examples/compat/synthesis/Unify.metta').
mf('./examples/compat/synthesis/UnifyTest.metta').
mf('./examples/compat/test_scripts/a1_symbols.metta').
mf('./examples/compat/test_scripts/a2_opencoggy.metta').
mf('./examples/compat/test_scripts/a3_twoside.metta').
mf('./examples/compat/test_scripts/b0_chaining_prelim.metta').
mf('./examples/compat/test_scripts/b1_equal_chain.metta').
mf('./examples/compat/test_scripts/b2_backchain.metta').
mf('./examples/compat/test_scripts/b3_direct.metta').
mf('./examples/compat/test_scripts/b4_nondeterm.metta').
mf('./examples/compat/test_scripts/b5_types_prelim.metta').
mf('./examples/compat/test_scripts/c1_grounded_basic.metta').
mf('./examples/compat/test_scripts/c2_spaces.metta').
mf('./examples/compat/test_scripts/c2_spaces_kb.metta').
mf('./examples/compat/test_scripts/c3_pln_stv.metta').
mf('./examples/compat/test_scripts/d1_gadt.metta').
mf('./examples/compat/test_scripts/d2_higherfunc.metta').
mf('./examples/compat/test_scripts/d3_deptypes.metta').
mf('./examples/compat/test_scripts/d4_type_prop.metta').
mf('./examples/compat/test_scripts/d5_auto_types.metta').
mf('./examples/compat/test_scripts/e1_kb_write.metta').
mf('./examples/compat/test_scripts/e2_states.metta').
mf('./examples/compat/test_scripts/e3_match_states.metta').
mf('./examples/compat/test_scripts/f1_imports.metta').
mf('./examples/compat/test_scripts/f1_moduleA.metta').
mf('./examples/compat/test_scripts/f1_moduleB.metta').
mf('./examples/compat/test_scripts/f1_moduleC.metta').
mf('./examples/compat/test_scripts/_e2_states_dia.metta').
mf('./examples/fibo.metta').
mf('./examples/fwgc.metta').
mf('./examples/httpclient.metta').
mf('./examples/NARS.metta').
mf('./examples/NARS_listing.metta').
mf('./examples/RUN_minnars.metta').
mf('./examples/RUN_tests0.metta').
mf('./examples/RUN_tests1.metta').
mf('./examples/RUN_tests2.metta').
mf('./examples/RUN_tests3.metta').
mf('./examples/send-more.metta').
mf('./examples/talk80.metta').
mf('./examples/VRUN_tests0.metta').
mf('./examples/VRUN_tests1.metta').
mf('./examples/VRUN_tests2.metta').
mf('./examples/VRUN_tests3.metta').
mf('./src/nm_test.metta').
mf('./src/r.metta').
mf('./src/test_nspace.metta').
:- forall(mf(H),add_history1(load_metta(H))).
%:- load_metta
end_of_file.
parsing(String, Expr) :- string(String),!,string_codes(String,Codes),phrase(expressions(Expr), Codes).
parsing(String, Expr) :- phrase(expressions(Expr), String).
expressions([E|Es]) -->
ws, expression(E), ws,
!, % single solution: longest input match
expressions(Es).
expressions([]) --> [].
% ws --> ";",until_eol,
ws --> [W], { code_type(W, space) }, ws.
ws --> [].
% A number N is represented as n(N), a symbol S as s(S).
expression(s(A)) --> symbol(Cs), { atom_codes(A, Cs) }.
expression(n(N)) --> number(Cs), { number_codes(N, Cs) }.
expression(List) --> [L],{is_bracket_lr(L,R)},expressions(List), [R].
expression([s(quote),Q]) --> "'", expression(Q).
number([D|Ds]) --> digit(D), number(Ds).
number([D]) --> digit(D).
digit(D) --> [D], { code_type(D, digit) }.
symbol([A|As]) -->
[A],
{ is_ok_symbolchar(A) },
symbolr(As).
symbolr([A|As]) -->
[A],
{ is_ok_symbolchar(A) ; code_type(A, alnum) },
symbolr(As).
symbolr([]) --> [].
is_bracket_lr(L,R):- member(LR,["()","{}","[]","\"\""]), nth0(0,LR,L),nth0(1,LR,R).
is_ok_symbolchar(A):- \+ code_type(A, space), \+ code_type(A, white), \+ is_bracket_lr(A,_), \+ is_bracket_lr(_,A).
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Interpretation
--------------
Declaratively, execution of a Lisp form is a relation between the
(function and variable) binding environment before its execution
and the environment after its execution. A Lisp program is a
sequence of Lisp forms, and its result is the sequence of their
results. The environment is represented as a pair of association
lists Fs-Vs, associating function names with argument names and
bodies, and variables with values. DCGs are used to implicitly
thread the environment state through.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
codelist_to_forms_i(AsciiCodesList,FormsOut):-
parsing(AsciiCodesList, Forms0),
compile_all(Forms0, FormsOut),!.
run(Program, Values) :-
parsing(Program, Forms0),
empty_assoc(E),
compile_all(Forms0, Forms),
writeq(seeingFormas(Forms)),nl,
phrase(eval_all(Forms, Values0), [E-E], _),
maplist(unfunc, Values0, Values).
unfunc(s(S), S).
unfunc(t, t).
unfunc(n(N), N).
unfunc([], []).
unfunc([Q0|Qs0], [Q|Qs]) :- unfunc(Q0, Q), unfunc(Qs0, Qs).
fold([], _, V, n(V)).
fold([n(F)|Fs], Op, V0, V) :- E =.. [Op,V0,F], V1 is E, fold(Fs, Op, V1, V).
compile_all(Fs0, Fs) :- maplist(compile, Fs0, Fs).
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
compile/2 marks (with 'user/1') calls of user-defined functions.
This eliminates an otherwise defaulty representation of function
calls and thus allows for first argument indexing in eval//3.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
compile(F0, F) :-
( F0 = n(_) -> F = F0
; F0 = s(t) -> F = t
; F0 = s(nil) -> F = []
; F0 = s(_) -> F = F0
; F0 = [] -> F = []
; F0 = [s(quote),Arg] -> F = [quote,Arg]
; F0 = [s(setq),s(Var),Val0] -> compile(Val0, Val), F = [setq,Var,Val]
; F0 = [s(Op)|Args0],
memberchk(Op, [+,-,*,equal,if,>,<,=,progn,eval,list,car,cons,
cdr,while,not]) ->
compile_all(Args0, Args),
F = [Op|Args]
; F0 = [s(defun),s(Name),Args0|Body0] ->
compile_all(Body0, Body),
maplist(arg(1), Args0, Args),
F = [defun,Name,Args|Body]
; F0 = [s(Op)|Args0] -> compile_all(Args0, Args), F = [user(Op)|Args]
).
eval_all([], []) --> [].
eval_all([A|As], [B|Bs]) --> eval(A, B), eval_all(As, Bs).
eval(n(N), n(N)) --> [].
eval(t, t) --> [].
eval([], []) --> [].
eval(s(A), V), [Fs-Vs] --> [Fs-Vs], { get_assoc(A, Vs, V) }.
eval([L|Ls], Value) --> eval(L, Ls, Value).
eval(quote, [Q], Q) --> [].
eval(+, As0, V) --> eval_all(As0, As), { fold(As, +, 0, V) }.
eval(-, As0, V) --> eval_all(As0, [n(V0)|Vs0]), { fold(Vs0, -, V0, V) }.
eval(*, As0, V) --> eval_all(As0, Vs), { fold(Vs, *, 1, V) }.
eval(car, [A], C) --> eval(A, V), { V == [] -> C = [] ; V = [C|_] }.
eval(cdr, [A], C) --> eval(A, V), { V == [] -> C = [] ; V = [_|C] }.
eval(list, Ls0, Ls) --> eval_all(Ls0, Ls).
eval(not, [A], V) --> eval(A, V0), goal_truth(V0=[], V).
eval(>, [A,B], V) --> eval(A, n(V1)), eval(B, n(V2)), goal_truth(V1>V2, V).
eval(<, [A,B], V) --> eval(>, [B,A], V).
eval(=, [A,B], V) --> eval(A, n(V1)), eval(B, n(V2)), goal_truth(V1=:=V2, V).
eval(progn, Ps, V) --> eval_all(Ps, Vs), { last(Vs, V) }.
eval(eval, [A], V) --> eval(A, F0), { compile(F0, F1) }, eval(F1, V).
eval(equal, [A,B], V) --> eval(A, V1), eval(B, V2), goal_truth(V1=V2, V).
eval(cons, [A,B], [V0|V1]) --> eval(A, V0), eval(B, V1).
eval(while, [Cond|Bs], []) -->
( eval(Cond, []) -> []
; eval_all(Bs, _),
eval(while, [Cond|Bs], _)
).
eval(defun, [F,As|Body], s(F)), [Fs-Vs0] -->
[Fs0-Vs0],
{ put_assoc(F, Fs0, As-Body, Fs) }.
eval(user(F), As0, V), [Fs-Vs] -->
eval_all(As0, As1),
[Fs-Vs],
{ empty_assoc(E),
get_assoc(F, Fs, As-Body),
bind_arguments(As, As1, E, Bindings),
phrase(eval_all(Body, Results), [Fs-Bindings], _),
last(Results, V) }.
eval('bind!', [Var,V0], V), [Fs0-Vs] -->
eval(V0, V),
[Fs0-Vs0],
{ put_assoc(Var, Vs0, V, Vs) }.
eval(setq, [Var,V0], V), [Fs0-Vs] -->
eval(V0, V),
[Fs0-Vs0],
{ put_assoc(Var, Vs0, V, Vs) }.
eval(if, [Cond,Then|Else], Value) -->
( eval(Cond, []) -> eval_all(Else, Values), { last(Values, Value) }
; eval(Then, Value)
).
:- meta_predicate goal_truth(0,*,//,//).
goal_truth(Goal, T) --> { Goal -> T = t ; T = [] }.
bind_arguments([], [], Bs, Bs).
bind_arguments([A|As], [V|Vs], Bs0, Bs) :-
put_assoc(A, Bs0, V, Bs1),
bind_arguments(As, Vs, Bs1, Bs).
run(S):-'format'('~n~s~n',[S]),run(S,V),writeq(V).
%if_script_file_time(X):-if_startup_script(time(X)).
if_script_file_time(_):-!.
%if_script_file_time(X):- nop(time(X)).
% Append:
:- if_script_file_time(run("
(defun append (x y)
(if x
(cons (car x) (append (cdr x) y))
y))
(append '(a b) '(3 4 5))")).
%@ V = [append, [a, b, 3, 4, 5]].
% Fibonacci, naive version:
:- if_script_file_time(run("
(defun fib (n)
(if (= 0 n)
0
(if (= 1 n)
1
(+ (fib (- n 1)) (fib (- n 2))))))
(fib 24)")).
%@ % 14,255,802 inferences, 3.71 CPU in 3.87 seconds (96% CPU, 3842534 Lips)
%@ V = [fib, 46368].
% Fibonacci, accumulating version:
:- if_script_file_time(run("
(defun fib (n)
(if (= 0 n) 0 (fib1 0 1 1 n)))
(defun fib1 (f1 f2 i to)
(if (= i to)
f2
(fib1 f2 (+ f1 f2) (+ i 1) to)))
(fib 250)")).
%@ % 39,882 inferences, 0.010 CPU in 0.013 seconds (80% CPU, 3988200 Lips)
%@ V = [fib, fib1, 7896325826131730509282738943634332893686268675876375].
% Fibonacci, iterative version:
:- if_script_file_time(run("
(defun fib (n)
(setq f (cons 0 1))
(setq i 0)
(while (< i n)
(setq f (cons (cdr f) (+ (car f) (cdr f))))
(setq i (+ i 1)))
(car f))
(fib 350)")).
%@ % 30,794 inferences, 0.002 CPU in 0.002 seconds (100% CPU, 12831368 Lips)
%@ V = [fib, 6254449428820551641549772190170184190608177514674331726439961915653414425].
% Fibonacci, accumulating version:
:- if_script_file_time(run("
(defun fib (n)
(if (= 0 n) 0 (fib1 0 1 1 n)))
(defun fib1 (f1 f2 i to)
(if (= i to)
f2
(fib1 f2 (+ f1 f2) (+ i 1) to)))
(fib 350)")).
%@ % 44,595 inferences, 0.003 CPU in 0.003 seconds (100% CPU, 14526532 Lips)
%@ V = [fib, fib1, 6254449428820551641549772190170184190608177514674331726439961915653414425].
% Higher-order programming and eval:
:- if_script_file_time(run("
(defun map (f xs)
(if xs
(cons (eval (list f (car xs))) (map f (cdr xs)))
()))
(defun plus1 (x) (+ 1 x))
(map 'plus1 '(1 2 3))
"
)).
%@ V = [map, plus1, [2, 3, 4]].
%:- ensure_loaded(metta_reader).
#[test]
fn test_case_operation() {
let metta = new_metta_rust();
let result = metta.run(&mut SExprParser::new("
"));
let expected = metta.run(&mut SExprParser::new("
! OK
! 7
! (superpose (OK-3 OK-4))
! (superpose (3 4 5))
! (superpose ())
"));
assert_eq!(result, expected);
let metta = new_metta_rust();
let result = metta.run(&mut SExprParser::new("
(Rel-P A B)
(Rel-Q A C)
; cases can be used for deconstruction
!(case (match &self ($rel A $x) ($rel $x))
(((Rel-P $y) (P $y))
((Rel-Q $y) (Q $y))))
; %void% can be used to capture empty results
!(case (match &self ($rel B $x) ($rel $x))
(((Rel-P $y) (P $y))
((Rel-Q $y) (Q $y))
(%void% no-match)))
; a functional example
(= (maybe-inc $x)
(case $x
(((Just $v) (Just (+ 1 $v)))
(Nothing Nothing)))
)
!(maybe-inc Nothing)
!(maybe-inc (Just 2))
"));
let expected = metta.run(&mut SExprParser::new("
! (superpose ((Q C) (P B)))
! no-match
! Nothing
! (Just 3)
"));
assert_eq_metta_results!(result, expected);
}
use hyperon::metta::text::*;
use hyperon::metta::runner::new_metta_rust;
#[test]
fn test_reduce_higher_order() {
let program = "
; Curried plus
(: plus (-> Number (-> Number Number)))
(= ((plus $x) $y) (+ $x $y))
; Define inc as partial evaluation of plus
(: inc (-> (-> Number Number)))
(= (inc) (plus 1))
!(assertEqualToResult ((inc) 2) (3))
";
let metta = new_metta_rust();
let result = metta.run(&mut SExprParser::new(program));
assert_eq!(result, Ok(vec![vec![]]));
}
use hyperon::*;
use hyperon::space::grounding::GroundingSpace;
#[test]
fn test_custom_match_with_space() {
let mut main_space = GroundingSpace::new();
let mut inserted_space = GroundingSpace::new();
inserted_space.add(expr!("implies" ("B" x) ("C" x)));
inserted_space.add(expr!("implies" ("A" x) ("B" x)));
inserted_space.add(expr!("A" "Sam"));
main_space.add(Atom::gnd(inserted_space));
let result = main_space.query(&expr!("," ("implies" ("B" x) z) ("implies" ("A" x) y) ("A" x)));
assert_eq!(result.len(), 1);
assert_eq!(result[0].resolve(&VariableAtom::new("y")), Some(expr!("B" "Sam")));
assert_eq!(result[0].resolve(&VariableAtom::new("z")), Some(expr!("C" "Sam")));
}
use hyperon::*;
use hyperon::common::*;
use hyperon::metta::interpreter::*;
use hyperon::space::grounding::GroundingSpace;
#[test]
fn test_types_in_metta() {
let mut space = GroundingSpace::new();
space.add(expr!("=" ("check" (":" n "Int")) ({IS_INT} n)));
space.add(expr!("=" ("check" (":" n "Nat")) ({AND} ("check" (":" n "Int")) ({GT} n {0}))));
space.add(expr!("=" ("if" {true} then else) then));
space.add(expr!("=" ("if" {false} then else) else));
space.add(expr!(":" "if" ("->" "bool" "Atom" "Atom" "Atom")));
space.add(expr!("=" ("fac" n) ("if" ("check" (":" n "Nat")) ("if" ({EQ} n {1}) {1} ({MUL} n ("fac" ({SUB} n {1})))) ({ERR}))));
assert_eq!(interpret(&space, &expr!("check" (":" {3} "Int"))), Ok(vec![expr!({true})]));
assert_eq!(interpret(&space, &expr!("check" (":" {(-3)} "Int"))), Ok(vec![expr!({true})]));
assert_eq!(interpret(&space, &expr!("check" (":" {3} "Nat"))), Ok(vec![expr!({true})]));
assert_eq!(interpret(&space, &expr!("check" (":" {(-3)} "Nat"))), Ok(vec![expr!({false})]));
assert_eq!(interpret(&space, &expr!("if" ("check" (":" {(3)} "Nat")) "ok" "nok")), Ok(vec![expr!("ok")]));
assert_eq!(interpret(&space, &expr!("if" ("check" (":" {(-3)} "Nat")) "ok" "nok")), Ok(vec![expr!("nok")]));
assert_eq!(interpret(&space, &expr!("fac" {1})), Ok(vec![expr!({1})]));
assert_eq!(interpret(&space, &expr!("fac" {3})), Ok(vec![expr!({6})]));
}
#[test]
fn test_match_expression_with_variables() {
let mut space = GroundingSpace::new();
space.add(expr!("+" "A" ("*" "B" "C")));
assert_eq!(space.query(&expr!("+" a ("*" b c))),
bind_set![{a: expr!("A"), b: expr!("B"), c: expr!("C") }]);
}
#[test]
fn test_match_different_value_for_variable() {
let mut space = GroundingSpace::new();
space.add(expr!("+" "A" ("*" "B" "C")));
assert_eq!(space.query(&expr!("+" a ("*" a c))), BindingsSet::empty());
}
#[test]
fn test_match_query_variable_has_priority() {
let mut space = GroundingSpace::new();
space.add(expr!("equals" x x));
let result = space.query(&expr!("equals" y z));
assert_eq!(result, bind_set![{ y: expr!(z) }]);
}
#[test]
fn test_match_query_variable_via_data_variable() {
let mut space = GroundingSpace::new();
space.add(expr!(x x));
assert_eq!(space.query(&expr!(y (z))), bind_set![{y: expr!((z))}]);
}
#[test]
fn test_match_if_then_with_x() {
let mut space = GroundingSpace::new();
space.add(expr!("=" ("if" "True" then) then));
assert_eq!(space.query(&expr!("=" ("if" "True" "42") X)),
bind_set![{X: expr!("42")}]);
}
#[test]
fn test_match_combined_query() {
let mut space = GroundingSpace::new();
space.add(expr!("posesses" "Sam" "baloon"));
space.add(expr!("likes" "Sam" ("blue" "stuff")));
space.add(expr!("has-color" "baloon" "blue"));
let result = space.query(&expr!("," ("posesses" "Sam" object)
("likes" "Sam" (color "stuff"))
("has-color" object color)));
assert_eq!(result, bind_set![{object: expr!("baloon"), color: expr!("blue")}]);
}