Merge pull request #3147 from jjtolton/error-termination-flag
Add -t flag for custom toplevel (replaces --halt-on-error)
This commit is contained in:
@@ -16,6 +16,8 @@
|
|||||||
|
|
||||||
:- dynamic(disabled_init_file/0).
|
:- dynamic(disabled_init_file/0).
|
||||||
:- dynamic(started/0).
|
:- dynamic(started/0).
|
||||||
|
:- dynamic(custom_toplevel/1).
|
||||||
|
:- dynamic(g_caused_exception/2).
|
||||||
|
|
||||||
load_scryerrc :-
|
load_scryerrc :-
|
||||||
( '$home_directory'(HomeDir) ->
|
( '$home_directory'(HomeDir) ->
|
||||||
@@ -53,7 +55,18 @@ start_repl :-
|
|||||||
; true
|
; true
|
||||||
),
|
),
|
||||||
(\+ disabled_init_file -> load_scryerrc ; true),
|
(\+ disabled_init_file -> load_scryerrc ; true),
|
||||||
repl.
|
start_toplevel.
|
||||||
|
|
||||||
|
start_toplevel :-
|
||||||
|
( custom_toplevel(Goal) ->
|
||||||
|
catch(user:call(Goal),
|
||||||
|
Exception,
|
||||||
|
( print_exception(Exception),
|
||||||
|
halt(1)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
; repl
|
||||||
|
).
|
||||||
|
|
||||||
args_consults_goals([], [], []).
|
args_consults_goals([], [], []).
|
||||||
args_consults_goals([Arg|Args], Consults, Goals) :-
|
args_consults_goals([Arg|Args], Consults, Goals) :-
|
||||||
@@ -64,19 +77,19 @@ arg_consults_goals(c(Mod), Args, [c(Mod)|Consults], Goals) :-
|
|||||||
arg_consults_goals(g(Goal), Args, Consults, [g(Goal)|Goals]) :-
|
arg_consults_goals(g(Goal), Args, Consults, [g(Goal)|Goals]) :-
|
||||||
args_consults_goals(Args, Consults, Goals).
|
args_consults_goals(Args, Consults, Goals).
|
||||||
|
|
||||||
delegate_task([], []).
|
|
||||||
delegate_task([], Goals0) :-
|
delegate_task([], Goals0) :-
|
||||||
(\+ disabled_init_file -> load_scryerrc ; true),
|
(\+ disabled_init_file -> load_scryerrc ; true),
|
||||||
reverse(Goals0, Goals1),
|
reverse(Goals0, Goals1),
|
||||||
args_consults_goals(Goals1, Consults, Goals),
|
args_consults_goals(Goals1, Consults, Goals),
|
||||||
run_goals(Consults),
|
run_goals(Consults),
|
||||||
run_goals(Goals),
|
run_goals(Goals),
|
||||||
repl.
|
start_toplevel.
|
||||||
|
|
||||||
delegate_task([Arg0|Args], Goals0) :-
|
delegate_task([Arg0|Args], Goals0) :-
|
||||||
( ( member(Arg0, ["-h", "--help"]) -> print_help
|
( ( member(Arg0, ["-h", "--help"]) -> print_help
|
||||||
; member(Arg0, ["-v", "--version"]) -> print_version
|
; member(Arg0, ["-v", "--version"]) -> print_version
|
||||||
; member(Arg0, ["-g", "--goal"]) -> gather_goal(g, Args, Goals0)
|
; member(Arg0, ["-g", "--goal"]) -> gather_goal(g, Args, Goals0)
|
||||||
|
; member(Arg0, ["-t"]) -> gather_toplevel(Args, Goals0)
|
||||||
; member(Arg0, ["-f"]) -> disable_init_file
|
; member(Arg0, ["-f"]) -> disable_init_file
|
||||||
; member(Arg0, ["--no-add-history"]) -> ignore_machine_arg
|
; member(Arg0, ["--no-add-history"]) -> ignore_machine_arg
|
||||||
),
|
),
|
||||||
@@ -96,6 +109,8 @@ print_help :-
|
|||||||
write('Print version information and exit'), nl,
|
write('Print version information and exit'), nl,
|
||||||
write(' -g, --goal GOAL '),
|
write(' -g, --goal GOAL '),
|
||||||
write('Run the query GOAL'), nl,
|
write('Run the query GOAL'), nl,
|
||||||
|
write(' -t GOAL '),
|
||||||
|
write('Use GOAL as custom toplevel'), nl,
|
||||||
write(' -f '),
|
write(' -f '),
|
||||||
write('Fast startup. Do not load initialization file (~/.scryerrc)'), nl,
|
write('Fast startup. Do not load initialization file (~/.scryerrc)'), nl,
|
||||||
write(' --no-add-history '),
|
write(' --no-add-history '),
|
||||||
@@ -117,6 +132,17 @@ gather_goal(Type, Args0, Goals) :-
|
|||||||
Gs =.. [Type, Gs1],
|
Gs =.. [Type, Gs1],
|
||||||
delegate_task(Args, [Gs|Goals]).
|
delegate_task(Args, [Gs|Goals]).
|
||||||
|
|
||||||
|
gather_toplevel(Args0, Goals0) :-
|
||||||
|
length(Args0, N),
|
||||||
|
( N < 1 -> print_help, halt
|
||||||
|
; true
|
||||||
|
),
|
||||||
|
[TopLevel|Args] = Args0,
|
||||||
|
atom_chars(Goal, TopLevel),
|
||||||
|
retractall(custom_toplevel(_)),
|
||||||
|
asserta(custom_toplevel(Goal)),
|
||||||
|
delegate_task(Args, Goals0).
|
||||||
|
|
||||||
disable_init_file :-
|
disable_init_file :-
|
||||||
asserta('disabled_init_file').
|
asserta('disabled_init_file').
|
||||||
|
|
||||||
@@ -154,7 +180,8 @@ run_goals([g(Gs0)|Goals]) :- !,
|
|||||||
Exception,
|
Exception,
|
||||||
( write_term(Goal, [variable_names(VNs),double_quotes(DQ)]),
|
( write_term(Goal, [variable_names(VNs),double_quotes(DQ)]),
|
||||||
write(' causes: '),
|
write(' causes: '),
|
||||||
write_term(Exception, [double_quotes(DQ)]), nl % halt?
|
write_term(Exception, [double_quotes(DQ)]), nl,
|
||||||
|
asserta(g_caused_exception(Goal, Exception))
|
||||||
)
|
)
|
||||||
) -> true
|
) -> true
|
||||||
; write('% Warning: initialization failed for: '),
|
; write('% Warning: initialization failed for: '),
|
||||||
|
|||||||
48
tests/scryer/cli/fixtures/toplevel_test_helper.pl
Normal file
48
tests/scryer/cli/fixtures/toplevel_test_helper.pl
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
% Helper predicates for testing custom toplevel functionality
|
||||||
|
|
||||||
|
success_toplevel :-
|
||||||
|
format("SUCCESS_TOPLEVEL_EXECUTED~n", []),
|
||||||
|
halt(0).
|
||||||
|
|
||||||
|
failure_toplevel :-
|
||||||
|
format("FAILURE_TOPLEVEL_EXECUTED~n", []),
|
||||||
|
halt(1).
|
||||||
|
|
||||||
|
exit_code_42 :-
|
||||||
|
format("EXIT_CODE_42~n", []),
|
||||||
|
halt(42).
|
||||||
|
|
||||||
|
write_and_exit :-
|
||||||
|
format("Output from custom toplevel~n", []),
|
||||||
|
halt(0).
|
||||||
|
|
||||||
|
% This one doesn't halt - to test what happens if toplevel doesn't halt
|
||||||
|
non_halting_toplevel :-
|
||||||
|
format("NON_HALTING_TOPLEVEL~n", []).
|
||||||
|
|
||||||
|
% Test that toplevel can access loaded predicates
|
||||||
|
test_file_loaded :-
|
||||||
|
format("LOADED_PREDICATE_CALLED~n", []),
|
||||||
|
halt(0).
|
||||||
|
|
||||||
|
helper_predicate :-
|
||||||
|
format("Helper predicate works~n", []).
|
||||||
|
|
||||||
|
% g_caused_exception/2 testing predicates
|
||||||
|
check_exception_halt_1 :-
|
||||||
|
( '$toplevel':g_caused_exception(Goal, Exception) ->
|
||||||
|
format("EXCEPTION_CAUGHT~n", []),
|
||||||
|
format("Goal: ~w~n", [Goal]),
|
||||||
|
format("Exception: ~w~n", [Exception]),
|
||||||
|
halt(1)
|
||||||
|
; format("NO_EXCEPTION~n", []),
|
||||||
|
halt(0)
|
||||||
|
).
|
||||||
|
|
||||||
|
check_exception_halt_0 :-
|
||||||
|
( '$toplevel':g_caused_exception(_, _) ->
|
||||||
|
format("UNEXPECTED_EXCEPTION~n", []),
|
||||||
|
halt(1)
|
||||||
|
; format("SUCCESS_NO_EXCEPTION~n", []),
|
||||||
|
halt(0)
|
||||||
|
).
|
||||||
125
tests/scryer/cli/src_tests/custom_toplevel.md
Normal file
125
tests/scryer/cli/src_tests/custom_toplevel.md
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
# Custom Toplevel Tests
|
||||||
|
|
||||||
|
## Basic -t halt functionality
|
||||||
|
Test that -t halt prevents entering REPL and exits cleanly
|
||||||
|
|
||||||
|
```trycmd
|
||||||
|
$ scryer-prolog -f --no-add-history -t halt
|
||||||
|
```
|
||||||
|
|
||||||
|
## -t halt with successful goal
|
||||||
|
Test that -t halt exits after running a goal successfully
|
||||||
|
|
||||||
|
```trycmd
|
||||||
|
$ scryer-prolog -f --no-add-history -g "write('Goal executed')" -t halt
|
||||||
|
Goal executed
|
||||||
|
```
|
||||||
|
|
||||||
|
## -t halt with failing goal
|
||||||
|
Test that -t halt still exits even when goal fails
|
||||||
|
|
||||||
|
```trycmd
|
||||||
|
$ scryer-prolog -f --no-add-history -g "fail" -t halt
|
||||||
|
% Warning: initialization failed for: fail
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## Custom toplevel with exit code 0
|
||||||
|
Test custom toplevel that exits with code 0
|
||||||
|
|
||||||
|
```trycmd
|
||||||
|
$ scryer-prolog -f --no-add-history -t success_toplevel tests/scryer/cli/fixtures/toplevel_test_helper.pl
|
||||||
|
SUCCESS_TOPLEVEL_EXECUTED
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## Custom toplevel with file loading
|
||||||
|
Test that custom toplevel can access predicates from loaded file
|
||||||
|
|
||||||
|
```trycmd
|
||||||
|
$ scryer-prolog -f --no-add-history -t test_file_loaded tests/scryer/cli/fixtures/toplevel_test_helper.pl
|
||||||
|
LOADED_PREDICATE_CALLED
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## Custom toplevel with -g goal
|
||||||
|
Test combining -g goal with custom toplevel
|
||||||
|
|
||||||
|
```trycmd
|
||||||
|
$ scryer-prolog -f --no-add-history -g "helper_predicate" -t halt tests/scryer/cli/fixtures/toplevel_test_helper.pl
|
||||||
|
Helper predicate works
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## Multiple goals with custom toplevel
|
||||||
|
Test multiple -g goals before custom toplevel
|
||||||
|
|
||||||
|
```trycmd
|
||||||
|
$ scryer-prolog -f --no-add-history -g "write('First goal'), nl" -g "write('Second goal'), nl" -t halt tests/scryer/cli/fixtures/toplevel_test_helper.pl
|
||||||
|
First goal
|
||||||
|
Second goal
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## File loading then custom toplevel
|
||||||
|
Test that files are loaded before toplevel runs
|
||||||
|
|
||||||
|
```trycmd
|
||||||
|
$ scryer-prolog -f --no-add-history -t write_and_exit tests/scryer/cli/fixtures/toplevel_test_helper.pl
|
||||||
|
Output from custom toplevel
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## Undefined toplevel predicate
|
||||||
|
Test error handling when toplevel predicate doesn't exist
|
||||||
|
|
||||||
|
```trycmd
|
||||||
|
$ scryer-prolog -f --no-add-history -t undefined_predicate
|
||||||
|
? failed
|
||||||
|
error(existence_error(procedure,undefined_predicate/0),undefined_predicate/0).
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## Test that default behavior unchanged
|
||||||
|
Without -t flag, a simple goal should still work (using halt to avoid REPL)
|
||||||
|
|
||||||
|
```trycmd
|
||||||
|
$ scryer-prolog -f --no-add-history -g "write('No custom toplevel'), nl, halt"
|
||||||
|
No custom toplevel
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## g_caused_exception/2 with exception thrown
|
||||||
|
Test that g_caused_exception/2 is asserted when -g goal throws exception
|
||||||
|
|
||||||
|
```trycmd
|
||||||
|
$ scryer-prolog -f --no-add-history -g "throw(test_error)" -t check_exception_halt_1 tests/scryer/cli/fixtures/toplevel_test_helper.pl
|
||||||
|
? 1
|
||||||
|
throw(test_error) causes: test_error
|
||||||
|
EXCEPTION_CAUGHT
|
||||||
|
Goal: throw(test_error)
|
||||||
|
Exception: test_error
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## g_caused_exception/2 with no exception
|
||||||
|
Test that g_caused_exception/2 is not asserted when -g goal succeeds
|
||||||
|
|
||||||
|
```trycmd
|
||||||
|
$ scryer-prolog -f --no-add-history -g "write('Success')" -t check_exception_halt_0 tests/scryer/cli/fixtures/toplevel_test_helper.pl
|
||||||
|
SuccessSUCCESS_NO_EXCEPTION
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## g_caused_exception/2 with error() term
|
||||||
|
Test that g_caused_exception/2 captures error/2 terms correctly
|
||||||
|
|
||||||
|
```trycmd
|
||||||
|
$ scryer-prolog -f --no-add-history -g "throw(error(type_error(integer, foo), context))" -t check_exception_halt_1 tests/scryer/cli/fixtures/toplevel_test_helper.pl
|
||||||
|
? 1
|
||||||
|
throw(error(type_error(integer,foo),context)) causes: error(type_error(integer,foo),context)
|
||||||
|
EXCEPTION_CAUGHT
|
||||||
|
Goal: throw(error(type_error(integer,foo),context))
|
||||||
|
Exception: error(type_error(integer,foo),context)
|
||||||
|
|
||||||
|
```
|
||||||
Reference in New Issue
Block a user