interpl

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

commit ef83e0383499ee1b1e52d2610fa39f5ba2191d79
Author: Jan Pobrislo <ccx@te2000.cz>
Date:   Fri, 30 May 2025 13:16:45 +0000

initial commit

Diffstat:
AMakefile | 2++
Ainterpreter.pl | 250+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atests.pl | 40++++++++++++++++++++++++++++++++++++++++
3 files changed, 292 insertions(+), 0 deletions(-)

diff --git a/Makefile b/Makefile @@ -0,0 +1,2 @@ +tests: + swipl -g run_tests -t halt tests.pl diff --git a/interpreter.pl b/interpreter.pl @@ -0,0 +1,250 @@ +%% #!/usr/bin/env swipl +:- module(interpreter, + [ parse_input_expression/2 + ]). +:- use_module(library(debug), [assertion/1]). +:- use_module(library(pairs)). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Self-evaluating expressions + +% (self-evaluating? 1) => true +% (self-evaluating? "test") => true +% (self-evaluating? '(a b c)) => false +self_evaluating(Exp) :- number(Exp). +self_evaluating(Exp) :- string(Exp). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Quoted expressions +quoted([quote|Rest], Rest). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Variables and environment + +variable(Exp) :- atom(Exp). + +frame_variables(Frame, Variables) :- pairs_keys(Frame, Variables). +frame_values(Frame, Variables) :- pairs_values(Frame, Variables). +add_binding_to_frame(VarName, Value, OldFrame, [VarName-Value|OldFrame]). + +% (enclosing-environment '(((a) . (1)) . ())) => '() +enclosing_environment([_|Env], Env). + +% (first-frame '(((a) . (1)) . ())) => '((a) . (1)) +first_frame([Frame|_], Frame). + +% No bindings, no enclosing environment +empty_environment([]). + +% (extend-environment '(a) '(1) '()) +% => '(((a) . (1)) . ()) +extend_environment(OldEnv, VarNames, Values, [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_aux(Env, VarName, Value) -> true ; assertion(false). % unbound variable +lookup_variable_value_aux([Frame|Env], VarName, Value) :- + lookup_variable_from_frame(Frame, VarName, Value) -> true ; lookup_variable_value_aux(Env, VarName, Value). +lookup_variable_from_frame([K-V|Rest], VarName, Value) :- + VarName = K -> V = Value ; lookup_variable_from_frame(Rest, VarName, Value). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Definitions + +% (definition? '(define x 1)) => true +% (definition? '(quote x)) => false +is_definition([define,_,_]). + +%definition_variable([define, VarName, _], VarName). +%definition_value([define, _, Value], Value). + +define_variable(VarName, Value, [OldFrame|EnvTail], [[VarName-Value|OldFrame]|EnvTail]). + +eval_definition([define, VarName, Definition], Value, OldEnv, NewEnv) :- + interp_eval(Definition, Value, OldEnv, Env1), + define_variable(VarName, Value, Env1, NewEnv). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Assignments + +is_assignment(['set!'|_]). + +set_variable_value(VarName, Value, OldEnv, NewEnv) :- + set_variable_value_aux(VarName, Value, OldEnv, NewEnv) -> true ; assertion(false). + +set_variable_value_aux(VarName, Value, [OldFrame|OldE], [NewFrame|NewE]) :- + ( set_variable_in_frame(VarName, Value, OldFrame, NewFrame) + -> OldE = NewE + ; OldFrame = NewFrame, + set_variable_value_aux(VarName, Value, OldE, NewE)). + +set_variable_in_frame(VarName, Value, [K-_OldV|OldT], [K-NewV|NewT]) :- + ( VarName = K + -> NewV = Value, + NewT = OldT + ; set_variable_in_frame(VarName, Value, OldT, NewT)). + +eval_assignment(['set!', VarName, Expression], Env, NewEnv) :- + interp_eval(Expression, Result, Env, Env1), + set_variable_value(VarName, Result, Env1, NewEnv). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Procedure Construction + +% (lambda? '(lambda () 1)) => 1 +is_lambda([lambda, Vars|_]) :- is_list(Vars). + +% (lambda-parameters '(lambda () 1)) => '() +%% (define (lambda-parameters exp) (cadr exp)) +% (lambda-body '(lambda () 1)) => '(1) +%% (define (lambda-body exp) (cddr exp)) + +make_procedure(Parameters, Body, Env, proc(Parameters, Body, Env)). + +%compound_procedure(proc(_,_,_)). +%(define (compound-procedure? p) +% (tagged-list? p 'procedure)) +%(define (procedure-parameters p) (cadr p)) +%(define (procedure-body p) (caddr p)) +%(define (procedure-environment p) (cadddr p)) + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Application Expressions + +%(define (application? exp) (pair? exp)) +is_application([_|_]). +%(define (operator exp) (car exp)) +operator([Operator|_], Operator). +%(define (operands exp) (cdr exp)) +operands([_|Operands], Operands). +%(define (no-operands? ops) (null? ops)) +operands([_|Operands], Operands). +%(define (first-operand ops) (car ops)) +first_operand([_,X|_], X). +%(define (rest-operands ops) (cdr ops)) +rest_operands([_,_|X], X). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Primitive Procedures + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Eval/Apply + +interp_eval(Expression, Expression, Env, Env) :- + self_evaluating(Expression), !. +interp_eval([quoted|Rest], Rest, Env, Env) :- !. +interp_eval(VarName, Result, Env, Env) :- + variable(VarName), + !, + lookup_variable_value(Env, VarName, Result). +interp_eval([define|Tail], Result, Env, NewEnv) :- + !, + eval_definition([define|Tail], Result, Env, NewEnv). +interp_eval(Expression, void, Env, NewEnv) :- + is_assignment(Expression), + !, + eval_assignment(Expression, Env, NewEnv). +interp_eval([lambda, Parameters|Body], Result, Env, Env) :- + make_procedure(Parameters, Body, Env, Result). +interp_eval([Operator|Operands], Result, Env, NewEnv) :- + !, + interp_eval(Operator, Procedure, Env, NewEnv), + list_of_values(Operands, Env, Arguments), + interp_apply(Procedure, Arguments, Result). +interp_eval(_, _, _, _) :- assertion(false). + +interp_apply(primitive(Name), Arguments, Result) :- + primitive_apply(Name, Arguments, Result). +interp_apply(proc(Parameters, Body, Env), Arguments, Result) :- + extend_environment(Env, Parameters, Arguments, NewEnv), + eval_sequence(Body, NewEnv, Result). + +eval_sequence([H|T], Env, Result) :- + interp_eval(H, FirstResult, Env, Env1), + eval_sequence_aux(T, Env1, FirstResult, Result). + +eval_sequence_aux([], _, Result, Result). +eval_sequence_aux([H|T], Env, _, Result) :- + interp_eval(H, ThisResult, Env, Env1), + eval_sequence_aux(T, Env1, ThisResult, Result). + +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, ValueTail). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Environment Setup + +primitive_apply(car, [[H|_]], H). +primitive_apply(cdr, [[_|T]], T). +primitive_apply(cons, [H,T], [H|T]). +primitive_apply(+, [A, B], Result) :- Result is A + B. +% primitive_apply('[]', [], []). + +setup_environment([[ + car-primitive(car), + cdr-primitive(cdr), + cons-primitive(cons), + (+)-primitive(+), + true-true, + false-false +]]). + +the_global_environment(Env) :- setup_environment(Env). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Driver + +user_print(proc(Parameters, Body, _)) :- + format('→ proc(~w, ~w, <env>)~n', [Parameters, Body]). + +interp_loop(Env, InputStream) :- + write('? '), + Here=interp_loop/1, + ( read_line_to_codes(InputStream, Codes) -> true + ; throw(error(read_error, context(Here, 'Failed to read line.')))), + 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 + ; throw(error(evaluation_error(Codes), context(Here, 'Failed to evaluate expression.')))), + user_print(Result), + nl, + interp_loop(NewEnv, InputStream). + +% errors {{{1 + +:- multifile prolog:error_message//1. +prolog:error_message(read_error) --> + [ 'Failed to read line.' ]. + +prolog:error_message(parse_error(Codes)) --> + [ 'Failed to parse line: "~s".' - [Codes] ]. + +prolog:error_message(evaluation_error(Codes)) --> + [ 'Failed to evaluate expression: "~s".' - [Codes] ]. + +% grammar {{{1 + +parse_input_expression(Codes, Expression) :- + phrase(sexp(Expression), Codes). + +sexp(_Expression) --> { false }. % TODO + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Interpreter wrapper + +% main {{{1 +%main :- current_prolog_flag(argv, Argv), main(Argv). + +%:- initialization(main, main). +main(_Args) :- + current_stream(0, read, StdInStream), + the_global_environment(Env), + interp_loop(Env, StdInStream). diff --git a/tests.pl b/tests.pl @@ -0,0 +1,40 @@ +:- use_module(library(debug), [assertion/1]). + +:- use_module(interpreter). + +test_one_result(Template, Goal, Output) :- + findall(Template, Goal, Results), + assertion(length(Results, 1)), + Results = [Output]. + +:- begin_tests(sexp). + +test(sexp_atom) :- + parse_input_expression(`a`, Expression), + assertion(Expression =@= a). + +test(sexp_atom2) :- + parse_input_expression(`hello-world!`, Expression), + assertion(Expression =@= 'hello-world!'). + +test(sexp_number) :- + parse_input_expression(`0`, Expression), + assertion(Expression =@= 0). + +test(sexp_number2) :- + parse_input_expression(`31337`, Expression), + assertion(Expression =@= 31337). + +test(sexp_list_empty) :- + parse_input_expression(`()`, Expression), + assertion(Expression =@= []). + +test(sexp_list_one_atom) :- + parse_input_expression(`(a)`, Expression), + assertion(Expression =@= [a]). + +test(sexp_list_one_two_atoms) :- + parse_input_expression(`(a)`, Expression), + assertion(Expression =@= [a, b]). + +:- end_tests(sexp).