interpl

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

commit b87864f34e921beea292d5e3c679ad406242bfde
parent 93ae3831b08c7608410fa4ae463fb6d33282aa3d
Author: Jan Pobrislo <ccx@te2000.cz>
Date:   Thu,  5 Jun 2025 23:57:59 +0000

Use swipl dict for state instead of edcg

Diffstat:
Minterpreter.pl | 191++++++++++++++++++++++++++++++++++++++++++-------------------------------------
Mtests.pl | 29++++++++++++++++++-----------
2 files changed, 120 insertions(+), 100 deletions(-)

diff --git a/interpreter.pl b/interpreter.pl @@ -3,9 +3,9 @@ [ parse_input_expression/2 , sexp//1 , sexp/4 - , interp_apply/3 + , interp_apply/5 , interp_eval/4 - , the_global_environment/1 + , initial_state/1 , primitive_arity/2 ]). :- use_module(library(debug), [assertion/1]). @@ -14,9 +14,9 @@ :- use_module(library(pairs)). :- use_module(interp_errors). -:- use_module(edcg). -%%%% conditionally throw errors +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% conditionally throw errors interp_check(Condition, Kind, ErrorData) :- parent_name(Caller), validate_and_conditional_throw(Condition, interp_error(Kind, ErrorData), Caller, _). @@ -29,21 +29,24 @@ validate_and_conditional_throw(Condition, Error, Caller, Message) :- validate_error(Error, Caller), ( call(Condition) -> true ; throw(error(Error, context(Caller, Message)))). -%%%% Extended DCG notation +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Interpreter state +check_state(State, State) :- + check_state(State). + +check_state(State) :- + assertion(check_state_aux(State)). -% Declare accumulators -edcg:acc_info(env, _X, _Env0, _Env1, assertion(false)). -edcg:acc_info(frames, _X, _Frames0, _Frames1, assertion(false)). +check_state_aux(state{env:Environment}) :- + ground(Environment), + is_list(Environment). -% 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]). +initial_state(state{env:Environment}) :- + setup_environment(Environment). + +:=(DictKey, Value, StateIn, StateOut) :- + assertion(is_dict(StateIn, state)), + put_dict(DictKey, StateIn, Value, StateOut). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Self-evaluating expressions @@ -67,14 +70,16 @@ empty_environment([]). % (extend-environment '(a) '(1) '()) % => '(((a) . (1)) . ()) -extend_environment(VarNames, Values, OldEnv, [NewFrame|OldEnv]) :- +extend_environment(VarNames, Values, StateIn, StateOut) :- + OldEnv = StateIn.env, + StateOut = StateIn.put(env, [NewFrame|OldEnv]), length(VarNames, VarLength), length(Values, ValLength), assertion(VarLength =@= ValLength), pairs_keys_values(NewFrame, VarNames, Values). -lookup_variable_value(VarName, Value, Env, Env) :- - interp_check(lookup_variable_value_aux(Env, VarName, Value), unbound_var, VarName). +lookup_variable_value(VarName, Value, State, State) :- + interp_check(lookup_variable_value_aux(State.env, VarName, Value), unbound_var, VarName). lookup_variable_value_aux([Frame|Env], VarName, Value) :- lookup_variable_from_frame(Frame, VarName, Value) -> true ; lookup_variable_value_aux(Env, VarName, Value). @@ -84,13 +89,15 @@ lookup_variable_from_frame([K-V|Rest], VarName, Value) :- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Definitions -define_variable(VarName, Value, [OldFrame|EnvTail], [[VarName-Value|OldFrame]|EnvTail]). +define_variable(VarName, Value, StateIn, StateOut) :- + StateIn.env = [OldFrame|EnvTail], + StateOut = StateIn.put(env, [[VarName-Value|OldFrame]|EnvTail]). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Assignments -set_variable_value(VarName, Value, OldEnv, NewEnv) :- - interp_check(set_variable_value_aux(VarName, Value, OldEnv, NewEnv), unbound_var, VarName). +set_variable_value(VarName, Value, StateIn, StateOut) :- + interp_check(set_variable_value_aux(VarName, Value, StateIn, StateOut), unbound_var, VarName). set_variable_value_aux(VarName, Value, [OldFrame|OldE], [NewFrame|NewE]) :- ( set_variable_in_frame(VarName, Value, OldFrame, NewFrame) @@ -108,7 +115,7 @@ set_variable_in_frame(VarName, Value, [K-_OldV|OldT], [K-NewV|NewT]) :- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Procedure Construction -make_procedure(Parameters, Body, Env, proc(Parameters, Body, Env)). +make_procedure(Parameters, Body, State, proc(Parameters, Body, State.env)). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Application Expressions @@ -126,82 +133,85 @@ is_eval_special(if). is_eval_special(cond). is_eval_special('set!'). -interp_eval_(Expression, Expression, Env, Env) :- - self_evaluating(Expression), !. -interp_eval_([quote, Text], Text, Env, Env) :- !. -interp_eval_(VarName, Result, Env, Env) :- - variable(VarName), - !, - lookup_variable_value(VarName, Result, Env, Env). -interp_eval_([Special|Tail], Result, Env, NewEnv) :- - is_eval_special(Special), - !, - interp_check(interp_eval_special(Special, Tail, Result, Env, NewEnv), eval, [Special|Tail]). -interp_eval_([Operator|Operands], Result, Env, NewEnv) :- - !, - interp_eval(Operator, Procedure, Env, NewEnv), - list_of_values(Operands, Env, Arguments), +expression_type(Expression, selfeval) :- self_evaluating(Expression), !. +expression_type([quote|_Text], quote) :- !. +expression_type(VarName, variable) :- variable(VarName), !. +expression_type([Special|_Tail], special) :- is_eval_special(Special), !. +expression_type([_Operator|_Operands], operator) :- !. + +interp_eval_(selfeval, Expression, Expression, State, State). +interp_eval_(quote, [quote, Text], Text, State, State). +interp_eval_(variable, VarName, Result) --> + lookup_variable_value(VarName, Result). +interp_eval_(special, [Special|Tail], Result, StateIn, StateOut) :- + interp_check(interp_eval_special(Special, Tail, Result, StateIn, StateOut), eval, [Special|Tail]). +interp_eval_(operator, [Operator|Operands], Result) --> + interp_eval(Operator, Procedure), + list_of_values(Operands, Arguments), interp_apply(Procedure, Arguments, Result). -interp_eval_(Expression, _, _, _) :- - interp_error(eval, Expression). -interp_eval_special('set!', [VarName, Expression], '#void', Env, NewEnv) :- - interp_eval(Expression, Result, Env, Env1), - set_variable_value(VarName, Result, Env1, NewEnv). +interp_eval_special('set!', [VarName, Expression], '#void') --> + interp_eval(Expression, Result), + set_variable_value(VarName, Result). -interp_eval_special(lambda, [Parameters|Body], Result, Env, Env) :- - make_procedure(Parameters, Body, Env, Result). +interp_eval_special(lambda, [Parameters|Body], Result, State, State) :- + make_procedure(Parameters, Body, State, Result). -interp_eval_special(define, [VarName, Definition], '#void', Env, NewEnv) :- - interp_eval(Definition, Value, Env, Env1), - define_variable(VarName, Value, Env1, NewEnv). +interp_eval_special(define, [VarName, Definition], '#void') --> + interp_eval(Definition, Value), + define_variable(VarName, Value). -interp_eval_special(cond, Conditions, Result, Env, NewEnv) :- - eval_cond(Conditions, Result, Env, NewEnv). +interp_eval_special(cond, Conditions, Result) --> + eval_cond(Conditions, Result). -eval_cond([], '#void', Env, Env). -eval_cond([[else|ThenBody]], Result, Env, NewEnv) :- +eval_cond([], '#void', State, State). +eval_cond([[else|ThenBody]], Result, StateIn, StateOut) :- !, - eval_sequence(ThenBody, Result, Env, NewEnv). -eval_cond([[TestExpr|ThenBody]|Rest], Result, Env, NewEnv) :- - interp_eval(TestExpr, TestResult, Env, Env1), + eval_sequence(ThenBody, Result, StateIn, StateOut). +eval_cond([[TestExpr|ThenBody]|Rest], Result, StateIn, StateOut) :- + interp_eval(TestExpr, TestResult, StateIn, State1), ( TestResult =@= '#t' - -> eval_sequence(ThenBody, Result, Env1, NewEnv) + -> eval_sequence(ThenBody, Result, State1, StateOut) ; ( TestResult =@= '#f' - -> eval_cond(Rest, Result, Env1, NewEnv) + -> eval_cond(Rest, Result, State1, StateOut) ; interp_error(type, bool))). -interp_eval(Expression, Result, Env, NewEnv) :- - assertion(ground(Expression)), - assertion(ground(Env)), - interp_eval_(Expression, Result, Env, NewEnv). +interp_eval(Expression, Result) --> + check_state, + { + assertion(ground(Expression)), + interp_check(expression_type(Expression, Type), eval, Expression) + }, + interp_eval_(Type, Expression, Result). -interp_apply_(primitive(Name, Arity), Arguments, Result) :- +interp_apply_(primitive(Name, Arity), Arguments, Result, StateIn, StateOut) :- length(Arguments, ALength), 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(Parameters, Arguments, Env, Env1), - eval_sequence(Body, Result, Env1, _Env2). - -interp_apply(Proc, Arguments, Result) :- - assertion(ground(Proc)), - assertion(ground(Arguments)), + interp_check(primitive_apply(Name, Arguments, Result, StateIn, StateOut), primitive, Name). +interp_apply_(proc(Parameters, Body, Env), Arguments, Result, StateIn, StateOut) :- + State1 = StateIn.put(env, Env), + extend_environment(Parameters, Arguments, State1, State2), + eval_sequence(Body, Result, State2, State3), + StateOut = State3.put(env, StateIn.env). + +interp_apply(Proc, Arguments, Result) --> + check_state, + { assertion(ground(Proc)), assertion(ground(Arguments)) }, interp_apply_(Proc, Arguments, Result). -eval_sequence([H|T], Result, Env, NextEnv) :- - interp_eval(H, FirstResult, Env, Env1), - eval_sequence_aux(T, FirstResult, Result, Env1, NextEnv). +eval_sequence([H|T], Result, StateIn, StateOut) :- + interp_eval(H, FirstResult, StateIn, State1), + eval_sequence_aux(T, FirstResult, Result, State1, StateOut). -eval_sequence_aux([], Result, Result, Env, Env). -eval_sequence_aux([H|T], _, Result, Env, NextEnv) :- - interp_eval(H, ThisResult, Env, Env1), - eval_sequence_aux(T, ThisResult, Result, Env1, NextEnv). +eval_sequence_aux([], Result, Result, State, State). +eval_sequence_aux([H|T], _, Result, StateIn, StateOut) :- + interp_eval(H, ThisResult, StateIn, State1), + eval_sequence_aux(T, ThisResult, Result, State1, StateOut). -list_of_values([], _, []). -list_of_values([Name|NameTail], Env, [Value|ValueTail]) :- - interp_eval(Name, Value, Env, Env), % enforces environment does not change - list_of_values(NameTail, Env, ValueTail). +list_of_values([], [], State, State). +list_of_values([Name|NameTail], [Value|ValueTail]) --> + interp_eval(Name, Value), + list_of_values(NameTail, ValueTail). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Environment Setup @@ -209,6 +219,11 @@ list_of_values([Name|NameTail], Env, [Value|ValueTail]) :- result_bool(Condition, Result) :- call(Condition) -> Result = '#t' ; Result = '#f'. +% in case we want primitive function which changes state we cand do so here +% otherwise call stateless primitive_apply/3 +primitive_apply(Name, Arguments, Result, State, State) :- + primitive_apply(Name, Arguments, Result). + primitive_apply('atom?', [X], Result) :- result_bool(atom(X), Result). primitive_apply('null?', [X], Result) :- result_bool(X =@= [], Result). primitive_apply(car, [[H|_]], H). @@ -230,8 +245,6 @@ primitive_arity(+, 2). setup_environment([['#t'-'#t', '#f'-'#f'|PrimitiveProcs]]) :- findall(Name-primitive(Name, Arity), primitive_arity(Name, Arity), PrimitiveProcs). -the_global_environment(Env) :- setup_environment(Env). - %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Driver @@ -239,7 +252,7 @@ the_global_environment(Env) :- setup_environment(Env). user_print(proc(Parameters, Body, _)) :- format('→ proc(~w, ~w, <env>)~n', [Parameters, Body]). -interp_loop(Env, InputStream) :- +interp_loop(State0, InputStream) :- write('? '), Here=interp_loop/1, ( read_line_to_codes(InputStream, Codes) -> true @@ -247,11 +260,11 @@ interp_loop(Env, InputStream) :- format('got input: "~s"~n', [Codes]), ( parse_input_expression(Codes, Expression) -> true ; throw(error(parse_error(Codes), context(Here, 'Failed to parse line.')))), - ( interp_eval(Expression, Result, Env, NewEnv) -> true + ( interp_eval(Expression, Result, State0, State1) -> true ; throw(error(evaluation_error(Codes), context(Here, 'Failed to evaluate expression.')))), user_print(Result), nl, - interp_loop(NewEnv, InputStream). + interp_loop(State1, InputStream). % grammar {{{1 @@ -300,5 +313,5 @@ sexp(Content, _, _, Result) :- %:- initialization(main, main). main(_Args) :- current_stream(0, read, StdInStream), - the_global_environment(Env), - interp_loop(Env, StdInStream). + initial_state(State), + interp_loop(State, StdInStream). diff --git a/tests.pl b/tests.pl @@ -2,6 +2,11 @@ :- use_module(interpreter). +portray_long :- portray_depth(32). +portray_depth(Depth) :- + set_prolog_flag(answer_write_options, [quoted(true), portrayed(true), max_depth(Depth), spacing(next_argument)]), + set_prolog_flag(debugger_write_options, [quoted(true), portrayed(true), max_depth(Depth), spacing(next_argument)]). + test_one_result(Template, Goal, Output) :- findall(Template, Goal, Results), % write(Results), nl, @@ -85,14 +90,16 @@ apply_example(`(+ 3 2)`, `5`). :- begin_tests(primitive_apply). test(apply_example, [forall(apply_example(Func, Args, ExpectedResult))]) :- + initial_state(State0), primitive_arity(Func, Arity), - interp_apply(primitive(Func, Arity), Args, Result), + interp_apply(primitive(Func, Arity), Args, Result, State0, _State1), assertion(Result =@= ExpectedResult). test(apply_arity_error, [error(interp_error(arity, car))]) :- Func=car, + initial_state(State0), primitive_arity(Func, Arity), - interp_apply(primitive(Func, Arity), [[a], [b]], _). + interp_apply(primitive(Func, Arity), [[a], [b]], _Result, State0, _State1). :- end_tests(primitive_apply). :- begin_tests(eval). @@ -101,11 +108,11 @@ quoted_args([], []). quoted_args([H|T], [[quote, H]|QT]) :- quoted_args(T, QT). test_eval(Call, ExpectedResult) :- - the_global_environment(Env), - test_eval(Call, ExpectedResult, Env). -test_eval(Call, ExpectedResult, Env) :- + initial_state(State0), + test_eval(Call, ExpectedResult, State0, _State1). +test_eval(Call, ExpectedResult, State0, State1) :- format('testing call: ~w → expecting ~w~n', [Call, ExpectedResult]), - interp_eval(Call, Result, Env, _), + interp_eval(Call, Result, State0, State1), assertion(Result =@= ExpectedResult). eval_example(`(car (cdr (quote ((b) (x y) ((c))))))`, `(x y)`). @@ -138,14 +145,14 @@ eval_define_lat :- ((null? l) #t) ((atom? (car l)) (lat? (cdr l))) (else #f))))|}, - the_global_environment(Env0), - interp_eval(DefLat, _, Env0, Env1), + initial_state(State0), + interp_eval(DefLat, _, State0, State1), Call0 = {|sexp||(lat? (quote ()))|}, - test_eval(Call0, '#t', Env1), + test_eval(Call0, '#t', State1, _), Call1 = {|sexp||(lat? (quote (Jack Sprat could eat no chicken fat)))|}, - test_eval(Call1, '#t', Env1), + test_eval(Call1, '#t', State1, _), Call2 = {|sexp||(lat? (quote ((Jack) Sprat could eat no chicken fat)))|}, - test_eval(Call2, '#f', Env1). + test_eval(Call2, '#f', State1, _). test(eval_define_lat) :- eval_define_lat. :- end_tests(eval).