cpsa-4.4.4: src/cpsa4prolog.pl
% -*- mode: prolog -*-
%% Library of predicates for analyzing cpsa4prolog output
%% The cpsa4prolog program is used to analyze the output of cpsa4. It
%% translates cpsa4 output into a Prolog database that captures the
%% essential features of the output in a form that can be queried
%% using all of the power of Prolog.
%% cpsa4prolog assembles the skeletons in the output into a forest of
%% derivation trees. It then prints the forest in Prolog syntax. To
%% be loadable by Prolog, the output must be sorted so that clauses of
%% one predicate are colocated. The sorting program provided in all
%% dialects of Unix does the job.
%% The output should be used by SWI Prolog as strings must not be
%% atoms. Load the file generated by cpsa4prolog using consult/1.
%% Next load your query. This file contains useful predicates that
%% may help formulate your query. The comments for each predicate are
%% intended help users understand the output format.
%% Copyright (c) 2024 The MITRE Corporation
%% This program is free software: you can redistribute it and/or
%% modify it under the terms of the BSD License as published by the
%% University of California.
%% PATHS
%% The predicate path(L, Ls) is true if Ls is a path through the
%% derivation tree that starts with label L and ends with the label of
%% a skeleton that is at the root of the tree that contains L. The
%% path predicate uses the step/3 predicate to determine if a skeletion
%% was derived from another skeleton.
%% The step predicate defines a transition relation for individual
%% cpsa4 steps. step(P, O, L) is true if a cpsa4 step that started at
%% the skeleton labeled P produced a skeleton labeled L using the
%% operation O. Notice that the same skeleton can appear as the
%% result of a step starting at different skeletons. This means the
%% path predicate can find more than one path to the root.
%% For the path predicate, the operation field information is not
%% used.
path(L, [L]) :-
root(L).
path(L, [L|Ls]) :-
step(P, _, L),
path(P, Ls).
%% A derivation tree is really a directed acyclic graph. If the links
%% used for seen children are removed, the derivation tree becomes a
%% true tree--every child has exactly one parent. The child/2
%% predicate encodes that relation. It is used to traverse the tree
%% without revisiting a skeleton twice. One can use it to count the
%% number of skeletons in a tree, for example. Here we use it to find
%% the terminal skeletons without deplicates. A terminal node is one
%% that has no successors.
terminal(L) :-
step(L, _, _), !,
fail.
terminal(_).
acc(W, W).
acc(W, X) :-
child(W, Y),
acc(Y, X).
%% A query to find the terminal skeletons using these predicates follows.
%% ask(L) :-
%% root(W), acc(W, L), terminal(L).
%% SKELETON ASSOCIATION LISTS
%% The body of a skeleton is available as an association list. It can
%% be queried using has_key_val(L, K, V), where L is a skeleton label,
%% K is a key, and V is its value.
has_key_val(L, K, V) :-
skel(L, A),
assoc(A, K, V).
assoc([[K | V] | _], K, V).
assoc([_ | A], K, V) :-
assoc(A, K, V).
%% A query to find the labels of skeletons that are not eventually
%% dead follows.
%% ask(L) :-
%% root(W), acc(W, L), acc(L, X), terminal(X), \+has_key_val(X, dead, _).
%% ask_all(Ls) :-
%% findall(L, ask(L), Ls).
%% *************************************************************A
%% Goal language
%% p(K, R, S, L) is true if skeleton K has a strand S that is an
%% instance of role R as a string of length L.
%% Listener case
p(K, "", S, L) :-
!,
strand(K, S, [deflistener | _]),
L == 2.
%% Non-listener case
p(K, R, S, L) :-
atom_string(Ra, R),
strand(K, S, [defstrand, Ra, L | _]).
%% p(K, R, V, S, T) if true if skeleton K has a strand S that is an
%% instance of role R as a string that binds role variable V as a
%% string to term T.
%% Listener case
p(K, "", S, V, T) :-
!,
V == "x",
strand(K, S, [deflistener, T]).
p(K, R, V, S, T) :-
atom_string(Ra, R),
atom_string(Va, V),
strand(K, S, [defstrand, Ra, _, Args]),
assoc(Args, Va, [T]).
%% prec(K, S1, I1, S2, I2) is true if skeleton K has an ordering from
%% node (S1, I1) to node (S2, I2).
prec(K, S1, I1, S2, I2) :-
skel(K, Alist),
assoc(Alist, precedes, Ans),
member([[S1, I1], [S2, I2]], Ans).
%% leads_to(K, S1, I1, S2, I2) is true if skeleton K has a leads-to
%% ordering from node (S1, I1) to node (S2, I2).
leads_to(K, S1, I1, S2, I2) :-
skel(K, Alist),
assoc(Alist, leads_to, Ans),
member([[S1, I1], [S2, I2]], Ans).
%% non(K, T) is true if skeleton K has a non-originating term T.
non(K, T) :-
skel(K, Alist),
assoc(Alist, non_orig, Ans),
member(T, Ans).
%% pnon(K, T) is true if skeleton K has a penetrator non-originating
%% term T.
pnon(K, T) :-
skel(K, Alist),
assoc(Alist, pen_non_orig, Ans),
member(T, Ans).
%% uniq(K, T) is true if skeleton K has a uniquely originating term T.
uniq(K, T) :-
skel(K, Alist),
assoc(Alist, uniq_orig, Ans),
member(T, Ans).
%% ugen(K, T) is true if skeleton K has a uniquely generating term T.
ugen(K, T) :-
skel(K, Alist),
assoc(Alist, uniq_gen, Ans),
member(T, Ans).
%% fact(K, P, As) is true if skeleton K has the fact [P | As].
fact(K, P, As) :-
skel(K, Alist),
assoc(Alist, facts, Ans),
member([P | As], Ans).