linfo2335-programming-parad.../solution.pl

453 lines
14 KiB
Prolog
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Authors:
* Dubois Brieuc
* Dubois Simon
*/
:- dynamic tables/0.
/*
Prints the names of all existing tables, one per line (using writeln/1).
A table name is always an atom.
*/
tables :- tabl(X, _), writeln(X).
:- dynamic tables/1.
/*
Unify Tables with a list of the names of all existing tables.
*/
tables(Tables) :- findall(X, tabl(X, _), Tables).
:- dynamic create_table/2.
/*
When this predicate is executed, the effect will be the creation of a new
table with the specified list of column names (order matters!).
A column name is always an atom.
If a table with the given name already exists, the predicate throws a
descriptive exception (using throw/1).
*/
create_table(Table, Cols) :-
(tabl(Table, _) -> throw("Table already exists"));
assertz(tabl(Table, Cols)).
:- dynamic cols/2.
/*
Unifies Cols with the list of columns for the specified table (in the same
order as they were supplied to create table/2).
If the given table does not exist, the predicate must throw a descriptive
exception (use throw/1).
*/
cols(Table, Cols) :-
(tabl(Table, _) -> tabl(Table, Cols));
throw("Table doesn't exist").
:- dynamic row/2.
/*
Unifies Row, one result at a time, with each row in the given Table.
If the given table does not exist, the predicate should fail.
*/
row(Table, _) :-
\+ tabl(Table, _),
throw("Table doesn't exist"),
!.
row(Table, _) :-
tabl(Table, Cols),
length(Cols, L),
\+ current_predicate(Table/L),
!.
row(Table, Row) :-
tabl(Table, Cols),
length(Cols, L),
length(Row, L),
apply(Table, Row).
:- dynamic rows/1.
/*
Displays all rows in the given table, one per line (using writeln/1).
If the given table does not exist, the predicate throws a descriptive
exception.
*/
rows(Table) :-
\+ tabl(Table, _),
throw(table_does_not_exist(Table)),
!.
rows(Table) :-
tabl(Table, Cols),
length(Cols, L),
\+ current_predicate(Table/L),
!.
rows(Table) :-
row(Table, Row),
writeln(Row),
fail.
rows(_).
:- dynamic insert/2.
/*
When this predicate is executed, the effect will be the addition of a given
row in the given table. The given row is a list of values for each of the
corresponding columns in the table (in the order in which the columns
were supplied to create table/2).
If the given table does not exist, the predicate throws a descriptive
exception.
If the row does not have as many elements as the number of columns in
the table, the predicate throws a descriptive exception.
*/
insert(Table, Row) :-
(tabl(Table, Cols) ->
(length(Row, L1), length(Cols, L2), L1 =:= L2 ->
Term =.. [Table | Row],
assertz(Term);
throw("Row doesn't have as many elements as the number of columns in the table")
);
throw("Table doesn't exist")
).
:- dynamic drop/1.
/*
When this predicate is executed, the effect will be the deletion of the given
table.
If the given table does not exist, the predicate throws a descriptive
exception.
*/
drop(Table) :-
(tabl(Table, _) ->
delete(Table), retract(tabl(Table, _));
throw("Table doesn't exist")
).
:- dynamic delete/1.
/*
When this predicate is executed, the effect will be the deletion of all rows
in the given table. The table itself should still exist after, but with no
more rows.
If the given table does not exist, the predicate throws a descriptive
exception.
*/
delete(Table) :-
(tabl(Table, _) ->
tabl(Table, C), length(C, L), call(abolish, Table, L);
throw("Table doesn't exist")
).
:- dynamic delete/2.
/*
When this predicate is executed, the effect will be the deletion of all rows
from the given table that match all of the given conditions. The table
still exist after.
If the given table does not exist, the predicate throws a descriptive
exception.
A condition is any Prolog predicate that could have been typed at the
prompt, but which may include selectors. Selectors are terms of the form
+<column>where <column>should be replaced by a column name.
*/
delete(Table, Conds) :-
(tabl(Table, _) ->
row(Table, Row),
does_match(Conds, Table, Row),
Term =.. [Table | Row],
retract(Term),
fail
;
throw("Table doesn't exist")
).
delete(_, _).
does_match([], _, _) :- !.
does_match([Cond|Rest], Table, Row) :-
% Extract the operator, field and value from the condition
Cond =.. [Operator | T],
[+F, S] = T,
% Get the columns of the table
tabl(Table, Cols),
% Get the index of the field in the columns
member(F, Cols),
nth0(Index, Cols, F),
% Get the value of the field in the row
nth0(Index, Row, Value),
% Apply the operator to the value and the selector
apply(Operator, [Value, S]),
% Check the next condition
does_match(Rest, Table, Row).
:- dynamic selec/4.
/*
Table is the name of a single table.
Selectors is either * or a list of selectors. These define the resulting
projection. * means: select all column names from the table. Other
selectors explicitly specify which columns to pick.
For example, +name would select the column named
name.
Conds has the same form as in delete/2 and works the same way: only
rows that match all conditions are selected.
Finally, Projection unifies with <selectors>/<projection>, where:
•<selectors>is the list of requested selectors.
•<projection>is a list of values coming from a single row from the
given table that matches the conditions. This mean this predicate
should be able to backtrack to generate all projections that match
the query.
For example, selec(persons,[+id,+first],[],P) returns as first result
P = [+id, +first]/[0, "Jeffrey"].
To obtain all projections that match the query, one could use the Prolog
query findall(X, selec(Table, Selectors, Conds, X), Projections).
For example: findall(X, selec(persons,[+last],[],X), Projections)
returns Projections = [[+last]/["Bowman"],[+last]/["Michaels"],. . . ]
Or if you only want the rows (since the selectors are repeated):
findall(X, selec(Table, Selectors, Conds, /X), Projections).
For example:
findall(Values, selec(persons,[+id,+first],[],Values), Projections)
returns: Projections = [[0, "Jeffrey"], [1, "Lorena"], [2, "Joseph"], ...
*/
selec(Table, _, _, _) :-
\+ tabl(Table, _),
throw("Table doesn't exist"),
!.
selec(Table, _, _, _) :-
tabl(Table, Cols),
length(Cols, L),
\+ current_predicate(Table/L),
!.
selec(Table, Selector, Cond, Projection) :-
tabl(Table, Cols),
row(Table, R),
does_match(Cond, Table, R),
selector(R,Cols, Selector, [],[],ColumnNames,ColumnValue),
reverse(ColumnNames, Names),
reverse(ColumnValue, Values),
Projection = Names/Values.
selec(Table, *, Cond, Projection) :-
tabl(Table, Cols),
row(Table, R),
does_match(Cond, Table, R),
Projection = Cols/R.
/**
* Row: A row from the table
* Cols: name of the column of the table
* [Selector|Selectors] name(s) of the column(s) for selection
* AccCols: acumulator list for the column(s) name(s)
* AccVals: acumulator list for the row(s) values
* ColumnNames: List returning the column(s) name(s)
* ColumnValue: List returning the row values for each column
*
* This predicate return
*/
selector(_,_,[],ColumnNames, ColumnValue, ColumnNames, ColumnValue):-!.
selector(Row, Cols, [+Selector|Selectors], AccCols, AccVals, ColumnNames, ColumnValue):-
(member(Selector, Cols) ->
%find the value corresponding to the column in the row
nth0(Index, Cols, Selector),
nth0(Index, Row, Value),
%go to next column tho evaluate
selector(Row, Cols, Selectors, [Selector|AccCols], [Value|AccVals], ColumnNames, ColumnValue)
;
string_concat(Selector, " isn't a column of table ", Error),
throw(Error)
).
:- dynamic selec/3.
/*
Simplified variant of the selec/4 predicate when there are no conditions
to be checked.
*/
selec(TableOrTables, Selectors, Projection) :-
tabl(TableOrTables, Cols),
row(TableOrTables, R),
selector(R,Cols, Selectors, [],[],ColumnNames,ColumnValue),
reverse(ColumnNames, Names),
reverse(ColumnValue, Values),
Projection = Names/Values.
selec(Table, *, Projection) :-
tabl(Table, Cols),
row(Table, R),
Projection = Cols/R.
:- dynamic query/2.
/*
where Query is a string whose syntax is defined by the following grammar:
⟨query⟩ ::= ⟨select⟩ | ⟨insert ⟩
⟨select⟩ ::= SELECT ⟨selectors⟩ FROM ⟨table⟩ [⟨where⟩];
⟨selectors⟩ ::= * | ⟨cols⟩
⟨cols⟩ ::= ⟨col ⟩ [, ⟨cols⟩]
⟨where⟩ ::= WHERE ⟨cond ⟩
⟨insert⟩ ::= INSERT INTO ⟨table⟩ [(⟨cols⟩)] VALUES (⟨values⟩);
⟨values⟩ ::= ⟨value⟩ [, ⟨values⟩]
About the notation: the pipe (|) and square brackets ([]) symbols in the
production rules above denote choice and optionality, respectively.
<table>and <col>denote table and column names (respectively). Ta-
ble names are atoms, while column names should follow the format out-
lined before (+<atom>). <cond>denotes a Prolog goal, in Prolog syntax
— following the same format as the possible values of items of the Conds
list passed to selec and delete.
<value>denotes a prolog value that can be stored into a row. You do
not need to handle parsing Prolog (<cond>and <value>) by yourself,
well show how to do it below.
The semantics of this predicate is that of the SQL-predicate contained in
the Query string.
A “SELECT” query maps to the selec predicate, while an “INSERT”
query maps to the insert predicate. For “SELECT”, all instances of
<cond>(appearing in <where>) are mapped to the Conds parameter.
<selectors>map to the Selectors parameter. Tables names (or *) are
mapped to the Table parameter.
For “INSERT”, if the <cols>part is absent, the mapping to insert/2
is straightforward. If <cols>is present, you will have to:
1. reorder the values according to the columns that are present;
2. fill in the missing columns (if any) with a null default values.
Example:
query("INSERT INTO cities (+name, +state)
VALUES (\"Tempa\", \"Florida\");").
maps to (for instance):
insert(cities, ["Tempa", "Florida"]).
When used with “SELECT”:
•query/1 must display the results a bit like the rows predicate would.
You have some flexibility here (displaying the selected column names
is a nice touch for instance).
•query/2 must pass the Result parameter as last parameter to the
selec/4 predicate.
For “INSERT”, nothing needs to be printed by query/1 or in case of
success, and if query/2 is used the Result parameter can be ignored.
*/
query(Query, Result):-
parse_query(Query, Fun, Args),
apply(Fun, Args),
last(Args, Result).
/**
* Parse SELECT * FROM <table> WHERE <conds>
*/
parse_query(Query, selec, [AtomTable, *, AtomsConds, R]):-
split_string(Query, " ", ",;+", SplitQuery),
append(["SELECT"], L1, SplitQuery),
append(["*"], ["FROM"|L2], L1),
append([Table], ["WHERE"|Conds],L2),
atom_string(AtomTable, Table),
maplist(parse_cond, AtomsConds, Conds),!.
/**
* Parse SELECT <cols> FROM <table> WHERE <conds>
*/
parse_query(Query, selec, [AtomTable, AtomsSelectors, AtomsConds, R]):-
split_string(Query, " ", ",;+", SplitQuery),
append(["SELECT"], L1, SplitQuery),
append(Selectors, ["FROM"|L2], L1),
append([Table], ["WHERE"|Conds],L2),
atom_string(AtomTable, Table),
maplist(atom_string, AtomsSelectors1, Selectors),
maplist(add_plus, AtomsSelectors, AtomsSelectors1),
maplist(parse_cond, AtomsConds, Conds),!.
/**
* Parse SELECT * FROM <table>
*/
parse_query(Query, selec, [AtomTable, *, R]):-
split_string(Query, " ", ",;+", SplitQuery),
append(["SELECT"], L1, SplitQuery),
append(["*"], ["FROM",Table], L1),
atom_string(AtomTable, Table),
maplist(parse_cond, AtomsConds, Conds),!.
/**
* Parse SELECT <cols> FROM <table>
*/
parse_query(Query, selec, [AtomTable, AtomsSelectors, R]):-
split_string(Query, " ", ",;+", SplitQuery),
append(["SELECT"], L1, SplitQuery),
append(Selectors, ["FROM",Table], L1),
atom_string(AtomTable, Table),
maplist(atom_string, AtomsSelectors1, Selectors),
maplist(add_plus, AtomsSelectors, AtomsSelectors1).
parse_query(Query, insert, [AtomTable, Values]):-
split_string(Query, " ", '",();', SplitQuery),
append(["INSERT", "INTO"], L1, SplitQuery),
L1 = [Table | L2],
append(_, ["VALUES"|Values1], L2),
maplist(is_number, Values, Values1),
atom_string(AtomTable, Table).
add_plus(+A, A).
% parse >= conditon
parse_cond(+AtomLeft>=AtomRight, Cond):-
string_chars(Cond, Code),
append(LeftMemb, [>,=|RightMemb], Code),
string_chars(Left, LeftMemb),
string_chars(Right1, RightMemb),
split_string(Right1,"",'"',[Right]),
atom_string(AtomLeft, Left),
is_number(AtomRight, Right),!.
% parse <= conditon
parse_cond(+AtomLeft<=AtomRight, Cond):-
string_chars(Cond, Code),
append(LeftMemb, [<,=|RightMemb], Code),
string_chars(Left, LeftMemb),
string_chars(Right1, RightMemb),
split_string(Right1,"",'"',[Right]),
atom_string(AtomLeft, Left),
is_number(AtomRight, Right),!.
% parse < conditon
parse_cond(+AtomLeft<AtomRight, Cond):-
string_chars(Cond, Code),
append(LeftMemb, [<|RightMemb], Code),
string_chars(Left, LeftMemb),
string_chars(Right1, RightMemb),
split_string(Right1,"",'"',[Right]),
atom_string(AtomLeft, Left),
is_number(AtomRight, Right),!.
% parse > conditon
parse_cond(+AtomLeft>AtomRight, Cond):-
string_chars(Cond, Code),
append(LeftMemb, [>|RightMemb], Code),
string_chars(Left, LeftMemb),
string_chars(Right1, RightMemb),
split_string(Right1,"",'"',[Right]),
atom_string(AtomLeft, Left),
is_number(AtomRight, Right),!.
% parse = conditon
parse_cond( +AtomLeft=AtomRight, Cond):-
string_chars(Cond, Code),
append(LeftMemb, [=|RightMemb], Code),
string_chars(Left, LeftMemb),
string_chars(Right1, RightMemb),
split_string(Right1,"",'"',[Right]),
atom_string(AtomLeft, Left),
is_number(AtomRight, Right),!.
is_number(R, N):-
atom_number(N, R); R = N.
:- dynamic query/1.
/*
cf. query/2
*/
query(Query) :-
query(Query, R),
writeln(R),
fail.
query(_).