%%% % Nano-datomic in Prolog (lacks a million features) %%% % % 1/ Construct the database index/graph % % ?- build_index. % % 2/ Run queries % % ?- test_query(Q), query(Q, Ans). % ?- query("[:find ?title :where [?book :book/title ?title]]", Ans). % ?- ??? % ?- PROFIT! % %%% %%% % Dataset %%% db( book{ id: bk_crypto, title: "Understanding Cryptography", topic: cs } ). db( book{ id: bk_numbers, title: "Number Theory", topic: math } ). db( topic{ id: cs, name: "Computer Science" } ). db( topic{ id: math, name: "Mathematics" } ). %%% % Index construction logic %%% build_index :- forall( db(Obj), ( build_index(Obj), !; print('Cannot index:'), nl, display(Obj), nl ) ). build_index(Obj) :- is_dict(Obj, Tag), get_dict(id, Obj, ObjId), forall( get_dict(Key, Obj, Value), ( atomic_list_concat([Tag, '/', Key], NamespacedAttr), build_index(Key, NamespacedAttr, ObjId, Value) ) ). build_index(id, Attr, ObjId, _Value) :- !, Record =.. [Attr, ObjId], assertz(Record). build_index(_Key, _Attr, _ObjId, []) :- !. build_index(Key, Attr, ObjId, [Value | Values]) :- build_index(Key, Attr, ObjId, Value), build_index(Key, Attr, ObjId, Values). build_index(_Key, Attr, ObjId, Value) :- Record =.. [Attr, ObjId, Value], assertz(Record). %%% % User entry point %%% test_query(" [:find ?title ?topic-name :where [?book :book/title ?title] [?book :book/topic ?topic] [?topic :topic/name ?topic-name]] "). query(Query, Answer) :- parse(Query, Vars, Preds), !, execute(Vars, Preds, Answer). %%% % Execution engine %%% execute(Vars, Preds, Answer) :- foldl(build_var, Vars, variables{}, Answer), build_preds(Answer, _AnswerAndTmpVars, Preds, Goal), !, Goal. build_var(Var, Out, Out) :- get_dict(Var, Out, _), !. build_var(Var, In, Out) :- put_dict(Var, In, _, Out). build_preds(Vars, Vars, [], true). build_preds(Vars, Vars4, [pred(A, Attr, B) | Ins], (Pred, Outs)) :- build_var(A, Vars, Vars2), build_var(B, Vars2, Vars3), get_dict(A, Vars3, VarA), get_dict(B, Vars3, VarB), Pred =.. [Attr, VarA, VarB], build_preds(Vars3, Vars4, Ins, Outs). %%% % Input cleanup plus call to DCG %%% parse(Query, Vars, Preds) :- atom_chars(Query, Chars), include(not_whitespace, Chars, FilteredChars), phrase(dat_query(Vars, Preds), FilteredChars). not_whitespace(C) :- not(char_type(C, space)). %%% % Query syntax DCG %%% dat_query(Vars, Preds) --> ['['], dat_symbol(find), dat_star(dat_variable, Vars), dat_symbol(where), dat_star(dat_predicate, Preds), [']']. dat_predicate(pred(ObjA, Attr, ObjB)) --> ['['], dat_variable(ObjA), dat_symbol(Attr), dat_variable(ObjB), [']']. dat_variable(Name) --> dat_prefixed_name('?', Name). dat_symbol(Name) --> dat_prefixed_name(':', Name). dat_prefixed_name(Prefix, Name) --> [Prefix], dat_name(NameChars), { atomic_list_concat(NameChars, Name) }. dat_name([A|As]) --> [A], { char_type(A, alnum); member(A, ['/', '-']) }, dat_name(As). dat_name([]) --> []. dat_star(PredName, [Out|Outs]) --> { Pred =.. [PredName, Out] }, Pred, dat_star(PredName, Outs). dat_star(_, []) --> [].