linked-data-python 0.0.4__py3-none-any.whl → 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ldpy/__init__.py +35 -11
- ldpy/__main__.py +75 -250
- ldpy/build.py +91 -0
- ldpy/console.py +116 -0
- ldpy/debug.py +235 -0
- ldpy/formatter.py +329 -0
- ldpy/importer.py +110 -0
- ldpy/lsp/__init__.py +11 -0
- ldpy/lsp/__main__.py +4 -0
- ldpy/lsp/backend.py +118 -0
- ldpy/lsp/rpc.py +104 -0
- ldpy/lsp/server.py +353 -0
- ldpy/lsp/translate.py +140 -0
- ldpy/pygments_lexer.py +613 -0
- ldpy/runtime.py +931 -0
- ldpy/sparql.py +551 -0
- ldpy/transpiler/__init__.py +14 -0
- ldpy/transpiler/core.py +2558 -0
- ldpy/transpiler/errors.py +38 -0
- ldpy/transpiler/linemap.py +292 -0
- linked_data_python-0.2.0.dist-info/METADATA +158 -0
- linked_data_python-0.2.0.dist-info/RECORD +26 -0
- {linked_data_python-0.0.4.dist-info → linked_data_python-0.2.0.dist-info}/WHEEL +1 -1
- linked_data_python-0.2.0.dist-info/entry_points.txt +9 -0
- {linked_data_python-0.0.4.dist-info → linked_data_python-0.2.0.dist-info/licenses}/LICENSE.md +0 -0
- {linked_data_python-0.0.4.dist-info → linked_data_python-0.2.0.dist-info}/top_level.txt +0 -0
- ldpy/grun/lib.py +0 -63
- ldpy/grun/util.py +0 -11
- ldpy/ldpy.py +0 -183
- ldpy/rewriter/IndentedStringWriter.py +0 -54
- ldpy/rewriter/LDPythonRewriter.py +0 -677
- ldpy/rewriter/MultiChannelTokenStream.py +0 -127
- ldpy/rewriter/Result.py +0 -49
- ldpy/rewriter/__init__.py +0 -7
- ldpy/rewriter/antlr/LDPythonLexer.py +0 -870
- ldpy/rewriter/antlr/LDPythonParser.py +0 -9336
- ldpy/rewriter/antlr/LDPythonVisitor.py +0 -573
- ldpy/sparql/builtin.py +0 -326
- linked_data_python-0.0.4.dist-info/METADATA +0 -139
- linked_data_python-0.0.4.dist-info/RECORD +0 -20
- linked_data_python-0.0.4.dist-info/entry_points.txt +0 -3
ldpy/sparql.py
ADDED
|
@@ -0,0 +1,551 @@
|
|
|
1
|
+
"""Evaluation semantics of SPARQL expression nodes.
|
|
2
|
+
|
|
3
|
+
`e{ <SPARQL expression> }` transpiles to a DEFERRED ``Expression`` object:
|
|
4
|
+
where ``f{...}``/``?{...}`` evaluate immediately, an Expression evaluates
|
|
5
|
+
later, against a *solution mapping*:
|
|
6
|
+
|
|
7
|
+
majeur = e{ ?age >= 18 && BOUND(?nom) }
|
|
8
|
+
majeur({"age": 20, "nom": "Ana"}) # -> Literal(True)
|
|
9
|
+
adult.ebv({"age": 20, "name": "Ana"}) # -> True (a Python bool)
|
|
10
|
+
|
|
11
|
+
The semantics follow SPARQL 1.1: numeric promotion (integer < decimal <
|
|
12
|
+
float < double, integer division -> decimal), errors PROPAGATED (unbound
|
|
13
|
+
variable, incomparable types) and absorbed by ``||``/``&&``/``IF``/
|
|
14
|
+
``COALESCE`` per SPARQL's three-valued truth table. No rdflib parser is used;
|
|
15
|
+
rdflib only serves as the data model.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
import re as _re
|
|
19
|
+
|
|
20
|
+
from rdflib import BNode, Literal, URIRef, Variable, XSD
|
|
21
|
+
from rdflib.term import Node
|
|
22
|
+
|
|
23
|
+
from ldpy.runtime import node as _node
|
|
24
|
+
|
|
25
|
+
__all__ = ["SparqlError", "Expression", "expr"]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class SparqlError(Exception):
|
|
29
|
+
"""A SPARQL evaluation error (unbound variable, incomparable types…).
|
|
30
|
+
|
|
31
|
+
It propagates through the operators and is absorbed only where SPARQL
|
|
32
|
+
SPARQL l'absorbe : ||, &&, IF, COALESCE."""
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class Expression:
|
|
36
|
+
"""A compiled SPARQL expression, to evaluate against a solution mapping.
|
|
37
|
+
|
|
38
|
+
The mapping accepts str or Variable keys, and Python or RDF values
|
|
39
|
+
termes RDF (coercition par ldpy.runtime.node)."""
|
|
40
|
+
|
|
41
|
+
__slots__ = ("_fn", "src")
|
|
42
|
+
|
|
43
|
+
def __init__(self, fn, src=""):
|
|
44
|
+
self._fn = fn
|
|
45
|
+
self.src = src
|
|
46
|
+
|
|
47
|
+
def _mapping(self, sm, kw):
|
|
48
|
+
out = {}
|
|
49
|
+
for k, v in list((sm or {}).items()) + list(kw.items()):
|
|
50
|
+
name = str(k) if isinstance(k, Variable) else k
|
|
51
|
+
out[name] = v if isinstance(v, Node) else _node(v)
|
|
52
|
+
return out
|
|
53
|
+
|
|
54
|
+
def __call__(self, sm=None, **kw):
|
|
55
|
+
"""Evaluate; returns an RDF term, or raises SparqlError."""
|
|
56
|
+
return self._fn(self._mapping(sm, kw))
|
|
57
|
+
|
|
58
|
+
def evaluate(self, sm=None, **kw):
|
|
59
|
+
"""Alias explicite de __call__."""
|
|
60
|
+
return self(sm, **kw)
|
|
61
|
+
|
|
62
|
+
def ebv(self, sm=None, **kw):
|
|
63
|
+
"""Effective boolean value (a Python bool); raises SparqlError otherwise."""
|
|
64
|
+
return ebv(self(sm, **kw))
|
|
65
|
+
|
|
66
|
+
def __repr__(self):
|
|
67
|
+
return "e{ %s }" % self.src if self.src else "Expression(...)"
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def expr(fn, src=""):
|
|
71
|
+
"""Build an Expression (called by the emitted code)."""
|
|
72
|
+
return Expression(fn, src)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
# ---------------------------------------------------------------- valeurs
|
|
76
|
+
|
|
77
|
+
_NUM_TYPES = {XSD.integer: 0, XSD.decimal: 1, XSD.float: 2, XSD.double: 3}
|
|
78
|
+
_INT_TYPES = {XSD.integer, XSD.int, XSD.long, XSD.short, XSD.byte,
|
|
79
|
+
XSD.nonNegativeInteger, XSD.positiveInteger,
|
|
80
|
+
XSD.negativeInteger, XSD.nonPositiveInteger,
|
|
81
|
+
XSD.unsignedInt, XSD.unsignedLong}
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def var(sm, name):
|
|
85
|
+
"""A variable's value; unbound -> SparqlError."""
|
|
86
|
+
try:
|
|
87
|
+
v = sm[name]
|
|
88
|
+
except KeyError:
|
|
89
|
+
raise SparqlError("unbound variable: ?%s" % name)
|
|
90
|
+
if v is None:
|
|
91
|
+
raise SparqlError("unbound variable: ?%s" % name)
|
|
92
|
+
return v
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def bound(sm, name):
|
|
96
|
+
"""BOUND(?v)."""
|
|
97
|
+
return Literal(name in sm and sm[name] is not None)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def py(value):
|
|
101
|
+
"""Python interpolation {expr}: coerced to a term, at EVERY evaluation."""
|
|
102
|
+
return _node(value)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def number(lexical):
|
|
106
|
+
"""A SPARQL numeric literal (integer / decimal / double)."""
|
|
107
|
+
if _re.search(r"[eE]", lexical):
|
|
108
|
+
return Literal(lexical, datatype=XSD.double)
|
|
109
|
+
if "." in lexical:
|
|
110
|
+
return Literal(lexical, datatype=XSD.decimal)
|
|
111
|
+
return Literal(lexical, datatype=XSD.integer)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _numeric(term):
|
|
115
|
+
if isinstance(term, Literal):
|
|
116
|
+
dt = term.datatype
|
|
117
|
+
if dt in _NUM_TYPES or dt in _INT_TYPES:
|
|
118
|
+
try:
|
|
119
|
+
return term.toPython()
|
|
120
|
+
except Exception:
|
|
121
|
+
pass
|
|
122
|
+
raise SparqlError("not a numeric value: %r" % (term,))
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _num_rank(term):
|
|
126
|
+
dt = term.datatype
|
|
127
|
+
if dt in _INT_TYPES:
|
|
128
|
+
return 0
|
|
129
|
+
return _NUM_TYPES.get(dt, 1)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _num_result(value, rank):
|
|
133
|
+
dt = (XSD.integer, XSD.decimal, XSD.float, XSD.double)[rank]
|
|
134
|
+
if dt == XSD.integer:
|
|
135
|
+
value = int(value)
|
|
136
|
+
return Literal(value, datatype=dt)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _promote(value, rank):
|
|
140
|
+
"""Bring a value to the Python type of the target rank — SPARQL 1.1
|
|
141
|
+
numeric promotion applies to the OPERANDS, not only to the result:
|
|
142
|
+
without it, xsd:double * xsd:decimal would be a float times a Decimal,
|
|
143
|
+
refuse."""
|
|
144
|
+
if rank == 0:
|
|
145
|
+
return int(value)
|
|
146
|
+
if rank == 1:
|
|
147
|
+
from decimal import Decimal
|
|
148
|
+
return value if isinstance(value, Decimal) else Decimal(str(value))
|
|
149
|
+
return float(value)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def _arith(a, b, op, div=False):
|
|
153
|
+
va, vb = _numeric(a), _numeric(b)
|
|
154
|
+
rank = max(_num_rank(a), _num_rank(b))
|
|
155
|
+
if div and rank == 0:
|
|
156
|
+
rank = 1 # SPARQL : integer / integer -> decimal
|
|
157
|
+
va, vb = _promote(va, rank), _promote(vb, rank)
|
|
158
|
+
try:
|
|
159
|
+
return _num_result(op(va, vb), rank)
|
|
160
|
+
except ZeroDivisionError:
|
|
161
|
+
raise SparqlError("division by zero")
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def add(a, b):
|
|
165
|
+
"""The + operator."""
|
|
166
|
+
return _arith(a, b, lambda x, y: x + y)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def sub(a, b):
|
|
170
|
+
"""The - operator."""
|
|
171
|
+
return _arith(a, b, lambda x, y: x - y)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def mul(a, b):
|
|
175
|
+
"""The * operator."""
|
|
176
|
+
return _arith(a, b, lambda x, y: x * y)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def div(a, b):
|
|
180
|
+
"""The / operator (integer/integer -> decimal). Operands are promoted to
|
|
181
|
+
the rank of the result, so never two ints here."""
|
|
182
|
+
return _arith(a, b, lambda x, y: x / y, div=True)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def neg(a):
|
|
186
|
+
"""Moins unaire."""
|
|
187
|
+
return _arith(a, number("0"), lambda x, y: -x)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
# ------------------------------------------------------------- comparaisons
|
|
191
|
+
|
|
192
|
+
def _cmp(a, b):
|
|
193
|
+
"""-1/0/1, or SparqlError if the terms are not comparable."""
|
|
194
|
+
if isinstance(a, Literal) and isinstance(b, Literal):
|
|
195
|
+
try:
|
|
196
|
+
va, vb = _numeric(a), _numeric(b)
|
|
197
|
+
return (va > vb) - (va < vb)
|
|
198
|
+
except SparqlError:
|
|
199
|
+
pass
|
|
200
|
+
if (a.datatype in (None, XSD.string)
|
|
201
|
+
and b.datatype in (None, XSD.string)
|
|
202
|
+
and a.language is None and b.language is None):
|
|
203
|
+
return (str(a) > str(b)) - (str(a) < str(b))
|
|
204
|
+
if a.datatype == XSD.boolean and b.datatype == XSD.boolean:
|
|
205
|
+
va, vb = a.toPython(), b.toPython()
|
|
206
|
+
return (va > vb) - (va < vb)
|
|
207
|
+
if a.datatype == b.datatype and a.datatype in (
|
|
208
|
+
XSD.dateTime, XSD.date):
|
|
209
|
+
va, vb = a.toPython(), b.toPython()
|
|
210
|
+
return (va > vb) - (va < vb)
|
|
211
|
+
if a.language and b.language and a.language == b.language:
|
|
212
|
+
return (str(a) > str(b)) - (str(a) < str(b))
|
|
213
|
+
raise SparqlError("termes non comparables : %r / %r" % (a, b))
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def eq(a, b):
|
|
217
|
+
"""The = operator (value equality, then term identity)."""
|
|
218
|
+
try:
|
|
219
|
+
return Literal(_cmp(a, b) == 0)
|
|
220
|
+
except SparqlError:
|
|
221
|
+
if a == b:
|
|
222
|
+
return Literal(True)
|
|
223
|
+
if isinstance(a, Literal) and isinstance(b, Literal):
|
|
224
|
+
raise
|
|
225
|
+
return Literal(False)
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def ne(a, b):
|
|
229
|
+
"""The != operator."""
|
|
230
|
+
return Literal(not ebv(eq(a, b)))
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def lt(a, b):
|
|
234
|
+
"""The < operator."""
|
|
235
|
+
return Literal(_cmp(a, b) < 0)
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def gt(a, b):
|
|
239
|
+
"""The > operator."""
|
|
240
|
+
return Literal(_cmp(a, b) > 0)
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def le(a, b):
|
|
244
|
+
"""The <= operator."""
|
|
245
|
+
return Literal(_cmp(a, b) <= 0)
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def ge(a, b):
|
|
249
|
+
"""The >= operator."""
|
|
250
|
+
return Literal(_cmp(a, b) >= 0)
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def in_(a, items):
|
|
254
|
+
"""The IN operator (semantics of "= chained by ||")."""
|
|
255
|
+
err = None
|
|
256
|
+
for it in items:
|
|
257
|
+
try:
|
|
258
|
+
if ebv(eq(a, it)):
|
|
259
|
+
return Literal(True)
|
|
260
|
+
except SparqlError as e:
|
|
261
|
+
err = e
|
|
262
|
+
if err is not None:
|
|
263
|
+
raise err
|
|
264
|
+
return Literal(False)
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def not_in(a, items):
|
|
268
|
+
"""The NOT IN operator."""
|
|
269
|
+
return Literal(not ebv(in_(a, items)))
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
# ------------------------------------------------------------------ logique
|
|
273
|
+
|
|
274
|
+
def ebv(term):
|
|
275
|
+
"""Effective boolean value (SPARQL 17.2.2)."""
|
|
276
|
+
if isinstance(term, Literal):
|
|
277
|
+
if term.datatype == XSD.boolean:
|
|
278
|
+
v = term.toPython()
|
|
279
|
+
if isinstance(v, bool):
|
|
280
|
+
return v
|
|
281
|
+
return str(term) in ("true", "1")
|
|
282
|
+
if term.datatype in _NUM_TYPES or term.datatype in _INT_TYPES:
|
|
283
|
+
try:
|
|
284
|
+
return bool(term.toPython())
|
|
285
|
+
except Exception:
|
|
286
|
+
return False
|
|
287
|
+
if term.datatype in (None, XSD.string):
|
|
288
|
+
return len(str(term)) > 0
|
|
289
|
+
raise SparqlError("no EBV for %r" % (term,))
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def and_(la, lb):
|
|
293
|
+
"""&& — SPARQL's three-valued table (F && err = F)."""
|
|
294
|
+
try:
|
|
295
|
+
a = ebv(la())
|
|
296
|
+
except SparqlError:
|
|
297
|
+
if ebv(lb()) is False:
|
|
298
|
+
return Literal(False)
|
|
299
|
+
raise
|
|
300
|
+
if not a:
|
|
301
|
+
return Literal(False)
|
|
302
|
+
return Literal(ebv(lb()))
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def or_(la, lb):
|
|
306
|
+
"""|| — SPARQL's three-valued table (T || err = T)."""
|
|
307
|
+
try:
|
|
308
|
+
a = ebv(la())
|
|
309
|
+
except SparqlError:
|
|
310
|
+
if ebv(lb()) is True:
|
|
311
|
+
return Literal(True)
|
|
312
|
+
raise
|
|
313
|
+
if a:
|
|
314
|
+
return Literal(True)
|
|
315
|
+
return Literal(ebv(lb()))
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def not_(a):
|
|
319
|
+
"""The ! operator."""
|
|
320
|
+
return Literal(not ebv(a))
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
def if_(cond, lt_, lf_):
|
|
324
|
+
"""IF(cond, alors, sinon) — branches paresseuses."""
|
|
325
|
+
return lt_() if ebv(cond) else lf_()
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
def coalesce(*lams):
|
|
329
|
+
"""COALESCE — premier argument sans erreur."""
|
|
330
|
+
for lam in lams:
|
|
331
|
+
try:
|
|
332
|
+
return lam()
|
|
333
|
+
except SparqlError:
|
|
334
|
+
continue
|
|
335
|
+
raise SparqlError("COALESCE: no argument could be evaluated")
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
# ------------------------------------------------------------- built-ins
|
|
339
|
+
|
|
340
|
+
def STR(t):
|
|
341
|
+
"""STR(term)."""
|
|
342
|
+
if isinstance(t, BNode):
|
|
343
|
+
raise SparqlError("STR of a blank node")
|
|
344
|
+
return Literal(str(t))
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
def LANG(t):
|
|
348
|
+
"""LANG(literal)."""
|
|
349
|
+
if not isinstance(t, Literal):
|
|
350
|
+
raise SparqlError("LANG wants a literal")
|
|
351
|
+
return Literal(t.language or "")
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
def DATATYPE(t):
|
|
355
|
+
"""DATATYPE(literal)."""
|
|
356
|
+
if not isinstance(t, Literal):
|
|
357
|
+
raise SparqlError("DATATYPE wants a literal")
|
|
358
|
+
if t.language:
|
|
359
|
+
return URIRef("http://www.w3.org/1999/02/22-rdf-syntax-ns#langString")
|
|
360
|
+
return t.datatype or XSD.string
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
def IRI(t, base=None):
|
|
364
|
+
"""IRI(str) — resolved against the lexical base of the call site."""
|
|
365
|
+
s = str(t)
|
|
366
|
+
if base and not _re.match(r"^[A-Za-z][A-Za-z0-9+.\-]*:", s):
|
|
367
|
+
from ldpy.runtime import firi
|
|
368
|
+
return firi(s, base=base)
|
|
369
|
+
return URIRef(s)
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
def BNODE(t=None):
|
|
373
|
+
"""BNODE() / BNODE(str)."""
|
|
374
|
+
return BNode() if t is None else BNode(str(t))
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
def CONCAT(*ts):
|
|
378
|
+
"""CONCAT(...)."""
|
|
379
|
+
return Literal("".join(str(t) for t in ts))
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
def UCASE(t):
|
|
383
|
+
"""UCASE(str)."""
|
|
384
|
+
return Literal(str(t).upper(), lang=getattr(t, "language", None))
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
def LCASE(t):
|
|
388
|
+
"""LCASE(str)."""
|
|
389
|
+
return Literal(str(t).lower(), lang=getattr(t, "language", None))
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
def STRLEN(t):
|
|
393
|
+
"""STRLEN(str)."""
|
|
394
|
+
return Literal(len(str(t)), datatype=XSD.integer)
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
def SUBSTR(t, start, length=None):
|
|
398
|
+
"""SUBSTR(str, start-1-based[, length])."""
|
|
399
|
+
s = str(t)
|
|
400
|
+
i = int(_numeric(start)) - 1
|
|
401
|
+
if length is None:
|
|
402
|
+
return Literal(s[i:], lang=getattr(t, "language", None))
|
|
403
|
+
return Literal(s[i:i + int(_numeric(length))],
|
|
404
|
+
lang=getattr(t, "language", None))
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
def STRSTARTS(a, b):
|
|
408
|
+
"""STRSTARTS(str, prefix)."""
|
|
409
|
+
return Literal(str(a).startswith(str(b)))
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
def STRENDS(a, b):
|
|
413
|
+
"""STRENDS(str, suffixe)."""
|
|
414
|
+
return Literal(str(a).endswith(str(b)))
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
def CONTAINS(a, b):
|
|
418
|
+
"""CONTAINS(str, aiguille)."""
|
|
419
|
+
return Literal(str(b) in str(a))
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
def STRBEFORE(a, b):
|
|
423
|
+
"""STRBEFORE(str, separator)."""
|
|
424
|
+
s, sep = str(a), str(b)
|
|
425
|
+
i = s.find(sep)
|
|
426
|
+
return Literal("" if i < 0 else s[:i])
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
def STRAFTER(a, b):
|
|
430
|
+
"""STRAFTER(str, separator)."""
|
|
431
|
+
s, sep = str(a), str(b)
|
|
432
|
+
i = s.find(sep)
|
|
433
|
+
return Literal("" if i < 0 else s[i + len(sep):])
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
def REPLACE(t, pat, repl, flags=None):
|
|
437
|
+
"""REPLACE(str, motif, remplacement[, drapeaux]) — regex XPath ~ Python."""
|
|
438
|
+
f = _re_flags(flags)
|
|
439
|
+
return Literal(_re.sub(str(pat), str(repl), str(t), flags=f))
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
def REGEX(t, pat, flags=None):
|
|
443
|
+
"""REGEX(str, motif[, drapeaux])."""
|
|
444
|
+
return Literal(bool(_re.search(str(pat), str(t), _re_flags(flags))))
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
def _re_flags(flags):
|
|
448
|
+
f = 0
|
|
449
|
+
for c in str(flags or ""):
|
|
450
|
+
f |= {"i": _re.I, "s": _re.S, "m": _re.M, "x": _re.X}.get(c, 0)
|
|
451
|
+
return f
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
def ABS(t):
|
|
455
|
+
"""ABS(num)."""
|
|
456
|
+
return _arith(t, number("0"), lambda x, y: abs(x))
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
def ROUND(t):
|
|
460
|
+
"""ROUND(num)."""
|
|
461
|
+
import math
|
|
462
|
+
return _arith(t, number("0"), lambda x, y: math.floor(x + 0.5))
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
def CEIL(t):
|
|
466
|
+
"""CEIL(num)."""
|
|
467
|
+
import math
|
|
468
|
+
return _arith(t, number("0"), lambda x, y: math.ceil(x))
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
def FLOOR(t):
|
|
472
|
+
"""FLOOR(num)."""
|
|
473
|
+
import math
|
|
474
|
+
return _arith(t, number("0"), lambda x, y: math.floor(x))
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
def SAMETERM(a, b):
|
|
478
|
+
"""SAMETERM(a, b)."""
|
|
479
|
+
return Literal(a == b)
|
|
480
|
+
|
|
481
|
+
|
|
482
|
+
def ISIRI(t):
|
|
483
|
+
"""isIRI / isURI."""
|
|
484
|
+
return Literal(isinstance(t, URIRef))
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
ISURI = ISIRI
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
def ISBLANK(t):
|
|
491
|
+
"""isBLANK."""
|
|
492
|
+
return Literal(isinstance(t, BNode))
|
|
493
|
+
|
|
494
|
+
|
|
495
|
+
def ISLITERAL(t):
|
|
496
|
+
"""isLITERAL."""
|
|
497
|
+
return Literal(isinstance(t, Literal))
|
|
498
|
+
|
|
499
|
+
|
|
500
|
+
def ISNUMERIC(t):
|
|
501
|
+
"""isNUMERIC."""
|
|
502
|
+
try:
|
|
503
|
+
_numeric(t)
|
|
504
|
+
return Literal(True)
|
|
505
|
+
except SparqlError:
|
|
506
|
+
return Literal(False)
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
def LANGMATCHES(tag, rng):
|
|
510
|
+
"""LANGMATCHES(tag, range) — '*' and range prefixes."""
|
|
511
|
+
t, r = str(tag).lower(), str(rng).lower()
|
|
512
|
+
if not t:
|
|
513
|
+
return Literal(False)
|
|
514
|
+
if r == "*":
|
|
515
|
+
return Literal(True)
|
|
516
|
+
return Literal(t == r or t.startswith(r + "-"))
|
|
517
|
+
|
|
518
|
+
|
|
519
|
+
def build_iri(parts, base=None):
|
|
520
|
+
"""e<...>: concatenate the parts — statics as they are, values through
|
|
521
|
+
STR() then IRI-safe encoding (the IRI(CONCAT(ENCODE_FOR_IRI…)) semantics
|
|
522
|
+
of the dev-sparql specification) — then resolve against the lexical base."""
|
|
523
|
+
from ldpy.runtime import firi
|
|
524
|
+
out = []
|
|
525
|
+
for p in parts:
|
|
526
|
+
if type(p) is str: # partie statique du gabarit
|
|
527
|
+
out.append(p)
|
|
528
|
+
else: # a value (Literal IS a str: exact type)
|
|
529
|
+
out.append(_iri_safe(str(STR(p))))
|
|
530
|
+
iri = "".join(out)
|
|
531
|
+
if base:
|
|
532
|
+
return firi(iri, base=base)
|
|
533
|
+
return URIRef(iri)
|
|
534
|
+
|
|
535
|
+
|
|
536
|
+
_IUNRESERVED = "-._~"
|
|
537
|
+
|
|
538
|
+
|
|
539
|
+
def _iri_safe(value):
|
|
540
|
+
"""ENCODE_FOR_IRI (aligned on the R2RML harness: iunreserved preserved)."""
|
|
541
|
+
out = []
|
|
542
|
+
for ch in value:
|
|
543
|
+
if ch.isalnum() and ch.isascii() or ch in _IUNRESERVED \
|
|
544
|
+
or not ch.isascii():
|
|
545
|
+
out.append(ch)
|
|
546
|
+
else:
|
|
547
|
+
out.append("".join("%%%02X" % b for b in ch.encode("utf-8")))
|
|
548
|
+
return "".join(out)
|
|
549
|
+
|
|
550
|
+
|
|
551
|
+
ENCODE_FOR_IRI = lambda t: Literal(_iri_safe(str(t))) # noqa: E731
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""Transpileur Linked-Data Python v2 (island parsing).
|
|
2
|
+
|
|
3
|
+
API publique : transpile(source, filename) -> TranspileResult.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from ldpy.transpiler.core import transpile, Transpiler, TranspileResult, \
|
|
7
|
+
RUNTIME_ALIAS, PRELUDE
|
|
8
|
+
from ldpy.transpiler.errors import LdpySyntaxError, LdpyWarning
|
|
9
|
+
from ldpy.transpiler.linemap import LanguageMap, Segment
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"transpile", "Transpiler", "TranspileResult", "RUNTIME_ALIAS", "PRELUDE",
|
|
13
|
+
"LdpySyntaxError", "LdpyWarning", "LanguageMap", "Segment",
|
|
14
|
+
]
|