interpl

Experiments with intepreters in Prolog
git clone https://ccx.te2000.cz/git/interpl
Log | Files | Refs | README

commit 93ae3831b08c7608410fa4ae463fb6d33282aa3d
parent 3da1eefff8ab0d6aafdb5d4073540bbb1bd901d8
Author: Jan Pobrislo <ccx@te2000.cz>
Date:   Thu,  5 Jun 2025 18:42:02 +0000

Add EDCG for environment and frame passing.

Diffstat:
Aedcg.pl | 539+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Minterpreter.pl | 25+++++++++++++++++++++----
2 files changed, 560 insertions(+), 4 deletions(-)

diff --git a/edcg.pl b/edcg.pl @@ -0,0 +1,539 @@ +:- module( edcg, [ + op(1200, xfx, '-->>'), % Similar to '-->' + op(1200, xfx, '==>>'), % Similar to '-->' + op( 990, fx, '?'), % For guards with '==>>' + edcg_import_sentinel/0 +]). + +% If running a version of SWI-Prolog older than 8.3.19, define the +% '=>' operator to prevent syntax errors in this module. The '==>>' +% operator is still defined in the module export, even though it'll +% generate a runtime error if it's used. +:- if(\+ current_op(_, _, '=>')). +:- op(1200, xfx, '=>'). +:- endif. + +:- use_module(library(debug), [debug/3]). +:- use_module(library(lists), [member/2, selectchk/3]). +:- use_module(library(apply), [maplist/3, maplist/4, foldl/4]). + +% These predicates define extra arguments and are defined in the +% modules that use the edcg module. +:- multifile + acc_info/5, + acc_info/7, + pred_info/3, + pass_info/1, + pass_info/2. + +:- multifile + prolog_clause:make_varnames_hook/5, + prolog_clause:unify_clause_hook/5. + +% True if the module being read has opted-in to EDCG macro expansion. +wants_edcg_expansion :- + prolog_load_context(module, Module), + wants_edcg_expansion(Module). + +wants_edcg_expansion(Module) :- + Module \== edcg, % don't expand macros in our own library + predicate_property(Module:edcg_import_sentinel, imported_from(edcg)). + +% dummy predicate exported to detect which modules want EDCG expansion +edcg_import_sentinel. + + +% term_expansion/4 is used to work around SWI-Prolog's attempts to +% match variable names when doing a listing (or interactive trace) and +% getting confused; this sometimes results in a strange error message +% for an unknown extended_pos(Pos,N). + +% Returning a variable for _Layout2 means "I don't know". +% See https://swi-prolog.discourse.group/t/strange-warning-message-from-compile-or-listing/3774 +user:term_expansion(Term, Layout0, Expansion, Layout) :- + wants_edcg_expansion, + edcg_expand_clause(Term, Expansion, Layout0, Layout). + +% TODO: +% prolog_clause:unify_clause_hook(Read, Decompiled, Module, TermPos0, TermPos) :- +% wants_edcg_expansion(Module), +% edcg_expand_clause(Read, Decompiled, TermPos0, TermPos). + +% TODO: +% prolog_clause:make_varnames_hook(ReadClause, DecompiledClause, Offsets, Names, Term) :- ... + +% TODO: support ((H,PB-->>B) [same as regular DCG] +edcg_expand_clause((H-->>B), Expansion, TermPos0, _) :- + edcg_expand_clause_wrap((H-->>B), Expansion, TermPos0, _). +edcg_expand_clause((H,PB==>>B), Expansion, TermPos0, _) :- + edcg_expand_clause_wrap((H,PB==>>B), Expansion, TermPos0, _). +edcg_expand_clause((H==>>B), Expansion, TermPos0, _) :- + edcg_expand_clause_wrap((H==>>B), Expansion, TermPos0, _). + +edcg_expand_clause_wrap(Term, Expansion, TermPos0, TermPos) :- + % ( valid_termpos(Term, TermPos0) % for debugging + % -> true + % ; throw(error(invalid_termpos_read(Term,TermPos0), _)) + % ), + ( '_expand_clause'(Term, Expansion, TermPos0, TermPos) + -> true + ; throw(error('FAILED_expand_clause'(Term, Expansion, TermPos0, TermPos), _)) + ), + % ( valid_termpos(Expansion, TermPos) % for debugging + % -> true + % ; throw(error(invalid_termpos_expansion(Expansion, TermPos), _)) + % ). + true. + +% :- det('_expand_clause'/4). +% Perform EDCG macro expansion +% TODO: support ((H,PB-->>B) [same as regular DCG] +'_expand_clause'((H-->>B), Expansion, TermPos0, TermPos) => + TermPos0 = term_position(From,To,ArrowFrom,ArrowTo,[H_pos,B_pos]), + TermPos = term_position(From,To,ArrowFrom,ArrowTo,[Hx_pos,Bx_pos]), + Expansion = (TH:-TB), + '_expand_head_body'(H, B, TH, TB, NewAcc, H_pos,B_pos, Hx_pos,Bx_pos), + '_finish_acc'(NewAcc), + !. +'_expand_clause'((H,PB==>>B), Expansion, _TermPos0, _) => % TODO TermPos + % '==>>'(',',(H,PB),B) + Expansion = (TH,Guards=>TB2), + '_expand_guard'(PB, Guards), + '_expand_head_body'(H, B, TH, TB, NewAcc, _H_pos,_B_pos, _Hx_pos,_Bx_pos), + '_finish_acc_ssu'(NewAcc, TB, TB2), + !. +'_expand_clause'((H==>>B), Expansion, TermPos0, TermPos) => + TermPos0 = term_position(From,To,ArrowFrom,ArrowTo,[H_pos,B_pos]), + TermPos = term_position(From,To,ArrowFrom,ArrowTo,[Hx_pos,Bx_pos]), + Expansion = (TH=>TB2), + '_expand_head_body'(H, B, TH, TB, NewAcc, H_pos,B_pos, Hx_pos,Bx_pos), + '_finish_acc_ssu'(NewAcc, TB, TB2), + !. + +:- det('_expand_guard'/2). +% TODO: Do we want to expand the guards? +% For now, just verify that they all start with '?' +'_expand_guard'((?G0,G2), Expansion) => + Expansion = (G, GE2), + '_expand_guard_curly'(G0, G), + '_expand_guard'(G2, GE2). +'_expand_guard'(?G0, G) => + '_expand_guard_curly'(G0, G). +'_expand_guard'(G, _) => + throw(error(type_error(guard,G),_)). + +:- det('_expand_guard_curly'/2). +'_expand_guard_curly'({G}, G) :- !. +'_expand_guard_curly'(G, G). + + +:- det('_expand_head_body'/9). +'_expand_head_body'(H, B, TH, TB, NewAcc, _H_pos,_B_pos, _Hx_pos,_Bx_pos) :- + functor(H, Na, Ar), + '_has_hidden'(H, HList), % TODO: can backtrack - should it? + debug(edcg,'Expanding ~w',[H]), + '_new_goal'(H, HList, HArity, TH), + '_create_acc_pass'(HList, HArity, TH, Acc, Pass), + '_expand_goal'(B, TB, Na/Ar, HList, Acc, NewAcc, Pass), + !. + +% Expand a goal: +'_expand_goal'((G1,G2), Expansion, NaAr, HList, Acc, NewAcc, Pass) => + Expansion = (TG1,TG2), + '_expand_goal'(G1, TG1, NaAr, HList, Acc, MidAcc, Pass), + '_expand_goal'(G2, TG2, NaAr, HList, MidAcc, NewAcc, Pass). +'_expand_goal'((G1->G2;G3), Expansion, NaAr, HList, Acc, NewAcc, Pass) => + Expansion = (TG1->TG2;TG3), + '_expand_goal'(G1, TG1, NaAr, HList, Acc, MidAcc, Pass), + '_expand_goal'(G2, MG2, NaAr, HList, MidAcc, Acc1, Pass), + '_expand_goal'(G3, MG3, NaAr, HList, Acc, Acc2, Pass), + '_merge_acc'(Acc, Acc1, MG2, TG2, Acc2, MG3, TG3, NewAcc). +'_expand_goal'((G1*->G2;G3), Expansion, NaAr, HList, Acc, NewAcc, Pass) => + Expansion = (TG1*->TG2;TG3), + '_expand_goal'(G1, TG1, NaAr, HList, Acc, MidAcc, Pass), + '_expand_goal'(G2, MG2, NaAr, HList, MidAcc, Acc1, Pass), + '_expand_goal'(G3, MG3, NaAr, HList, Acc, Acc2, Pass), + '_merge_acc'(Acc, Acc1, MG2, TG2, Acc2, MG3, TG3, NewAcc). +'_expand_goal'((G1;G2), Expansion, NaAr, HList, Acc, NewAcc, Pass) => + Expansion = (TG1;TG2), + '_expand_goal'(G1, MG1, NaAr, HList, Acc, Acc1, Pass), + '_expand_goal'(G2, MG2, NaAr, HList, Acc, Acc2, Pass), + '_merge_acc'(Acc, Acc1, MG1, TG1, Acc2, MG2, TG2, NewAcc). +'_expand_goal'((G1->G2), Expansion, NaAr, HList, Acc, NewAcc, Pass) => + Expansion = (TG1->TG2), + '_expand_goal'(G1, TG1, NaAr, HList, Acc, MidAcc, Pass), + '_expand_goal'(G2, TG2, NaAr, HList, MidAcc, NewAcc, Pass). +'_expand_goal'((G1*->G2), Expansion, NaAr, HList, Acc, NewAcc, Pass) => + Expansion = (TG1*->TG2), + '_expand_goal'(G1, TG1, NaAr, HList, Acc, MidAcc, Pass), + '_expand_goal'(G2, TG2, NaAr, HList, MidAcc, NewAcc, Pass). +'_expand_goal'((\+G), Expansion, NaAr, HList, Acc, NewAcc, Pass) => + Expansion = (\+TG), + NewAcc = Acc, + '_expand_goal'(G, TG, NaAr, HList, Acc, _TempAcc, Pass). +'_expand_goal'({G}, Expansion, _, _, Acc, NewAcc, _) => + Expansion = G, + NewAcc = Acc. +'_expand_goal'(insert(X,Y), Expansion, _, _, Acc, NewAcc, _) => + Expansion = (LeftA=X), + '_replace_acc'(dcg, LeftA, RightA, Y, RightA, Acc, NewAcc), !. +'_expand_goal'(insert(X,Y):A, Expansion, _, _, Acc, NewAcc, _) => + Expansion = (LeftA=X), + '_replace_acc'(A, LeftA, RightA, Y, RightA, Acc, NewAcc), + debug(edcg,'Expanding accumulator goal: ~w',[insert(X,Y):A]), + !. +% Force hidden arguments in L to be appended to G: +'_expand_goal'((G:A), TG, _, _HList, Acc, NewAcc, Pass), + \+'_list'(G), + '_has_hidden'(G, []) => + '_make_list'(A, AList), + '_new_goal'(G, AList, GArity, TG), + '_use_acc_pass'(AList, GArity, TG, Acc, NewAcc, Pass). +% Use G's regular hidden arguments & override defaults for those arguments +% not in the head: +'_expand_goal'((G:A), TG, _, _HList, Acc, NewAcc, Pass), + \+'_list'(G), + '_has_hidden'(G, GList), GList\==[] => + '_make_list'(A, L), + '_new_goal'(G, GList, GArity, TG), + '_replace_defaults'(GList, NGList, L), + '_use_acc_pass'(NGList, GArity, TG, Acc, NewAcc, Pass). +'_expand_goal'((L:A), Joiner, NaAr, _, Acc, NewAcc, _), + '_list'(L) => + '_joiner'(L, A, NaAr, Joiner, Acc, NewAcc). +'_expand_goal'(L, Joiner, NaAr, _, Acc, NewAcc, _), + '_list'(L) => + '_joiner'(L, dcg, NaAr, Joiner, Acc, NewAcc). +'_expand_goal'((X/A), Expansion, _, _, Acc, NewAcc, _), + atomic(A), + member(acc(A,X,_), Acc) => + Expansion = true, + NewAcc = Acc, + debug(edcg,'Expanding accumulator goal: ~w',[X/A]), + !. +'_expand_goal'((X/A), Expansion, _, _, Acc, NewAcc, Pass), + atomic(A), + member(pass(A,X), Pass) => + Expansion = true, + NewAcc = Acc, + debug(edcg,'Expanding passed argument goal: ~w',[X/A]), + !. +'_expand_goal'((A/X), Expansion, _, _, Acc, NewAcc, _), + atomic(A), + member(acc(A,_,X), Acc) => + Expansion = true, + NewAcc = Acc. +'_expand_goal'((X/A/Y), Expansion, _, _, Acc, NewAcc, _), + member(acc(A,X,Y), Acc), + var(X), var(Y), atomic(A) => + Expansion = true, + NewAcc = Acc. +'_expand_goal'((X/Y), true, NaAr, _, Acc, NewAcc, _) => + NewAcc = Acc, + print_message(warning,missing_hidden_parameter(NaAr,X/Y)). +% Defaulty cases: +'_expand_goal'(G, TG, _HList, _, Acc, NewAcc, Pass) => + '_has_hidden'(G, GList), !, + '_new_goal'(G, GList, GArity, TG), + '_use_acc_pass'(GList, GArity, TG, Acc, NewAcc, Pass). + +% ==== The following was originally acc-pass.pl ==== + +% Operations on the Acc and Pass data structures: + +:- det('_create_acc_pass'/5). +% Create the Acc and Pass data structures: +% Acc contains terms of the form acc(A,LeftA,RightA) where A is the name of an +% accumulator, and RightA and LeftA are the accumulating parameters. +% Pass contains terms of the form pass(A,Arg) where A is the name of a passed +% argument, and Arg is the argument. +'_create_acc_pass'([], _, _, Acc, Pass) => + Acc = [], + Pass = []. +'_create_acc_pass'([A|AList], Index, TGoal, Acc2, Pass), + '_is_acc'(A) => + Acc2 = [acc(A,LeftA,RightA)|Acc], + Index1 is Index+1, + arg(Index1, TGoal, LeftA), + Index2 is Index+2, + arg(Index2, TGoal, RightA), + '_create_acc_pass'(AList, Index2, TGoal, Acc, Pass). +'_create_acc_pass'([A|AList], Index, TGoal, Acc, Pass2), + '_is_pass'(A) => + Pass2 = [pass(A,Arg)|Pass], + Index1 is Index+1, + arg(Index1, TGoal, Arg), + '_create_acc_pass'(AList, Index1, TGoal, Acc, Pass). +'_create_acc_pass'([A|_AList], _Index, _TGoal, _Acc, _Pass), + \+'_is_acc'(A), + \+'_is_pass'(A) => + print_message(error,not_a_hidden_param(A)). + + +:- det('_use_acc_pass'/6). +% Use the Acc and Pass data structures to create the arguments of a body goal: +% Add the hidden parameters named in GList to the goal. +'_use_acc_pass'([], _, _, Acc, NewAcc, _) => + NewAcc = Acc. +% 1a. The accumulator A is used in the head: +% Note: the '_replace_acc' guard instantiates MidAcc +'_use_acc_pass'([A|GList], Index, TGoal, Acc, NewAcc, Pass), + '_replace_acc'(A, LeftA, RightA, MidA, RightA, Acc, MidAcc) => + Index1 is Index+1, + arg(Index1, TGoal, LeftA), + Index2 is Index+2, + arg(Index2, TGoal, MidA), + '_use_acc_pass'(GList, Index2, TGoal, MidAcc, NewAcc, Pass). +% 1b. The accumulator A is not used in the head: +'_use_acc_pass'([A|GList], Index, TGoal, Acc, NewAcc, Pass), + '_acc_info'(A, LStart, RStart) => + Index1 is Index+1, + arg(Index1, TGoal, LStart), + Index2 is Index+2, + arg(Index2, TGoal, RStart), + '_use_acc_pass'(GList, Index2, TGoal, Acc, NewAcc, Pass). +% 2a. The passed argument A is used in the head: +'_use_acc_pass'([A|GList], Index, TGoal, Acc, NewAcc, Pass), + '_is_pass'(A), + member(pass(A,Arg), Pass) => + Index1 is Index+1, + arg(Index1, TGoal, Arg), + '_use_acc_pass'(GList, Index1, TGoal, Acc, NewAcc, Pass). +% 2b. The passed argument A is not used in the head: +'_use_acc_pass'([A|GList], Index, TGoal, Acc, NewAcc, Pass), + '_pass_info'(A, AStart) => + Index1 is Index+1, + arg(Index1, TGoal, AStart), + '_use_acc_pass'(GList, Index1, TGoal, Acc, NewAcc, Pass). +% 3. Defaulty case when A does not exist: +'_use_acc_pass'([A|_GList], _Index, _TGoal, Acc, Acc, _Pass) => + print_message(error,not_a_hidden_param(A)). + +:- det('_finish_acc'/1). +% Finish the Acc data structure: +% Link its Left and Right accumulation variables together in pairs: +% TODO: does this work correctly in the presence of cuts? ("!") - see README +'_finish_acc'([]). +'_finish_acc'([acc(_,Link,Link)|Acc]) :- '_finish_acc'(Acc). + +:- det('_finish_acc_ssu'/3). +'_finish_acc_ssu'([], TB, TB). +'_finish_acc_ssu'([acc(_,Link0,Link1)|Acc], TB0, TB) :- + '_finish_acc_ssu'(Acc, (Link0=Link1,TB0), TB). + +% Replace elements in the Acc data structure: +% Succeeds iff replacement is successful. +'_replace_acc'(A, L1, R1, L2, R2, Acc, NewAcc) :- + member(acc(A,L1,R1), Acc), !, + '_replace'(acc(A,_,_), acc(A,L2,R2), Acc, NewAcc). + +:- det('_merge_acc'/8). +% Combine two accumulator lists ('or'ing their values) +'_merge_acc'([], [], G1, G1, [], G2, G2, []) :- !. +'_merge_acc'([acc(Acc,OL,R)|Accs], [acc(Acc,L1,R)|Accs1], G1, NG1, + [acc(Acc,L2,R)|Accs2], G2, NG2, [acc(Acc,NL,R)|NewAccs]) :- !, + ( ( OL == L1, OL \== L2 ) -> + MG1 = (G1,L1=L2), MG2 = G2, NL = L2 + ; ( OL == L2, OL \== L1 ) -> + MG2 = (G2,L2=L1), MG1 = G1, NL = L1 + ; MG1 = G1, MG2 = G2, L1 = L2, L2 = NL ), + '_merge_acc'(Accs, Accs1, MG1, NG1, Accs2, MG2, NG2, NewAccs). + +% ==== The following was originally generic-util.pl ==== + +% Generic utilities special-util.pl + +:- det('_match'/4). +% Match arguments L, L+1, ..., H of the predicates P and Q: +'_match'(L, H, _, _) :- L>H, !. +'_match'(L, H, P, Q) :- L=<H, !, + arg(L, P, A), + arg(L, Q, A), + L1 is L+1, + '_match'(L1, H, P, Q). + + +'_list'(L) :- nonvar(L), L=[_|_], !. +'_list'(L) :- L==[], !. + +:- det('_make_list'/2). +'_make_list'(A, [A]) :- \+'_list'(A), !. +'_make_list'(L, L) :- '_list'(L), !. + +:- det('_replace'/4). +% replace(Elem, RepElem, List, RepList) +'_replace'(_, _, [], []) :- !. +'_replace'(A, B, [A|L], [B|R]) :- !, + '_replace'(A, B, L, R). +'_replace'(A, B, [C|L], [C|R]) :- + \+C=A, !, + '_replace'(A, B, L, R). + +% ==== The following was originally special-util.pl ==== + +% Specialized utilities: + +% Given a goal Goal and a list of hidden parameters GList +% create a new goal TGoal with the correct number of arguments. +% Also return the arity of the original goal. +'_new_goal'(Goal, GList, GArity, TGoal) :- + functor(Goal, Name, GArity), + '_number_args'(GList, GArity, TArity), + functor(TGoal, Name, TArity), + '_match'(1, GArity, Goal, TGoal). + +% Add the number of arguments needed for the hidden parameters: +'_number_args'([], N, N). +'_number_args'([A|List], N, M) :- + '_is_acc'(A), !, + N2 is N+2, + '_number_args'(List, N2, M). +'_number_args'([A|List], N, M) :- + '_is_pass'(A), !, + N1 is N+1, + '_number_args'(List, N1, M). +'_number_args'([_|List], N, M) :- !, + % error caught elsewhere + '_number_args'(List, N, M). + +% Give a list of G's hidden parameters: +'_has_hidden'(G, GList) :- + functor(G, GName, GArity), + ( pred_info(GName, GArity, GList) + -> true + ; GList = [] + ). + +% Succeeds if A is an accumulator: +'_is_acc'(A), atomic(A) => '_acc_info'(A, _, _, _, _, _, _). +'_is_acc'(A), functor(A, N, 2) => '_acc_info'(N, _, _, _, _, _, _). + +% Succeeds if A is a passed argument: +'_is_pass'(A), atomic(A) => '_pass_info'(A, _). +'_is_pass'(A), functor(A, N, 1) => '_pass_info'(N, _). + +% Get initial values for the accumulator: +'_acc_info'(AccParams, LStart, RStart) :- + functor(AccParams, Acc, 2), + '_is_acc'(Acc), !, + arg(1, AccParams, LStart), + arg(2, AccParams, RStart). +'_acc_info'(Acc, LStart, RStart) :- + '_acc_info'(Acc, _, _, _, _, LStart, RStart). + +% Isolate the internal database from the user database: +'_acc_info'(Acc, Term, Left, Right, Joiner, LStart, RStart) :- + acc_info(Acc, Term, Left, Right, Joiner, LStart, RStart). +'_acc_info'(Acc, Term, Left, Right, Joiner, _, _) :- + acc_info(Acc, Term, Left, Right, Joiner). +'_acc_info'(dcg, Term, Left, Right, Left=[Term|Right], _, []). + +% Get initial value for the passed argument: +% Also, isolate the internal database from the user database. +'_pass_info'(PassParam, PStart) :- + functor(PassParam, Pass, 1), + '_is_pass'(Pass), !, + arg(1, PassParam, PStart). +'_pass_info'(Pass, PStart) :- + pass_info(Pass, PStart). +'_pass_info'(Pass, _) :- + pass_info(Pass). + +% Calculate the joiner for an accumulator A: +'_joiner'([], _, _, true, Acc, Acc). +'_joiner'([Term|List], A, NaAr, (Joiner,LJoiner), Acc, NewAcc) :- + '_replace_acc'(A, LeftA, RightA, MidA, RightA, Acc, MidAcc), + '_acc_info'(A, Term, LeftA, MidA, Joiner, _, _), !, + '_joiner'(List, A, NaAr, LJoiner, MidAcc, NewAcc). +% Defaulty case: +'_joiner'([_Term|List], A, NaAr, Joiner, Acc, NewAcc) :- + print_message(warning, missing_accumulator(NaAr,A)), + '_joiner'(List, A, NaAr, Joiner, Acc, NewAcc). + +% Replace hidden parameters with ones containing initial values: +'_replace_defaults'([], [], _). +'_replace_defaults'([A|GList], [NA|NGList], AList) :- + '_replace_default'(A, NA, AList), + '_replace_defaults'(GList, NGList, AList). + +'_replace_default'(A, NewA, AList) :- % New initial values for accumulator. + functor(NewA, A, 2), + member(NewA, AList), !. +'_replace_default'(A, NewA, AList) :- % New initial values for passed argument. + functor(NewA, A, 1), + member(NewA, AList), !. +'_replace_default'(A, NewA, _) :- % Use default initial values. + A=NewA. + +% ==== The following was originally messages.pl ==== + +:- multifile prolog:message//1. + +prolog:message(missing_accumulator(Predicate,Accumulator)) --> + ['In ~w the accumulator ''~w'' does not exist'-[Predicate,Accumulator]]. +prolog:message(missing_hidden_parameter(Predicate,Term)) --> + ['In ~w the term ''~w'' uses a non-existent hidden parameter.'-[Predicate,Term]]. +prolog:message(not_a_hidden_param(Name)) --> + ['~w is not a hidden parameter'-[Name]]. +% === The following are for debugging term_expansion/4 + +% :- det(valid_termpos/2). % DO NOT SUBMIT +%! valid_termpos(+Term, ?TermPos) is semidet. +% Checks that a Term has an appropriate TermPos. +% This should always succeed: +% read_term(Term, [subterm_positions(TermPos)]), +% valid_termpos(Term, TermPos) +% Note that this can create a TermPos. Each clause ends with +% a cut, to avoid unneeded backtracking. +valid_termpos(Term, TermPos) :- + ( valid_termpos_(Term, TermPos) + -> true + ; fail % throw(error(invalid_termpos(Term,TermPos), _)) % DO NOT SUBMIT + ). + +valid_termpos_(Var, _From-_To) :- var(Var). +valid_termpos_(Atom, _From-_To) :- atom(Atom), !. +valid_termpos_(Number, _From-_To) :- number(Number), !. +valid_termpos_(String, string_position(_From,_To)) :- string(String), !. +valid_termpos_([], _From-_To) :- !. +valid_termpos_({Arg}, brace_term_position(_From,_To,ArgPos)) :- + valid_termpos(Arg, ArgPos), !. +% TODO: combine the two list_position clauses +valid_termpos_([Hd|Tl], list_position(_From,_To, ElemsPos, none)) :- + maplist(valid_termpos, [Hd|Tl], ElemsPos), + list_tail([Hd|Tl], _, []), !. +valid_termpos_([Hd|Tl], list_position(_From,_To, ElemsPos, TailPos)) :- + list_tail([Hd|Tl], HdPart, Tail), + tailPos \= none, Tail \= [], + maplist(valid_termpos, HdPart, ElemsPos), + valid_termpos(Tail, TailPos), !. +valid_termpos_(Term, term_position(_From,_To, FFrom,FTo,SubPos)) :- + compound_name_arguments(Term, Name, Arguments), + valid_termpos(Name, FFrom-FTo), + maplist(valid_termpos, Arguments, SubPos), !. +valid_termpos_(Dict, dict_position(_From,_To,TagFrom,TagTo,KeyValuePosList)) :- + dict_pairs(Dict, Tag, Pairs), + valid_termpos(Tag, TagFrom-TagTo), + foldl(valid_termpos_dict, Pairs, KeyValuePosList, []), !. +% key_value_position(From, To, SepFrom, SepTo, Key, KeyPos, ValuePos) is handled +% in valid_termpos_dict. +valid_termpos_(Term, parentheses_term_position(_From,_To,ContentPos)) :- + valid_termpos(Term, ContentPos), !. +% TODO: documentation for quasi_quotation_position is wrong (SyntaxTo,SyntaxFrom should be SYntaxTerm,SyntaxPos). +valid_termpos_(_Term, quasi_quotation_position(_From,_To,SyntaxTerm,SyntaxPos,_ContentPos)) :- + valid_termpos(SyntaxTerm, SyntaxPos), !. + +:- det(valid_termpos_dict/3). +valid_termpos_dict(Key-Value, KeyValuePosList0, KeyValuePosList1) :- + selectchk(key_value_position(_From,_To,_SepFrom,_SepTo,Key,KeyPos,ValuePos), + KeyValuePosList0, KeyValuePosList1), + valid_termpos(Key, KeyPos), + valid_termpos(Value, ValuePos). + +:- det(list_tail/3). +list_tail([X|Xs], HdPart, Tail) => + HdPart = [X|HdPart2], + list_tail(Xs, HdPart2, Tail). +list_tail(Tail0, HdPart, Tail) => HdPart = [], Tail0 = Tail. + +end_of_file. diff --git a/interpreter.pl b/interpreter.pl @@ -14,6 +14,7 @@ :- use_module(library(pairs)). :- use_module(interp_errors). +:- use_module(edcg). %%%% conditionally throw errors interp_check(Condition, Kind, ErrorData) :- @@ -28,6 +29,22 @@ validate_and_conditional_throw(Condition, Error, Caller, Message) :- validate_error(Error, Caller), ( call(Condition) -> true ; throw(error(Error, context(Caller, Message)))). +%%%% Extended DCG notation + +% Declare accumulators +edcg:acc_info(env, _X, _Env0, _Env1, assertion(false)). +edcg:acc_info(frames, _X, _Frames0, _Frames1, assertion(false)). + +% Declare predicates +edcg:pred_info(extend_environment, 2, [env]). +edcg:pred_info(lookup_variable_value, 2, [env]). +edcg:pred_info(define_variable, 2, [env]). +edcg:pred_info(set_variable_value, 2, [env]). +edcg:pred_info(interp_eval_, 2, [env]). +edcg:pred_info(interp_eval, 2, [env]). +edcg:pred_info(eval_cond, 2, [env]). +edcg:pred_info(eval_sequence, 2, [env]). + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Self-evaluating expressions @@ -50,13 +67,13 @@ empty_environment([]). % (extend-environment '(a) '(1) '()) % => '(((a) . (1)) . ()) -extend_environment(OldEnv, VarNames, Values, [NewFrame|OldEnv]) :- +extend_environment(VarNames, Values, OldEnv, [NewFrame|OldEnv]) :- length(VarNames, VarLength), length(Values, ValLength), assertion(VarLength =@= ValLength), pairs_keys_values(NewFrame, VarNames, Values). -lookup_variable_value(Env, VarName, Value) :- +lookup_variable_value(VarName, Value, Env, Env) :- interp_check(lookup_variable_value_aux(Env, VarName, Value), unbound_var, VarName). lookup_variable_value_aux([Frame|Env], VarName, Value) :- @@ -115,7 +132,7 @@ interp_eval_([quote, Text], Text, Env, Env) :- !. interp_eval_(VarName, Result, Env, Env) :- variable(VarName), !, - lookup_variable_value(Env, VarName, Result). + lookup_variable_value(VarName, Result, Env, Env). interp_eval_([Special|Tail], Result, Env, NewEnv) :- is_eval_special(Special), !, @@ -164,7 +181,7 @@ interp_apply_(primitive(Name, Arity), Arguments, Result) :- interp_check(Arity = ALength, arity, Name), interp_check(primitive_apply(Name, Arguments, Result), primitive, Name). interp_apply_(proc(Parameters, Body, Env), Arguments, Result) :- - extend_environment(Env, Parameters, Arguments, Env1), + extend_environment(Parameters, Arguments, Env, Env1), eval_sequence(Body, Result, Env1, _Env2). interp_apply(Proc, Arguments, Result) :-