view jamendo/sparql-archived/SeRQL/sparql_grammar.pl @ 27:d95e683fbd35 tip

Enable CORS on urispace redirects as well
author Chris Cannam
date Tue, 20 Feb 2018 14:52:02 +0000
parents df9685986338
children
line wrap: on
line source
/*  $Id$

    Part of SWI-Prolog

    Author:        Jan Wielemaker
    E-mail:        wielemak@science.uva.nl
    WWW:           http://www.swi-prolog.org
    Copyright (C): 2007, University of Amsterdam

    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License
    as published by the Free Software Foundation; either version 2
    of the License, or (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

    As a special exception, if you link this library with other files,
    compiled with a Free Software compiler, to produce an executable, this
    library does not by itself cause the resulting executable to be covered
    by the GNU General Public License. This exception does not however
    invalidate any other reasons why the executable file might be covered by
    the GNU General Public License.
*/

:- module(sparql_grammar,
	  [ sparql_parse/3		% +In, -Query, Options
	  ]).
:- use_module(library('semweb/rdf_db')).
:- use_module(library(lists)).
:- use_module(library(assoc)).
:- use_module(library(url)).
:- use_module(library(option)).

%%	sparql_parse(+SPARQL, -Query, +Options)
%	
%	Parse the SPARQL statement Input   into a Prolog representation.
%	Based on "SPARQL Query Language for RDF", April 6, 2006. Options
%	supported:
%	
%		* base_uri(+Base)
%		Base used if there is no BASE clause in the query

sparql_parse(Codes, Query, Options) :-
	is_list(Codes), !,
	(   phrase(sparql_query(Prolog, Query0), Codes)
	->  true
	;   syntax_error(unknown)
	),
	resolve_names(Prolog, Query0, Query, Options).
sparql_parse(Atomic, Query, Options) :-
	atomic(Atomic), !,
	atom_codes(Atomic, Codes),
	sparql_parse(Codes, Query, Options).
sparql_parse(Input, _, _) :-
	throw(error(type_error(text, Input), _)).


		 /*******************************
		 *	       ERRORS		*
		 *******************************/

syntax_error(What) :-
	throw(error(syntax_error(What),
		    context(_, 'in SPARQL query'))).

syntax_error(What, In, []) :-
	throw(error(syntax_error(What),
		    context(_, left(In)))).

add_error_location(error(syntax_error(What),
			 context(_, left(After))),
		   Input) :-
	append(Before, After, Input),
	length(Before, BL),
	CLen = 80,
	(   BL =< CLen
	->  BC = Before
	;   length(BC0, CLen),
	    append(_, BC0, Before),
	    append("...", BC0, BC)
	),
	length(After, AL),
	(   AL =< CLen
	->  AC = After
	;   length(AC0, CLen),
	    append(AC0, _, After),
	    append(AC0, "...", AC)
	),
	append("\n**here**\n", AC, HAC),
	append([0'\n|BC], HAC, ContextCodes),	% '
	atom_codes(Context, ContextCodes),
	throw(error(syntax_error(What),
		    context('SPARQL', Context))).


		 /*******************************
		 *	      RESOLVE		*
		 *******************************/

%%	resolve_names(+Prolog, +Query0, -Query, +Options)
%	
%	Turn var(Name) into Prolog variables and resolve all IRIs to
%	absolute IRIs.

resolve_names(Prolog, Q0, Q, Options) :-
	resolve_state(Prolog, State0, Options),
	resolve(Q0, Q, State0).

resolve(select(Proj0, DataSets0, Q0, Solutions0),
	select(Proj,  DataSets,  Q,  Solutions),
	State0) :-
	resolve_datasets(DataSets0, DataSets, State0),
	resolve_query(Q0, Q1, State0, State1),
	resolve_projection(Proj0, Proj, State1, State2),
	resolve_solutions(Solutions0, Solutions, Q2, State2),
	mkconj(Q1, Q2, Q).
resolve(construct(Templ0, DataSets0, Q0, Solutions0),
	construct(Templ,  DataSets,  Q,  Solutions),
	State0) :-
	resolve_datasets(DataSets0, DataSets, State0),
	resolve_query(Q0, Q1, State0, State1),
	resolve_construct_template(Templ0, Templ, State1, State2),
	resolve_solutions(Solutions0, Solutions, Q2, State2),
	mkconj(Q1, Q2, Q).
resolve(ask(DataSets0, Q0), ask(DataSets, Q), State0) :-
	resolve_datasets(DataSets0, DataSets, State0),
	resolve_query(Q0, Q, State0, _).
resolve(describe(Proj0, DataSets0, Q0, Solutions0),
	describe(Proj,  DataSets,  Q,  Solutions),
	State0) :-
	resolve_datasets(DataSets0, DataSets, State0),
	resolve_query(Q0, Q1, State0, State1),
	resolve_projection(Proj0, Proj, State1, State2),
	resolve_solutions(Solutions0, Solutions, Q2, State2),
	mkconj(Q1, Q2, Q).
	
%%	resolve_datasets(+Raw, -IRIs, +State)
%	
%	TBD: what is the difference between named and non-named?

resolve_datasets([], [], _).
resolve_datasets([H0|T0], [H|T], S) :-
	resolve_dataset(H0, H, S),
	resolve_datasets(T0, T, S).

resolve_dataset(T0, IRI, S) :-
	resolve_iri(T0, IRI, S).

%%	resolve_query(+Q0, -Q, +State0, -State)

resolve_query(List, Q, S0, S) :-
	is_list(List), !,
	list_to_conj(List, Q, S0, S).
resolve_query((A0,B0), Q, S0, S) :- !,
	resolve_query(A0, A, S0, S1),
	resolve_query(B0, B, S1, S),
	mkconj(A, B, Q).
resolve_query((A0;B0), (A;B), S0, S) :- !,
	resolve_query(A0, A, S0, S1),
	resolve_query(B0, B, S1, S).
resolve_query(optional(true), true, S, S) :- !.
resolve_query(optional(Q0), (Q *-> true ; true), S0, S) :- !,
	resolve_query(Q0, Q, S0, S).
resolve_query(rdf(Subj0,P0,O0), rdf(Subj,P,O), S0, S) :- !,
	resolve_graph_term(Subj0, Subj, S0, S1),
	resolve_graph_term(P0, P, S1, S2),
	resolve_graph_term(O0, O, S2, S).
resolve_query(graph(G0, Q0), sparql_in_graph(G, Q), S0, S) :- !,
	resolve_graph_term(G0, G, S0, S1),
	resolve_query(Q0, Q, S1, S).
resolve_query(Function, Call, S0, S) :-
	resolve_function(Function, Call, S0, S), !.
resolve_query(ebv(E0), sparql_true(E), S0, S) :- !,
	resolve_expression(E0, E, S0, S).
resolve_query(Q, Q, S, S).		% TBD

mkconj(true, Q, Q) :- !.
mkconj(Q, true, Q) :- !.
mkconj(A, B, (A,B)).

list_to_conj([], true, S, S) :- !.
list_to_conj([Q0], Q, S0, S) :- !,
	resolve_query(Q0, Q, S0, S).
list_to_conj([H|T], (QH,QT), S0, S) :-
	resolve_query(H, QH, S0, S1),
	list_to_conj(T, QT, S1, S).

%%	resolve_projection(+Proj0, -VarList, +State0, State)
%	
%	Return actual projection as a list of Name=Var

resolve_projection(*, Vars, State, State) :- !,
	arg(4, State, Vars0),
	reverse(Vars0, Vars).
resolve_projection(VarNames, Vars, State, State) :-
	proj_vars(VarNames, Vars, State).

proj_vars([], [], _).
proj_vars([var(Name)|T0], [Name=Var|T], State) :- !,
	arg(3, State, Assoc),
	(   get_assoc(Name, Assoc, Var)
	->  true
	;   Var = '$null$'			% or error?
	),
	proj_vars(T0, T, State).
proj_vars([IRI0|T0], [IRI|T], State) :-		% for DESCRIBE queries
	resolve_iri(IRI0, IRI, State),
	proj_vars(T0, T, State).

%%	resolve_construct_template(+Templ0, -Templ, +State)
%	
%	Deal with ORDER BY clause.

resolve_construct_template([], [], S, S).
resolve_construct_template([H0|T0], [H|T], S0, S) :-
	resolve_construct_triple(H0, H, S0, S1),
	resolve_construct_template(T0, T, S1, S).

resolve_construct_triple(rdf(S0,P0,O0), rdf(S,P,O), St0, St) :-
	resolve_graph_term(S0, S, St0, St1),
	resolve_graph_term(P0, P, St1, St2),
	resolve_graph_term(O0, O, St2, St).

%%	resolve_solutions(+Solutions0, -Solutions, -Goal, +State)
%	
resolve_solutions(distinct(S0), distinct(S), Goal, State) :- !,
	resolve_solutions(S0, S, Goal, State).
resolve_solutions(solutions(unsorted, Limit, Offset),
		  solutions(unsorted, Limit, Offset), true, _) :- !.
resolve_solutions(solutions(order_by(OrderBy0), Limit, Offset),
		  solutions(order_by(OrderBy),  Limit, Offset),
		  Goal, State) :-
	resolve_order_by_cols(OrderBy0, OrderBy, Goal, State).

resolve_order_by_cols([], [], true, _).
resolve_order_by_cols([H0|T0], [H|T], Goal, State) :-
	resolve_order_by_col(H0, H, G0, State),
	resolve_order_by_cols(T0, T, G1, State),
	mkconj(G0, G1, Goal).

resolve_order_by_col(ascending(O0), ascending(O), Goal, State) :- !,
	compile_expression(O0, O, Goal, State).
resolve_order_by_col(decending(O0), decending(O), Goal, State) :- !,
	compile_expression(O0, O, Goal, State).

%%	resolve_state(+Prolog, -State)
%	
%	Create initial state.  State is a term
%	
%%		state(Base, PrefixAssoc, VarAssoc, VarList)

resolve_state(prolog(PrefixesList), State, Options) :-
	option(base_uri(Base), Options, 'http://default.base.org'),
	resolve_state(prolog(Base, PrefixesList), State, Options).
resolve_state(prolog(Base, PrefixesList),
	      state(Base, Prefixes, Vars, []), _Options) :-
	list_to_assoc(PrefixesList, Prefixes),
	empty_assoc(Vars).

%%	resolve_graph_term(+T0, -T, +State0, -State)

resolve_graph_term(Var, Var, S, S) :-
	var(Var), !.
resolve_graph_term(var(Name), Var, S0, S) :- !,
	resolve_var(Name, Var, S0, S).
resolve_graph_term(T, IRI, S, S) :-
	resolve_iri(T, IRI, S), !.
resolve_graph_term(literal(type(IRI0, Value)),
		   literal(type(IRI, Value)), S, S) :- !,
	resolve_iri(IRI0, IRI, S).
resolve_graph_term(boolean(Val),
		   literal(type(Type, Val)), S, S) :- !,
	rdf_equal(Type, xsd:boolean).
resolve_graph_term(numeric(Type, Value),
		   literal(type(Type, Atom)), S, S) :-
	atom_number(Atom, Value).
resolve_graph_term(T, T, S, S).


%%	resolve_expression(+E0, -E, +State0, -State)

resolve_expression(Var, Var, S, S) :-
	var(Var), !.
resolve_expression(or(A0,B0), or(A,B), S0, S) :- !,
	resolve_expression(A0, A, S0, S1),
	resolve_expression(B0, B, S1, S).
resolve_expression(and(A0,B0), and(A,B), S0, S) :- !,
	resolve_expression(A0, A, S0, S1),
	resolve_expression(B0, B, S1, S).
resolve_expression(regex(V0,P0,F0), regex(V,P,F), S0, S) :- !,
	resolve_expression(V0, V, S0, S1),
	resolve_expression(P0, P, S1, S2),
	resolve_expression(F0, F, S2, S).
resolve_expression(E0, E, S0, S) :-
	expression_op(E0), !,
	E0 =.. [Op|Args0],
	resolve_expressions(Args0, Args, S0, S),
	E =.. [Op|Args].
resolve_expression(E0, E, S0, S) :-
	resolve_function(E0, E, S0, S), !.
resolve_expression(T0, T, S0, S) :-
	resolve_graph_term(T0, T, S0, S). 	% OK?

expression_op(_ = _).
expression_op(_ \= _).			% SPARQL !=
expression_op(_ =< _).			% SPARQL <=
expression_op(_ >= _).
expression_op(_ < _).
expression_op(_ > _).
expression_op(_ + _).
expression_op(_ - _).
expression_op(_ * _).
expression_op(_ / _).
expression_op(not(_)).			% SPARQL !(_)
expression_op(+ _).
expression_op(- _).


resolve_expressions([], [], S, S).
resolve_expressions([H0|T0], [H|T], S0, S) :-
	resolve_expression(H0, H, S0, S1),
	resolve_expressions(T0, T, S1, S).
		   
resolve_function(function(F0, Args0), function(Term), S0, S) :- !,
	resolve_iri(F0, F, S0),
	resolve_expressions(Args0, Args, S0, S),
	Term =.. [F|Args].
resolve_function(Builtin, Term, S0, S) :- !,
	built_in_function(Builtin), !,
	Builtin =.. [F|Args0],
	resolve_expressions(Args0, Args, S0, S),
	Term =.. [F|Args].

built_in_function(str(_)).
built_in_function(lang(_)).
built_in_function(langmatches(_,_)).
built_in_function(datatype(_)).
built_in_function(bound(_)).
built_in_function(isiri(_)).
built_in_function(isuri(_)).
built_in_function(isblank(_)).
built_in_function(isliteral(_)).


%%	resolve_var(+Name, -Var, +State0, ?State)
%	
%	Resolve a variable. If State0 ==  State   and  it concenrs a new
%	variable the variable is bound to '$null$'.

resolve_var(Name, Var, State, State) :-
	arg(3, State, Vars),
	get_assoc(Name, Vars, Var), !.
resolve_var(Name, Var, state(Base, Prefixes, Vars0, VL),
		       state(Base, Prefixes, Vars, [Name=Var|VL])) :- !,
	put_assoc(Name, Vars0, Var, Vars), !.
resolve_var(_, '$null$', State, State).

%%	resolve_iri(+Spec, -IRI:atom, +State) is det.
%
%	Translate Spec into a fully expanded IRI as used in RDF-DB. Note
%	that we must expand %xx sequences here.

resolve_iri(P:N, IRI, State) :- !,
	resolve_prefix(P, Prefix, State),
	url_iri(N, LocalIRI),
	atom_concat(Prefix, LocalIRI, IRI).
resolve_iri(URL0, IRI, State) :-
	atom(URL0),
	arg(1, State, Base),		% TBD: What if there is no base?
	global_url(URL0, Base, URL1),
	url_iri(URL1, IRI).

% compatibility with SWI-Prolog < 5.6.46
:- if(\+ source_exports(library(url), url_iri/2)).
:- use_module(library(rdf_parser), []).

url_iri(URL, IRI) :-
	rdf_parser:decode_uri(URL, IRI).

:- endif.

resolve_prefix(P, IRI, State) :-
	arg(2, State, Prefixes),
	(   get_assoc(P, Prefixes, IRI)
	->  true
	;   rdf_db:ns(P, IRI)		% Extension: database known
	->  true
	;   throw(error(existence_error(prefix, P), _))
	).


%%	resolve_bnodes(+Pattern0, -Pattern)
%	
%	Blank nodes are scoped into a   basic graph pattern (i.e. within
%	{...}). The code below  does  a   substitution  of  bnode(X)  to
%	variables in an arbitrary term.

resolve_bnodes(P0, P) :-
	empty_assoc(BN0),
	resolve_bnodes(P0, P, BN0, _).

resolve_bnodes(Var, Var, BN, BN) :-
	var(Var), !.
resolve_bnodes(bnode(Name), Var, BN0, BN) :- !,
	(   get_assoc(Name, BN0, Var)
	->  BN = BN0
	;   put_assoc(Name, BN0, Var, BN)
	).
resolve_bnodes(Term0, Term, BN0, BN) :-
	compound(Term0), !,
	functor(Term0, F, A),
	functor(Term, F, A),
	resolve_bnodes_args(0, A, Term0, Term, BN0, BN).
resolve_bnodes(Term, Term, BN, BN).

resolve_bnodes_args(A, A, _, _, BN, BN) :- !.
resolve_bnodes_args(I0, A, T0, T, BN0, BN) :-
	I is I0 + 1,
	arg(I, T0, A0),
	resolve_bnodes(A0, A1, BN0, BN1),
	arg(I, T, A1),
	resolve_bnodes_args(I, A, T0, T, BN1, BN).


		 /*******************************
		 *	COMPILE EXPRESSIONS	*
		 *******************************/

%%	compile_expression(+Expression, -Var, -Var, -Goal, +State)
%	
%	Compile an expression into a (compound)   goal that evaluates to
%	the variable var. This version is  not realy compiling. Its just
%	the entry point for a future compiler.

compile_expression(Expr0, Var, Goal, State) :-
	resolve_expression(Expr0, Expr, State, State),
	(   primitive(Expr)
	->  Var = Expr,
	    Goal = true
	;   Goal = sparql_eval(Expr, Var)
	).

primitive(Var)  :- var(Var), !.
primitive(Atom) :- atom(Atom).		% IRI, '$null$'


		 /*******************************
		 *	    SPARQL DCG		*
		 *******************************/

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
From A.7. We keep the same naming and   order of the productions to make
it as easy as possible to verify the correctness of the parser.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

%%	query(-Prolog, -Query)//

sparql_query(Prolog, Query, In, Out) :-
	catch(query(Prolog, Query, In, Out),
	      E,
	      add_error_location(E, In)).

query(Prolog, Query) -->
	skip_ws,
	prolog(Prolog),
	(   select_query(Query)
	;   construct_query(Query)
	;   describe_query(Query)
	;   ask_query(Query)
	), !.

%%	prolog(-Decls)//

prolog(prolog(Base, Prefixes)) -->
	base_decl(Base), !,
	prefix_decls(Prefixes).
prolog(prolog(Prefixes)) -->
	prefix_decls(Prefixes).

prefix_decls([H|T]) -->
	prefix_decl(H), !,
	prefix_decls(T).
prefix_decls([]) -->
	[].

%%	base_decl(-Base:uri)// is semidet.
%
%	Match "base <URI>"

base_decl(Base) -->
	keyword("base"),
	q_iri_ref(Base).

%%	prefix_decl(-Prefix)// is semidet.
%
%	Process "prefix <qname> <URI>" into a term Qname-IRI

prefix_decl(Id-IRI) -->
	keyword("prefix"),
	(   qname_ns(Id),
	    q_iri_ref(IRI)
	->  ""
	;   syntax_error(illegal_prefix_declaration)
	).

%%	select_query(-Select)// is semidet.
%
%	Process "select ..." into a term
%	
% 	select(Projection, DataSets, Query, Solutions)

select_query(select(Projection, DataSets, Query, Solutions)) -->
	keyword("select"),
	(   keyword("distinct")
	->  { Solutions = distinct(S0) }
	;   { Solutions = S0 }
	),
	select_projection(Projection),
	data_sets(DataSets),
	where_clause(Query),
	solution_modifier(S0).

select_projection(*) --> "*", !, skip_ws.
select_projection([H|T]) -->
	var(H),
	vars(T), !.
select_projection(_) -->
	syntax_error(projection_expected).

vars([H|T]) -->
	var(H),
	vars(T).
vars([]) -->
	[].


%%	construct_query(-Construct)// is semidet.
%
%	Processes "construct ..." into a term
%	
%	construct(Template, DataSets, Query, Solutions)

construct_query(construct(Template, DataSets, Query, Solutions)) -->
	keyword("construct"),
	construct_template(Template),
	data_sets(DataSets),
	where_clause(Query),
	solution_modifier(Solutions).

%%	describe_query(-Describe)// is semidet.
%
%	Processes "describe ..." into a term
%	
%	describe(Projection, DataSets, Query, Solutions)

describe_query(describe(Projection, DataSets, Query, Solutions)) -->
	keyword("describe"),
	desc_projection(Projection),
	data_sets(DataSets),
	(where_clause(Query) -> [] ; {Query = true}),
	solution_modifier(Solutions).

desc_projection(*) --> "*", !, skip_ws.
desc_projection([H|T]) -->
	var_or_iri_ref(H), !,
	var_or_iri_refs(T).
desc_projection(_) -->
	syntax_error(projection_expected).

var_or_iri_refs([H|T]) -->
	var_or_iri_ref(H), !,
	var_or_iri_refs(T).
var_or_iri_refs([]) -->
	[].

%%	ask_query(Query)//

ask_query(ask(DataSets, Query)) -->
	keyword("ask"),
	data_sets(DataSets),
	where_clause(Query).

data_sets([H|T]) -->
	dataset_clause(H), !,
	data_sets(T).
data_sets([]) -->
	[].

%%	dataset_clause(-Src)//

dataset_clause(Src) -->
	keyword("from"),
	(   default_graph_clause(Src)
	->  []
	;   named_graph_clause(Src)
	).

%%	default_graph_clause(-Src)

default_graph_clause(Src) -->
	source_selector(Src).

%%	named_graph_clause(Graph)//

named_graph_clause(Src) -->
	keyword("named"),
	source_selector(Src).

%%	source_selector(-Src)//

source_selector(Src) -->
	iri_ref(Src).

%%	where_clause(-Pattern)//

where_clause(Pattern) -->
	keyword("where"), !,
	must_see_group_graph_pattern(Pattern).
where_clause(Pattern) -->
	group_graph_pattern(Pattern).

must_see_group_graph_pattern(Pattern) -->
	group_graph_pattern(Pattern), !.
must_see_group_graph_pattern(_) -->
	syntax_error(group_graph_pattern_expected).


%%	solution_modifier(-Solutions)// is det.
%
%	Processes order by, limit and offet clauses into a term
%	
%	solutions(Order, Limit, Offset)

solution_modifier(solutions(Order, Limit, Offset)) -->
	( order_clause(Order)   -> [] ; { Order  = unsorted } ),
	( limit_clause(Limit)   -> [] ; { Limit  = inf } ),
	( offset_clause(Offset) -> [] ; { Offset = 0 } ).

%%	order_clause(-Order)//

order_clause(order_by([H|T])) -->
	keyword("order"), 
	(   keyword("by"),
	    order_condition(H),
	    order_conditions(T)
	->  ""
	;   syntax_error(illegal_order_clause)
	).

order_conditions([H|T]) -->
	order_condition(H), !,
	order_conditions(T).
order_conditions([]) -->
	[].

%%	order_condition(-Order)//

order_condition(ascending(Expr)) -->
	keyword("asc"), !,
	bracketted_expression(Expr).
order_condition(decending(Expr)) -->
	keyword("desc"), !,
	bracketted_expression(Expr).
order_condition(ascending(Value)) -->
	(   function_call(Value)
	;   var(Value)
	;   bracketted_expression(Value)
	), !.


%%	limit_clause(-Limit)//

limit_clause(Limit) -->
	keyword("limit"),
	integer(Limit).


%%	offset_clause(Offset)//

offset_clause(Offset) -->
	keyword("offset"),
	integer(Offset).


%%	group_graph_pattern(P)//

group_graph_pattern(P) -->
	"{", skip_ws,
	(   graph_pattern(P0)
	->  (   "}"
	    ->  skip_ws
	    ;	syntax_error(expected('}'))
	    ),
	    { resolve_bnodes(P0, P) }
	;   syntax_error(graph_pattern_expected)
	).


%%	graph_pattern(P)//

graph_pattern(P) -->
	filtered_basic_graph_pattern(P1),
	(   graph_pattern_not_triples(P2)
	->  optional_dot,
	    graph_pattern(P3),
	    { P = (P1,P2,P3) }
	;   { P = P1 }
	).


%%	filtered_basic_graph_pattern(P)

filtered_basic_graph_pattern(P) -->
	(   block_of_triples(P1)
	->  ""
	;   {P1=true}
	),
	(   constraint(C)
	->  optional_dot,
	    filtered_basic_graph_pattern(P2),
	    { P = (P1,C,P2) }
	;   { P = P1 }
	).

optional_dot --> ".", skip_ws.
optional_dot --> "".

%%	block_of_triples(P)//
%	
%	Looks the same to me??

block_of_triples(P) --> 
	block_of_triples(P, []).

block_of_triples(List, T) -->
	triples_same_subject(List, T0),
	block_of_triples_cont(T0, T).

block_of_triples_cont(List, T) -->
	".", skip_ws,
	triples_same_subject(List, T0), !,
	block_of_triples_cont(T0, T).
block_of_triples_cont(List, T) -->
	".", !, skip_ws,
	block_of_triples_cont(List, T).
block_of_triples_cont(T, T) -->
	"".

%%	graph_pattern_not_triples(-Pattern)//

graph_pattern_not_triples(P) --> optional_graph_pattern(P), !.
graph_pattern_not_triples(P) --> group_or_union_graph_pattern(P), !.
graph_pattern_not_triples(P) --> graph_graph_pattern(P).

%%	optional_graph_pattern(Pattern)//

optional_graph_pattern(Pattern) -->
	keyword("optional"),
	must_see_group_graph_pattern(P0),
	{ Pattern = optional(P0) }.

%%	graph_graph_pattern(-Graph)// is semidet
%
%	Processes a "graph ..." clause into
%	
%	graph(Graph, Pattern)

graph_graph_pattern(graph(Graph, Pattern)) -->
	keyword("graph"), !,
	var_or_blank_node_or_iri_ref(Graph),
	must_see_group_graph_pattern(Pattern).

%%	group_or_union_graph_pattern(-Pattern)//

group_or_union_graph_pattern(Pattern) -->
	group_graph_pattern(P0),
	add_union(P0, Pattern).

add_union(P0, (P0;P)) -->
	keyword("union"), !,
	must_see_group_graph_pattern(P1),
	add_union(P1, P).
add_union(P, P) -->
	[].


%%	constraint(-Filter)//

constraint(ebv(Exp)) -->
	keyword("filter"),
	(   bracketted_expression(Exp)
	->  []
	;   built_in_call(Exp)
	->  ""
	;   function_call(Exp)
	->  ""
	;   syntax_error(filter_expected)
	).

%%	function_call(-Function)// is semidet.
%
%	Processes <URI>(Arg ...) into function(IRI, Args)

function_call(function(F, Args)) -->
	iri_ref(F),
	arg_list(Args).

%%	arg_list(-List)//

arg_list(List) -->
	"(", skip_ws,
	(   must_see_expression(A0)
	->  arg_list_cont(As),
	    {List = [A0|As]}
	;   {List = []}
	),
	")", skip_ws.

arg_list_cont([H|T]) -->
	",", !, skip_ws,
	must_see_expression(H),
	arg_list_cont(T).
arg_list_cont([]) -->
	[].

%%	construct_template(Triples)//

construct_template(Triples) -->
	"{", skip_ws, construct_triples(Triples), "}", !, skip_ws.
construct_template(_) -->
	syntax_error(construct_template_expected).

%%	construct_triples(-List)//

construct_triples(List) -->
	construct_triples(List, []).

construct_triples(List, T) -->
	triples_same_subject(List, T0), !,
	(   ".", skip_ws
	->  (   peek("}")
	    ->  { T = T0 }
	    ;   construct_triples(T0, T)
	    )
	;   { T = T0 }
	).
construct_triples(T, T) -->
	"".

%%	triples_same_subject(-List, ?Tail)//
%	
%	Return list of rdf(S,P,O) from triple spec.

triples_same_subject(List, Tail) -->
	var_or_term(S), !,
	property_list_not_empty(L, List, T0),
	{ make_triples_same_subject(L, S, T0, Tail) }.
triples_same_subject(List, Tail) -->
	triples_node(S, List, T0),
	property_list(L, T0, T1),
	{ make_triples_same_subject(L, S, T1, Tail) }.

make_triples_same_subject([], _, T, T).
make_triples_same_subject([property(P,O)|TP], S, [rdf(S,P,O)|T0], T) :-
	make_triples_same_subject(TP, S, T0, T).

%%	property_list(-L, -Triples, ?TriplesTail)//

property_list(L, Triples, Tail) -->
	property_list_not_empty(L, Triples, Tail), !.
property_list([], Tail, Tail) --> [].

%%	property_list_not_empty(-L, -Triples, ?TriplesTail)//

property_list_not_empty(E, Triples, Tail) -->
	verb(P),
	must_see_object_list(OL, Triples, T0),
	{ mk_proplist(OL, P, E, T) },
	(   ";", skip_ws
	->  property_list(T, T0, Tail)
	;   { T = [],
	      Tail = T0
	    }
	).

mk_proplist([], _, T, T).
mk_proplist([O|OT], P, [property(P,O)|T0], T) :-
	mk_proplist(OT, P, T0, T).

%%	object_list(-L, -Triples, ?TriplesTail)//

object_list(List, Triples, Tail) -->
	graph_node(H, Triples, T0),
	(   ",", skip_ws
	->  objects(T, T0, Tail),
	    { List = [H|T] }
	;   { List = [H],
	      Tail = T0
	    }
	).

must_see_object_list(List, Triples, Tail) -->
	object_list(List, Triples, Tail), !.
must_see_object_list(_,_,_) -->
	syntax_error(object_list_expected).


objects([H|T], Triples, Tail) -->
	graph_node(H, Triples, T0), !,
	objects(T, T0, Tail).
objects([], Tail, Tail) -->
	[].

%%	verb(-E)//

verb(E) --> var_or_iri_ref(E), !.
verb(E) --> "a", skip_ws, { rdf_equal(E, rdf:type) }.

%%	triples_node(-Subj, -Triples, ?TriplesTail)//

triples_node(Subj, Triples, Tail) -->
	collection(Subj, Triples, Tail), !.
triples_node(Subj, Triples, Tail) -->
	blank_node_property_list(Subj, Triples, Tail).

%%	blank_node_property_list(-Subj, -Triples, ?TriplesTail)//

blank_node_property_list(Subj, Triples, Tail) -->
	"[", skip_ws,
	property_list_not_empty(List, Triples, T0),
	"]", skip_ws,
	{ make_triples_same_subject(List, Subj, T0, Tail) }.

%%	collection(-Subj, -Triples, ?TriplesTail)//

collection(CollSubj, Triples, Tail) -->
	"(", skip_ws,
	graph_node(H, Triples, T0),
	graph_nodes(T, T0, T1),
	")", skip_ws,
	{ mkcollection([H|T], CollSubj, T1, Tail) }.

mkcollection([Last], S, [ rdf(S, rdf:first, Last),
			  rdf(S, rdf:rest, rdf:nil)
			| Tail
			], Tail) :- !.
mkcollection([H|T], S, [ rdf(S, rdf:first, H),
			 rdf(S, rdf:rest, R)
		       | RDF
		       ], Tail) :-
	mkcollection(T, R, RDF, Tail).

graph_nodes([H|T], Triples, Tail) -->
	graph_node(H, Triples, T0), !,
	graph_nodes(T, T0, Tail).
graph_nodes([], T, T) --> [].

%%	graph_node(E, -Triples, ?TriplesTail)//

graph_node(E, T, T)       --> var_or_term(E), !.
graph_node(E, Triples, T) --> triples_node(E, Triples, T).

%%	var_or_term(-E)//

var_or_term(E) --> var(E), !.
var_or_term(E) --> graph_term(E).

%%	var_or_iri_ref(-E)//

var_or_iri_ref(E) --> var(E), !.
var_or_iri_ref(E) --> iri_ref(E), !.

%%	var_or_blank_node_or_iri_ref(-E)//

var_or_blank_node_or_iri_ref(T) --> var(T), !.
var_or_blank_node_or_iri_ref(T) --> blank_node(T), !.
var_or_blank_node_or_iri_ref(T) --> iri_ref(T), !.

%%	var(-Var)//

var(var(Name)) -->
	(   var1(Name)
	->  []
	;   var2(Name)
	),
	skip_ws.

%%	graph_term(-T)//

graph_term(T)    --> iri_ref(T), !.
graph_term(T)    --> rdf_literal(T), !.
graph_term(-(T)) --> "-", !, numeric_literal(T).
graph_term(T)    --> "+", !, numeric_literal(T).
graph_term(T)    --> numeric_literal(T), !.
graph_term(T)    --> boolean_literal(T), !.
graph_term(T)	 --> blank_node(T).
graph_term(T)	 --> nil(T).


%%	expression(-E)//

expression(E) -->
	conditional_or_expression(E),
	skip_ws.

must_see_expression(E) -->
	expression(E), !.
must_see_expression(_) -->
	syntax_error(expression_expected).

%%	conditional_or_expression(-E)//

conditional_or_expression(E) -->
	conditional_and_expression(E0),
	or_args(E0, E).

or_args(E0, or(E0,E)) --> "||", !, skip_ws, value_logical(E1), or_args(E1, E).
or_args(E, E) --> [].

%%	conditional_and_expression(-E)//

conditional_and_expression(E) -->
	value_logical(E0),
	and_args(E0, E).

and_args(E0, and(E0,E)) --> "&&", !, skip_ws, value_logical(E1), and_args(E1, E).
and_args(E, E) --> [].


%%	value_logical(-E)//

value_logical(E) --> relational_expression(E).

%%	relational_expression(E)//

relational_expression(E) -->
	numeric_expression(E0),
	(   relational_op(Op)
	->  skip_ws,
	    numeric_expression(E1),
	    { E =.. [Op,E0,E1] }
	;   { E = E0 }
	).

relational_op(=) --> "=".
relational_op(\=) --> "!=".
relational_op(=<) --> "<=".
relational_op(>=) --> ">=".
relational_op(<) --> "<".
relational_op(>) --> ">".


%%	numeric_expression(-E)//

numeric_expression(E) -->
	additive_expression(E).

%%	additive_expression(-E)//

additive_expression(E) -->
	multiplicative_expression(E0),
	add_args(E0, E).

add_args(E0, E0+E) --> "+", !, skip_ws,
	multiplicative_expression(E1), add_args(E1, E).
add_args(E0, E0-E) --> "-", !, skip_ws,
	multiplicative_expression(E1), add_args(E1, E).
add_args(E, E) --> [].



%%	multiplicative_expression(-E)//

multiplicative_expression(E) -->
	unary_expression(E0),
	mult_args(E0, E).

mult_args(E0, E0*E) --> "*", !, skip_ws,
	unary_expression(E1), mult_args(E1, E).
mult_args(E0, E0/E) --> "/", !, skip_ws,
	unary_expression(E1), mult_args(E1, E).
mult_args(E, E) --> [].


%%	unary_expression(-E)//

unary_expression(not(E)) --> "!", skip_ws, primary_expression(E).
unary_expression(+(E))   --> "+", skip_ws, primary_expression(E).
unary_expression(-(E))   --> "-", skip_ws, primary_expression(E).
unary_expression(E)      -->      	 primary_expression(E).


%%	primary_expression(-E)//

primary_expression(E) --> bracketted_expression(E), !.
primary_expression(E) --> built_in_call(E), !.
primary_expression(E) --> iri_ref_or_function(E), !.
primary_expression(E) --> rdf_literal(E), !.
primary_expression(E) --> numeric_literal(E), !.
primary_expression(E) --> boolean_literal(E), !.
primary_expression(E) --> blank_node(E), !.
primary_expression(E) --> var(E), !.


%%	bracketted_expression(-E)//

bracketted_expression(E) -->
	"(", skip_ws, must_see_expression(E), ")", skip_ws.

%%	built_in_call(-Call)//

built_in_call(F) -->
	get_keyword(KWD),
	{ built_in_function(KWD, Types) },
	"(", skip_ws, arg_list(Types, Args), ")", skip_ws,
	{ F =.. [KWD|Args] }.
built_in_call(Regex) -->
	regex_expression(Regex).

built_in_function(str,	       [expression]).
built_in_function(lang,	       [expression]).
built_in_function(langmatches, [expression, expression]).
built_in_function(datatype,    [expression]).
built_in_function(bound,       [var]).
built_in_function(isiri,       [expression]).
built_in_function(isuri,       [expression]).
built_in_function(isblank,     [expression]).
built_in_function(isliteral,   [expression]).
	
arg_list([HT|TT], [HA|TA]) -->
	arg(HT, HA),
	arg_list_cont(TT, TA).

arg_list_cont([], []) -->
	[].
arg_list_cont([H|T], [A|AT]) -->
	",", skip_ws,
	arg(H, A),
	arg_list_cont(T, AT).

arg(expression, A) --> expression(A).
arg(var,        A) --> var(A).

%%	regex_expression(-Regex)//

regex_expression(regex(Target, Pattern, Flags)) -->
	keyword("regex"),
	"(", skip_ws,
	must_see_expression(Target), ",", skip_ws,
	must_see_expression(Pattern),
	(   ",", skip_ws, must_see_expression(Flags)
	->  []
	;   {Flags = literal('')}
	),
	")", skip_ws.

%%	iri_ref_or_function(-Term)//

iri_ref_or_function(Term) -->
	iri_ref(IRI),
	(   arg_list(Args)
	->  { Term = function(IRI, Args) }
	;   { Term = IRI }
	).

%%	rdf_literal(-Literal)//

rdf_literal(literal(Value)) -->
	string(String),
	(   langtag(Lang)
	->  { Value = lang(Lang, String) }
	;   "^^", iri_ref(IRI)
	->  { Value = type(IRI, String) }
	;   { Value = String }
	),
	skip_ws.

%%	numeric_literal(-Number)//

numeric_literal(numeric(Type, Number)) --> 
	(   double(Number)
	->  { rdf_equal(xsd:double, Type) }
	;   decimal(Number)
	->  { rdf_equal(xsd:decimal, Type) }
	;   integer(Number)
	->  { rdf_equal(xsd:integer, Type) }
	), !,
	skip_ws.

%%	boolean_literal(-TrueOrFalse)//

boolean_literal(Lit) -->
	(   keyword("true")
	->  { Lit = boolean(true) }
	;   keyword("false")
	->  { Lit = boolean(false) }
	).

%%	string(-Atom)//

string(Atom) --> string_literal_long1(Atom), !.
string(Atom) --> string_literal_long2(Atom), !.
string(Atom) --> string_literal1(Atom), !.
string(Atom) --> string_literal2(Atom).

%%	iri_ref(IRI)//

iri_ref(IRI) -->
	q_iri_ref(IRI).
iri_ref(IRI) -->
	qname(IRI).			% TBD: qname_ns also returns atom!?

%%	qname(-Term)//
%	
%	TBD: Looks like this is ambiguous!?

qname(Term) -->
	'QNAME'(Term), !, skip_ws.
qname(Q:'') -->
	qname_ns(Q).

%%	blank_node(-Id)//
%	
%	Blank node.  Anonymous blank nodes are returned with unbound Id

blank_node(Id) -->
	blank_node_label(Id), !.
blank_node(Id) -->
	anon(Id).

		 /*******************************
		 *	       BASICS		*
		 *******************************/

%%	q_iri_ref(-Atom)//

q_iri_ref(Atom) -->
	"<",
	(    q_iri_ref_codes(Codes), ">"
	->   skip_ws,
	     { atom_codes(Atom, Codes) }
	;    syntax_error(illegal_qualified_iri)
	).

q_iri_ref_codes([]) -->
	[].
q_iri_ref_codes([H|T]) -->
	iri_code(H), !,
	q_iri_ref_codes(T).
q_iri_ref_codes(_) -->
	syntax_error(illegal_code_in_iri).

iri_code(Code) -->
	[Code],
	{ \+ not_iri_code(Code) }, !.
iri_code(Code) -->
	uchar(Code).

not_iri_code(0'<).
not_iri_code(0'>).
not_iri_code(0'').
not_iri_code(0'{).
not_iri_code(0'}).
not_iri_code(0'|).
not_iri_code(0'\\).			% not sure!?
not_iri_code(0'`).
not_iri_code(Code) :- between(0x00, 0x20, Code).


%%	qname_ns(Q)//

qname_ns(Q) -->
	ncname_prefix(Q), ":", !, skip_ws.
qname_ns('') -->
	":", skip_ws.

%	'QNAME'(-Term)//
%	
%	Qualified name.  Term is one of Q:N or '':N

'QNAME'(Q:N) -->
	ncname_prefix(Q), ":", !, ncname(N).
'QNAME'('':N) -->
	":", ncname(N).


%%	blank_node_label(-Bnode)// is semidet.
%
%	Processes "_:..." into a bnode(Name) term.

blank_node_label(bnode(Name)) -->
	"_:", ncname(Name), skip_ws.


%%	var1(-Atom)// is semidet.
%%	var2(-Atom)// is semidet.

var1(Name) --> "?", varname(Name).
var2(Name) --> "$", varname(Name).


%%	langtag(-Tag)//
%	
%	Return language tag (without leading @)

langtag(Atom) -->
	"@",
	one_or_more_ascii_letters(Codes, T0),
	sub_lang_ids(T0, []),
	skip_ws,
	{ atom_codes(Atom, Codes) }.

sub_lang_ids([0'-|Codes], Tail) -->
	"-", !,
	one_or_more_ascii_letter_or_digits(Codes, T0),
	sub_lang_ids(T0, Tail).
sub_lang_ids(T, T) -->
	[].


%%	integer(-Integer)//
%	
%	Extract integer value.

integer(Int) -->
	one_or_more_digits(Codes, []), !,
	skip_ws,
	{ number_codes(Int, Codes) }.


%%	decimal(-Float)//
%	
%	Extract float without exponent and return numeric value.
%	TBD: merge with double?

decimal(Float) -->
	one_or_more_digits(Codes, T0), !,
	dot(T0, T1),
	digits(T1, "0"),		% extra 0 to ensure at least one
	{ number_codes(Float, Codes) }.
decimal(Float) -->
	dot(Codes, T1),
	one_or_more_digits(T1, []),
	{ number_codes(Float, Codes) }.


%%	double(-Float)//
%	
%	Extract a float number with  exponent   and  return  the numeric
%	value.

double(Float) -->
	one_or_more_digits(Codes, T0), !,
	dot(T0, T1),
	digits(T1, T2),
	exponent(T2, []),
	{ number_codes(Float, Codes) }.
double(Float) -->
	dot(Codes, T1),
	one_or_more_digits(T1, T2), !,
	exponent(T2, []),
	{ number_codes(Float, Codes) }.
double(Float) -->
	one_or_more_digits(Codes, T2), !,
	exponent(T2, []),
	{ number_codes(Float, Codes) }.


dot([0'.|T], T) --> ".".


%%	exponent(-Codes, ?Tail)//
%	
%	Float exponent.  Returned as difference-list

exponent(Codes, T) -->
	optional_e(Codes, T0),
	optional_pm(T0, T1),
	one_or_more_digits(T1, T).
	
optional_e([0'e|T], T) -->
	(   "e"
	;   "E"
	), !.
optional_e(T, T) -->
	"".

optional_pm([C|T], T) -->
	[C],
	{ C == 0'+ ; C == 0'- }, !.
optional_pm(T, T) -->
	"".

%%	string_literal1(-Atom)//

string_literal1(Atom) -->
	"'", !,
	string_literal_codes(Codes),
	"'", !,
	{ atom_codes(Atom, Codes) }.

%%	string_literal2(-Atom)//

string_literal2(Atom) -->
	"\"", !,
	string_literal_codes(Codes),
	"\"", !,
	{ atom_codes(Atom, Codes) }.

string_literal_codes([]) -->
	"".
string_literal_codes([H|T]) -->
	(   echar(H)
	;   uchar(H)
	;   [H], { \+ not_in_string_literal(H) }
	), 
	string_literal_codes(T).

not_in_string_literal(0x5C).
not_in_string_literal(0x0A).
not_in_string_literal(0x0D).

%%	string_literal_long1(-Atom)//

string_literal_long1(Atom) -->
	"'''", !,
	string_literal_codes_long(Codes),
	"'''", !,
	{ atom_codes(Atom, Codes) }.

%%	string_literal_long2(-Atom)//

string_literal_long2(Atom) -->
	"\"\"\"", !,
	string_literal_codes_long(Codes),
	"\"\"\"", !,
	{ atom_codes(Atom, Codes) }.

string_literal_codes_long([]) -->
	"".
string_literal_codes_long([H|T]) -->
	(   echar(H)
	;   uchar(H)
	;   [H], { H \== 0'\\ }
	), 
	string_literal_codes_long(T).


%%	echar(-Code)//
%	
%	Escaped character

echar(Code) -->
	"\\", echar2(Code).

echar2(0'\t) --> "t".
echar2(0'\b) --> "b".
echar2(0'\n) --> "n".
echar2(0'\r) --> "r".
echar2(0'\f) --> "f".
echar2(0'\\) --> "\\".
echar2(0'")  --> "\"".
echar2(0'')  --> "'".

%%	uchar(-Code)//
%	
%	\uXXXX or \UXXXXXXXX, returning character value

uchar(Code) -->
	"\\u", !,
	(   hex(D1), hex(D2), hex(D3), hex(D4)
	->  { Code is D1<<24 + D2<<16 + D3<<8 + D4 }
	;   syntax_error(illegal_uchar)
	).
uchar(Code) -->
	"\\U", !,
	(   hex(D1), hex(D2), hex(D3), hex(D4),
	    hex(D5), hex(D6), hex(D7), hex(D8)
	->  { Code is D1<<56 + D2<<48 + D3<<40 + D4<<32 +
	              D5<<24 + D6<<16 + D7<<8 + D8 }
	;   syntax_error(illegal_Uchar)
	).

%%	hex(-Weigth)//
%	
%	HEX digit (returning numeric value)

hex(Weigth) -->
	[C],
	{ code_type(C, xdigit(Weigth)) }.


%%	nil(-NIL)//
%	
%	End-of-collection (rdf:nil)

nil(NIL) --> "(", ws_star, ")", skip_ws, { rdf_equal(NIL, rdf:nil) }.

%	ws//
%	
%	white space characters.

ws --> [0x20].
ws --> [0x09].
ws --> [0x0D].
ws --> [0x0A].

%	ws_star//

ws_star --> ws, !, ws_star.
ws_star --> "".

%	anon//
%	
%	Anonymous resource

anon(bnode(_)) --> "[", ws_star, "]", skip_ws.


%%	ncchar1p(-Code)//
%	
%	Basic identifier characters

ncchar1p(Code) -->
	esc_code(Code),
	{ ncchar1p(Code) }, !.

ncchar1p(Code) :- between(0'A, 0'Z, Code).
ncchar1p(Code) :- between(0'a, 0'z, Code).
ncchar1p(Code) :- between(0x00C0, 0x00D6, Code).
ncchar1p(Code) :- between(0x00D8, 0x00F6, Code).
ncchar1p(Code) :- between(0x00F8, 0x02FF, Code).
ncchar1p(Code) :- between(0x0370, 0x037D, Code).
ncchar1p(Code) :- between(0x037F, 0x1FFF, Code).
ncchar1p(Code) :- between(0x200C, 0x200D, Code).
ncchar1p(Code) :- between(0x2070, 0x218F, Code).
ncchar1p(Code) :- between(0x2C00, 0x2FEF, Code).
ncchar1p(Code) :- between(0x3001, 0xD7FF, Code).
ncchar1p(Code) :- between(0xF900, 0xFDCF, Code).
ncchar1p(Code) :- between(0xFDF0, 0xFFFD, Code).
ncchar1p(Code) :- between(0x10000, 0xEFFFF, Code).

esc_code(Code) -->
	uchar(Code), !.
esc_code(Code) -->
	[ Code ].

%%	ncchar1(-Code)//
%	
%	Allows for _

ncchar1(Code) -->
	esc_code(Code),
	{ ncchar1(Code) }.

ncchar1(Code) :-
	ncchar1p(Code).
ncchar1(0'_).


%%	varname(-Atom)//
%	
%	Name of a variable (after the ? or $)

varname(Atom) -->
	varchar1(C0),
	varchars(Cs),
	{ atom_codes(Atom, [C0|Cs]) },
	skip_ws.

varchar1(Code) -->
	esc_code(Code),
	{ varchar1(Code) }.

varchar1(Code) :-
	ncchar1(Code), !.
varchar1(Code) :-
	between(0'0, 0'9, Code).

varchars([H|T]) -->
	varchar(H), !,
	varchars(T).
varchars([]) -->
	[].

varchar(Code) -->
	esc_code(Code),
	{ varchar(Code) }.

varchar(Code) :-
	varchar1(Code), !.
varchar(Code) :-
	varchar_extra(Code), !.

varchar_extra(0x00B7).
varchar_extra(Code) :- between(0x0300, 0x036F, Code).
varchar_extra(Code) :- between(0x203F, 0x2040, Code).

ncchar(Code) :-
	varchar(Code), !.
ncchar(0'-).

%%	ncname_prefix(-Atom)//

ncname_prefix(Atom) -->
	ncchar1p(C0),
	(   ncname_prefix_suffix(Cs)
	->  { atom_codes(Atom, [C0|Cs]) }
        ;   { char_code(Atom, C0) }
	).

ncname_prefix_suffix(Codes) -->
	ncchar_or_dots(Codes, []),
	{ \+ append(_, [0'.], Codes) }.

ncchar_or_dots([H|T0], T) -->
	ncchar_or_dot(H), !,
	ncchar_or_dots(T0, T).
ncchar_or_dots(T, T) -->
	[].
	
ncchar_or_dot(Code) -->
	esc_code(Code),
	{ ncchar_or_dot(Code) }.

ncchar_or_dot(Code) :-
	ncchar(Code), !.
ncchar_or_dot(0'.).

%%	ncname(-Atom)//

ncname(Atom) -->
	ncchar1(C0),
	(   ncname_prefix_suffix(Cs)
	->  { atom_codes(Atom, [C0|Cs]) }
        ;   { char_code(Atom, C0) }
	).

		 /*******************************
		 *	      EXTRAS		*
		 *******************************/

digit(Code) -->
	[Code],
	{ between(0'0, 0'9, Code) }.

ascii_letter(Code) -->
	[Code],
	{ between(0'a, 0'z, Code)
	; between(0'A, 0'Z, Code)
	}, !.

ascii_letter_or_digit(Code) -->
	[Code],
	{ between(0'a, 0'z, Code)
	; between(0'A, 0'Z, Code)
	; between(0'0, 0'9, Code)
	}, !.

digits([H|T0], T) -->
	digit(H), !,
	digits(T0, T).
digits(T, T) -->
	[].

ascii_letters([H|T0], T) -->
	ascii_letter(H), !,
	ascii_letters(T0, T).
ascii_letters(T, T) -->
	[].

ascii_letter_or_digits([H|T0], T) -->
	ascii_letter_or_digit(H), !,
	ascii_letter_or_digits(T0, T).
ascii_letter_or_digits(T, T) -->
	[].

one_or_more_digits([C0|CT], Tail) -->
	digit(C0),
	digits(CT, Tail).

one_or_more_ascii_letters([C0|CT], Tail) -->
	ascii_letter(C0),
	ascii_letters(CT, Tail).

one_or_more_ascii_letter_or_digits([C0|CT], Tail) -->
	ascii_letter_or_digit(C0),
	ascii_letter_or_digits(CT, Tail).

%%	keyword(+Codes)
%	
%	Case-insensitive match for a keyword.

keyword([]) -->
	(  ascii_letter(_)
	-> !, {fail}
	;  skip_ws
	).
keyword([H|T]) -->
	[C],
	{ code_type(H, to_lower(C)) },
	keyword(T).

%%	get_keyword(-Atom)
%	
%	Get next identifier as lowercase

get_keyword(Atom) -->
	one_or_more_ascii_letters(Letters, []),
	{ atom_codes(Raw, Letters),
	  downcase_atom(Raw, Atom)
	},
	skip_ws.

%	skip_ws//

skip_ws -->
	ws, !,
	skip_ws.
skip_ws -->
	"#", !,
	skip_comment,
	skip_ws.
skip_ws -->
	[].

skip_comment --> "\n", !.
skip_comment --> "\r", !.
skip_comment --> eos, !.
skip_comment --> [_], skip_comment.

eos([], []).

peek(C, T, T) :-
	append(C, _, T), !.