linked-data-python 0.5.1__py3-none-any.whl → 0.6.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/__main__.py +4 -1
- ldpy/backend.py +90 -0
- ldpy/build.py +41 -6
- ldpy/console.py +13 -0
- ldpy/debug.py +24 -2
- ldpy/runtime.py +178 -56
- ldpy/sparql.py +4 -4
- ldpy/transpiler/core.py +32 -11
- {linked_data_python-0.5.1.dist-info → linked_data_python-0.6.0.dist-info}/METADATA +1 -1
- {linked_data_python-0.5.1.dist-info → linked_data_python-0.6.0.dist-info}/RECORD +14 -13
- {linked_data_python-0.5.1.dist-info → linked_data_python-0.6.0.dist-info}/WHEEL +0 -0
- {linked_data_python-0.5.1.dist-info → linked_data_python-0.6.0.dist-info}/entry_points.txt +0 -0
- {linked_data_python-0.5.1.dist-info → linked_data_python-0.6.0.dist-info}/licenses/LICENSE.md +0 -0
- {linked_data_python-0.5.1.dist-info → linked_data_python-0.6.0.dist-info}/top_level.txt +0 -0
ldpy/__main__.py
CHANGED
|
@@ -30,6 +30,9 @@ def main(argv=None):
|
|
|
30
30
|
help="open the interactive console after the script.")
|
|
31
31
|
parser.add_argument("-m", "--map", action="store_true",
|
|
32
32
|
help="also write the language map (<source>.map).")
|
|
33
|
+
parser.add_argument("--target", choices=["micropython"], default=None,
|
|
34
|
+
help="transpile for a device: s{ } is refused here, "
|
|
35
|
+
"at build time, where the message can be read.")
|
|
33
36
|
parser.add_argument("source", nargs="?",
|
|
34
37
|
help=".ldpy (or .py) file to run.")
|
|
35
38
|
args = parser.parse_args(argv)
|
|
@@ -45,7 +48,7 @@ def main(argv=None):
|
|
|
45
48
|
with open(args.source, "r", encoding="utf-8") as f:
|
|
46
49
|
source = f.read()
|
|
47
50
|
try:
|
|
48
|
-
result = transpile(source, args.source)
|
|
51
|
+
result = transpile(source, args.source, target=args.target)
|
|
49
52
|
except LdpySyntaxError as e:
|
|
50
53
|
print(str(e), file=sys.stderr)
|
|
51
54
|
return 1
|
ldpy/backend.py
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""The RDF backend of the façade: rdflib on CPython, urdflib on MicroPython.
|
|
2
|
+
|
|
3
|
+
The generated code never touches rdflib: it goes through `ldpy.runtime`,
|
|
4
|
+
and `ldpy.runtime` takes its terms and graphs from HERE. So this module is
|
|
5
|
+
the one place that knows which library is underneath — and the contract a
|
|
6
|
+
backend must meet is exactly its public names (record urdflib/306 measures
|
|
7
|
+
them with `urdflib/tools/api_gap.py`).
|
|
8
|
+
|
|
9
|
+
Two backends:
|
|
10
|
+
|
|
11
|
+
* **rdflib**, whenever it imports — the default on CPython, and the only one
|
|
12
|
+
with a SPARQL engine (`s{ }`);
|
|
13
|
+
* **urdflib**, the MicroPython C module of the linked-data-python
|
|
14
|
+
organisation, when rdflib is absent. It has the terms and the graphs;
|
|
15
|
+
`Namespace`, `RDF` and `XSD` are pure Python and provided here.
|
|
16
|
+
|
|
17
|
+
The two are not exclusive options of the same kind: the backend is chosen
|
|
18
|
+
by what is importable, and SPARQL queries are a capability of one backend.
|
|
19
|
+
`e{ }` expressions (ldpy.sparql) are pure Python over the terms, and run on
|
|
20
|
+
both.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
try:
|
|
24
|
+
import rdflib as _lib
|
|
25
|
+
from rdflib import RDF, XSD, BNode, Literal, Namespace, Variable
|
|
26
|
+
from rdflib.term import Node
|
|
27
|
+
URIRef = _lib.URIRef
|
|
28
|
+
Graph = _lib.Graph
|
|
29
|
+
NAME = "rdflib"
|
|
30
|
+
HAS_SPARQL = True
|
|
31
|
+
HAS_NAMESPACE_MANAGER = True
|
|
32
|
+
except ImportError: # MicroPython
|
|
33
|
+
import urdflib as _lib
|
|
34
|
+
from urdflib import BNode, Literal, Node, URIRef, Variable, Graph
|
|
35
|
+
NAME = "urdflib"
|
|
36
|
+
HAS_SPARQL = False
|
|
37
|
+
HAS_NAMESPACE_MANAGER = False
|
|
38
|
+
|
|
39
|
+
class Namespace(str):
|
|
40
|
+
"""A namespace IRI whose attributes and items are terms — rdflib's
|
|
41
|
+
Namespace, reduced to what the emitted code uses."""
|
|
42
|
+
|
|
43
|
+
def __getattr__(self, name):
|
|
44
|
+
if name.startswith("__"):
|
|
45
|
+
raise AttributeError(name)
|
|
46
|
+
return URIRef(self + name)
|
|
47
|
+
|
|
48
|
+
def __getitem__(self, name):
|
|
49
|
+
return URIRef(self + name)
|
|
50
|
+
|
|
51
|
+
def term(self, name):
|
|
52
|
+
return URIRef(self + name)
|
|
53
|
+
|
|
54
|
+
RDF = Namespace("http://www.w3.org/1999/02/22-rdf-syntax-ns#")
|
|
55
|
+
XSD = Namespace("http://www.w3.org/2001/XMLSchema#")
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def new_graph(base=None, identifier=None):
|
|
59
|
+
"""A graph of the backend. rdflib's takes a base, urdflib's does not
|
|
60
|
+
(a base is a lexical matter, resolved by the transpiler already)."""
|
|
61
|
+
if NAME == "rdflib":
|
|
62
|
+
return Graph(base=base, identifier=identifier)
|
|
63
|
+
return Graph(identifier=identifier) if identifier is not None else Graph()
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def bind_namespaces(graph, namespaces):
|
|
67
|
+
"""Give `graph` the serialisation prefixes of `namespaces` (a dict
|
|
68
|
+
prefix -> namespace IRI). On rdflib the runtime shares a cached
|
|
69
|
+
NamespaceManager instead (record ldpy/021); this is the plain path."""
|
|
70
|
+
for prefix, ns in namespaces.items():
|
|
71
|
+
graph.bind(prefix, str(ns))
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def prepare_sparql(text, namespaces, update=False):
|
|
75
|
+
"""A prepared SPARQL query or update — rdflib's engine, or an error
|
|
76
|
+
that says which backend is missing it."""
|
|
77
|
+
if not HAS_SPARQL:
|
|
78
|
+
raise RuntimeError(
|
|
79
|
+
"s{ } needs a SPARQL engine, and the %s backend has none: "
|
|
80
|
+
"transpile with target='micropython' to be told at build time, "
|
|
81
|
+
"and use m{ } and e{ } on the device" % NAME)
|
|
82
|
+
from rdflib.plugins.sparql import prepareQuery, prepareUpdate
|
|
83
|
+
return (prepareUpdate if update else prepareQuery)(
|
|
84
|
+
text, initNs=dict(namespaces))
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
__all__ = ["NAME", "HAS_SPARQL", "HAS_NAMESPACE_MANAGER",
|
|
88
|
+
"URIRef", "BNode", "Literal", "Variable", "Node", "Graph",
|
|
89
|
+
"Namespace", "RDF", "XSD",
|
|
90
|
+
"new_graph", "bind_namespaces", "prepare_sparql"]
|
ldpy/build.py
CHANGED
|
@@ -16,7 +16,35 @@ from ldpy.transpiler import transpile, LdpySyntaxError
|
|
|
16
16
|
DEFAULT_OUT = ".ldpy-build"
|
|
17
17
|
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
#: What a device needs of ldpy at run time, and nothing else: the façade,
|
|
20
|
+
#: its backend, and the expressions of e{ }. The transpiler stays on the
|
|
21
|
+
#: host. `ldpy/__init__.py` is NOT copied — it imports the transpiler and
|
|
22
|
+
#: the import hook — and is replaced by the docstring below.
|
|
23
|
+
DEVICE_RUNTIME = ("runtime.py", "backend.py", "sparql.py")
|
|
24
|
+
DEVICE_INIT = ('"""ldpy runtime for a device: what `import ldpy.runtime as '
|
|
25
|
+
'_ldpy_` needs.\n\nWritten by `python -m ldpy.build --target '
|
|
26
|
+
'micropython`; the transpiler is not here.\n"""\n')
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def bundle_device_runtime(out_dir):
|
|
30
|
+
"""Copy the device runtime into <out_dir>/ldpy/, so that the emitted
|
|
31
|
+
files and what they import travel together. Returns the paths."""
|
|
32
|
+
here = os.path.dirname(os.path.abspath(__file__))
|
|
33
|
+
dst = os.path.join(out_dir, "ldpy")
|
|
34
|
+
os.makedirs(dst, exist_ok=True)
|
|
35
|
+
written = []
|
|
36
|
+
with open(os.path.join(dst, "__init__.py"), "w", encoding="utf-8") as f:
|
|
37
|
+
f.write(DEVICE_INIT)
|
|
38
|
+
written.append(os.path.join(dst, "__init__.py"))
|
|
39
|
+
for name in DEVICE_RUNTIME:
|
|
40
|
+
with open(os.path.join(here, name), "rb") as fi, \
|
|
41
|
+
open(os.path.join(dst, name), "wb") as fo:
|
|
42
|
+
fo.write(fi.read())
|
|
43
|
+
written.append(os.path.join(dst, name))
|
|
44
|
+
return written
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def build_file(src_path, out_dir, rel=None, target=None):
|
|
20
48
|
"""Transpile one file; returns (py_path, map_path, result)."""
|
|
21
49
|
with open(src_path, "r", encoding="utf-8") as f:
|
|
22
50
|
source = f.read()
|
|
@@ -24,7 +52,7 @@ def build_file(src_path, out_dir, rel=None):
|
|
|
24
52
|
stem = rel[:-5] if rel.endswith(".ldpy") else rel
|
|
25
53
|
py_path = os.path.join(out_dir, stem + ".py")
|
|
26
54
|
map_path = os.path.join(out_dir, stem + ".ldpy.map")
|
|
27
|
-
result = transpile(source, src_path)
|
|
55
|
+
result = transpile(source, src_path, target=target)
|
|
28
56
|
result.map.generated_name = py_path
|
|
29
57
|
os.makedirs(os.path.dirname(py_path) or ".", exist_ok=True)
|
|
30
58
|
with open(py_path, "w", encoding="utf-8") as f:
|
|
@@ -37,7 +65,7 @@ def build_file(src_path, out_dir, rel=None):
|
|
|
37
65
|
return py_path, map_path, result
|
|
38
66
|
|
|
39
67
|
|
|
40
|
-
def build_tree(root, out_dir):
|
|
68
|
+
def build_tree(root, out_dir, target=None):
|
|
41
69
|
"""Recursively transpile every .ldpy under root. Plain .py files are
|
|
42
70
|
copied as they are (a mixed package must stay importable)."""
|
|
43
71
|
built, errors = [], []
|
|
@@ -49,7 +77,7 @@ def build_tree(root, out_dir):
|
|
|
49
77
|
rel = os.path.relpath(src, root)
|
|
50
78
|
if name.endswith(".ldpy"):
|
|
51
79
|
try:
|
|
52
|
-
built.append(build_file(src, out_dir, rel))
|
|
80
|
+
built.append(build_file(src, out_dir, rel, target))
|
|
53
81
|
except LdpySyntaxError as e:
|
|
54
82
|
errors.append(e)
|
|
55
83
|
elif name.endswith(".py"):
|
|
@@ -68,16 +96,23 @@ def main(argv=None):
|
|
|
68
96
|
parser.add_argument("source", help=".ldpy file or directory")
|
|
69
97
|
parser.add_argument("-o", "--out", default=DEFAULT_OUT,
|
|
70
98
|
help="output directory (default: %(default)s)")
|
|
99
|
+
parser.add_argument("--target", choices=["micropython"], default=None,
|
|
100
|
+
help="build for a device: s{ } is refused, and the "
|
|
101
|
+
"ldpy runtime is copied next to the output")
|
|
71
102
|
args = parser.parse_args(argv)
|
|
72
103
|
|
|
104
|
+
if args.target == "micropython":
|
|
105
|
+
os.makedirs(args.out, exist_ok=True)
|
|
106
|
+
bundle_device_runtime(args.out)
|
|
73
107
|
if os.path.isdir(args.source):
|
|
74
|
-
built, errors = build_tree(args.source, args.out)
|
|
108
|
+
built, errors = build_tree(args.source, args.out, args.target)
|
|
75
109
|
for e in errors:
|
|
76
110
|
print(str(e), file=sys.stderr)
|
|
77
111
|
print("%d file(s) transpiled into %s" % (len(built), args.out))
|
|
78
112
|
return 1 if errors else 0
|
|
79
113
|
try:
|
|
80
|
-
py_path, _, result = build_file(args.source, args.out
|
|
114
|
+
py_path, _, result = build_file(args.source, args.out,
|
|
115
|
+
target=args.target)
|
|
81
116
|
except LdpySyntaxError as e:
|
|
82
117
|
print(str(e), file=sys.stderr)
|
|
83
118
|
return 1
|
ldpy/console.py
CHANGED
|
@@ -70,6 +70,12 @@ class LdpyConsole(code.InteractiveConsole):
|
|
|
70
70
|
self._prefixes = dict(prefixes or {})
|
|
71
71
|
self._prefix_cols = {k: 0 for k in self._prefixes}
|
|
72
72
|
self._base = base
|
|
73
|
+
# The current graph and the current bindings are declarations too:
|
|
74
|
+
# like @prefix, a top-level @graph must survive to the next entry
|
|
75
|
+
# (otherwise `+{ ... }` after `@graph as g` sees no graph at all).
|
|
76
|
+
self._graph_var = None
|
|
77
|
+
self._bindings_var = None
|
|
78
|
+
self._counter = 0
|
|
73
79
|
|
|
74
80
|
def runsource(self, source, filename=None, symbol="single"):
|
|
75
81
|
"""Transpile then compile one entry; True = incomplete entry
|
|
@@ -79,6 +85,10 @@ class LdpyConsole(code.InteractiveConsole):
|
|
|
79
85
|
t.prefixes = dict(self._prefixes)
|
|
80
86
|
t._prefix_col = dict(self._prefix_cols)
|
|
81
87
|
t.base = self._base
|
|
88
|
+
t._graph_var = self._graph_var
|
|
89
|
+
t._bindings_var = self._bindings_var
|
|
90
|
+
# fresh names must not collide with those of the previous entries
|
|
91
|
+
t._ns_counter = self._counter
|
|
82
92
|
try:
|
|
83
93
|
result = t.run()
|
|
84
94
|
except LdpySyntaxError as e:
|
|
@@ -98,6 +108,9 @@ class LdpyConsole(code.InteractiveConsole):
|
|
|
98
108
|
self._prefixes = dict(t.prefixes)
|
|
99
109
|
self._prefix_cols = dict(t._prefix_col)
|
|
100
110
|
self._base = t.base
|
|
111
|
+
self._graph_var = t._graph_var
|
|
112
|
+
self._bindings_var = t._bindings_var
|
|
113
|
+
self._counter = t._ns_counter
|
|
101
114
|
for w in result.warnings:
|
|
102
115
|
self.write(str(w) + "\n")
|
|
103
116
|
self.runcode(code_obj)
|
ldpy/debug.py
CHANGED
|
@@ -92,11 +92,30 @@ def probe():
|
|
|
92
92
|
#: re-exported: debug tooling looks for them here (pure functions, defined
|
|
93
93
|
#: alongside the map in ldpy/transpiler/linemap.py)
|
|
94
94
|
__all__ = ["stepping_rules", "probe", "package_dir", "load_map",
|
|
95
|
+
"shadow_rel",
|
|
95
96
|
"translate_breakpoints", "translate_frames",
|
|
96
97
|
"snap_breakpoint_line", "snap_breakpoint_lines",
|
|
97
98
|
"run_direct", "main"]
|
|
98
99
|
|
|
99
100
|
|
|
101
|
+
def shadow_rel(source, root=None):
|
|
102
|
+
"""Path of the shadow inside the output directory.
|
|
103
|
+
|
|
104
|
+
Without a root, the basename: the shadow sits beside the source, and two
|
|
105
|
+
files never meet. With one — a workspace whose whole build is gathered in
|
|
106
|
+
a single directory — the tree is mirrored, which is what keeps `a/m.ldpy`
|
|
107
|
+
and `b/m.ldpy` from writing the same `m.py`. A source outside the root
|
|
108
|
+
falls back to its basename rather than climbing out of the output
|
|
109
|
+
directory with `../`."""
|
|
110
|
+
name = os.path.basename(source)
|
|
111
|
+
if not root:
|
|
112
|
+
return name
|
|
113
|
+
rel = os.path.relpath(os.path.abspath(source), os.path.abspath(root))
|
|
114
|
+
if rel.startswith(os.pardir + os.sep) or rel == os.pardir:
|
|
115
|
+
return name
|
|
116
|
+
return rel
|
|
117
|
+
|
|
118
|
+
|
|
100
119
|
def load_map(map_path):
|
|
101
120
|
"""Load a LanguageMap from a .ldpy.map file (JSON)."""
|
|
102
121
|
with open(map_path, "r", encoding="utf-8") as f:
|
|
@@ -167,6 +186,10 @@ def main(argv=None):
|
|
|
167
186
|
"straight in the .ldpy)")
|
|
168
187
|
parser.add_argument("-o", "--out", default=DEFAULT_OUT,
|
|
169
188
|
help="shadow directory (default: %(default)s)")
|
|
189
|
+
parser.add_argument("--root", metavar="DIR",
|
|
190
|
+
help="tree root the shadow mirrors: the .py is "
|
|
191
|
+
"written at OUT/<path of the source relative "
|
|
192
|
+
"to DIR> (default: OUT/<basename>)")
|
|
170
193
|
parser.add_argument("--listen", metavar="HOTE:PORT",
|
|
171
194
|
help="start debugpy listening (e.g. 127.0.0.1:5678)")
|
|
172
195
|
parser.add_argument("--wait-for-client", action="store_true",
|
|
@@ -201,8 +224,7 @@ def main(argv=None):
|
|
|
201
224
|
|
|
202
225
|
try:
|
|
203
226
|
py_path, map_path, result = build_file(
|
|
204
|
-
args.source, args.out,
|
|
205
|
-
rel=os.path.basename(args.source))
|
|
227
|
+
args.source, args.out, rel=shadow_rel(args.source, args.root))
|
|
206
228
|
except LdpySyntaxError as e:
|
|
207
229
|
print(str(e), file=sys.stderr)
|
|
208
230
|
return 1
|
ldpy/runtime.py
CHANGED
|
@@ -1,35 +1,33 @@
|
|
|
1
1
|
"""Runtime Linked-Data Python v2.
|
|
2
2
|
|
|
3
3
|
The code generated by the transpiler imports this module under the reserved
|
|
4
|
-
alias `_ldpy_` and uses ONLY this facade — never
|
|
5
|
-
|
|
6
|
-
MicroPython
|
|
4
|
+
alias `_ldpy_` and uses ONLY this facade — never an RDF library directly.
|
|
5
|
+
The library underneath is chosen by `ldpy.backend`: rdflib on the host,
|
|
6
|
+
urdflib on a MicroPython device. The generated code is the same on both.
|
|
7
7
|
|
|
8
8
|
Emission and runtime choices are explained in docs/explanation/.
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
|
-
import
|
|
12
|
-
|
|
13
|
-
import
|
|
14
|
-
from rdflib import RDF, BNode, Literal, Variable, Namespace
|
|
15
|
-
from rdflib.term import Node
|
|
11
|
+
from ldpy import backend as _backend
|
|
12
|
+
from ldpy.backend import RDF, BNode, Literal, Variable, Namespace, Node
|
|
13
|
+
from ldpy.backend import URIRef as _URIRef, Graph as _Graph
|
|
16
14
|
|
|
17
15
|
_URI_CACHE = {}
|
|
18
16
|
|
|
19
17
|
|
|
20
18
|
def URIRef(value, base=None):
|
|
21
|
-
"""
|
|
22
|
-
transpiler are CONSTANTS of the program: without a cache, every
|
|
23
|
-
loop over a g{...} rebuilt them (see OPTIMIZATION.md). The
|
|
24
|
-
bounded by the text of the programs; a 1M-entry guard covers
|
|
25
|
-
through the API."""
|
|
19
|
+
"""The backend's URIRef with a cache (key = the string). The IRIs emitted
|
|
20
|
+
by the transpiler are CONSTANTS of the program: without a cache, every
|
|
21
|
+
turn of a loop over a g{...} rebuilt them (see OPTIMIZATION.md). The
|
|
22
|
+
cache is bounded by the text of the programs; a 1M-entry guard covers
|
|
23
|
+
dynamic uses through the API."""
|
|
26
24
|
if base is not None:
|
|
27
|
-
return
|
|
25
|
+
return _URIRef(value, base)
|
|
28
26
|
u = _URI_CACHE.get(value)
|
|
29
27
|
if u is None:
|
|
30
|
-
if len(_URI_CACHE) >
|
|
31
|
-
return
|
|
32
|
-
u = _URI_CACHE.setdefault(value,
|
|
28
|
+
if len(_URI_CACHE) > 1000000:
|
|
29
|
+
return _URIRef(value)
|
|
30
|
+
u = _URI_CACHE.setdefault(value, _URIRef(value))
|
|
33
31
|
return u
|
|
34
32
|
|
|
35
33
|
try:
|
|
@@ -152,7 +150,7 @@ def _bnode_cached(label, _cache={}):
|
|
|
152
150
|
return b
|
|
153
151
|
|
|
154
152
|
|
|
155
|
-
_PASSTHROUGH = frozenset((
|
|
153
|
+
_PASSTHROUGH = frozenset((_URIRef, Literal, BNode, Variable, bn))
|
|
156
154
|
|
|
157
155
|
|
|
158
156
|
_COERCION_STACK = []
|
|
@@ -210,11 +208,11 @@ class Coercion:
|
|
|
210
208
|
|
|
211
209
|
def _apply_conv(conv, value):
|
|
212
210
|
"""Apply a Coercion conversion: datatype, IRI, or function."""
|
|
213
|
-
if conv is
|
|
214
|
-
return
|
|
211
|
+
if conv is _URIRef or conv is URIRef:
|
|
212
|
+
return _URIRef(str(value))
|
|
215
213
|
if conv is Literal:
|
|
216
214
|
return Literal(value)
|
|
217
|
-
if isinstance(conv,
|
|
215
|
+
if isinstance(conv, _URIRef): # un datatype (XSD.integer, ...)
|
|
218
216
|
return Literal(value, datatype=conv)
|
|
219
217
|
return node(conv(value))
|
|
220
218
|
|
|
@@ -245,7 +243,7 @@ def dtype(value):
|
|
|
245
243
|
A datatype is ALWAYS an IRI: unlike node(), a string is therefore read as
|
|
246
244
|
an IRI and not as a literal. Also accepts an object exposing its IRI
|
|
247
245
|
through str() (URIRef, a DefinedNamespace term)."""
|
|
248
|
-
if type(value) is
|
|
246
|
+
if type(value) is _URIRef:
|
|
249
247
|
return value
|
|
250
248
|
if isinstance(value, str):
|
|
251
249
|
return URIRef(value)
|
|
@@ -269,7 +267,7 @@ def pname(ns, *parts):
|
|
|
269
267
|
tp = type(p)
|
|
270
268
|
if tp is Variable or (cls and tp is cls):
|
|
271
269
|
return _deferred_pname(ns, parts)
|
|
272
|
-
return
|
|
270
|
+
return _URIRef(
|
|
273
271
|
str(ns) + "".join(p if isinstance(p, str) else str(p) for p in parts))
|
|
274
272
|
|
|
275
273
|
|
|
@@ -292,7 +290,7 @@ def _deferred_pname(ns, parts):
|
|
|
292
290
|
out.append(str(p(sm)))
|
|
293
291
|
else:
|
|
294
292
|
out.append(p if isinstance(p, str) else str(p))
|
|
295
|
-
return
|
|
293
|
+
return _URIRef("".join(out))
|
|
296
294
|
|
|
297
295
|
src = str(ns) + "".join(
|
|
298
296
|
"?%s" % p if type(p) is Variable
|
|
@@ -308,7 +306,19 @@ def firi(*parts, base=None):
|
|
|
308
306
|
return URIRef(iri)
|
|
309
307
|
|
|
310
308
|
|
|
311
|
-
|
|
309
|
+
class _Counter:
|
|
310
|
+
"""itertools.count(), which MicroPython does not ship."""
|
|
311
|
+
__slots__ = ("n",)
|
|
312
|
+
|
|
313
|
+
def __init__(self):
|
|
314
|
+
self.n = 0
|
|
315
|
+
|
|
316
|
+
def __next__(self):
|
|
317
|
+
self.n += 1
|
|
318
|
+
return self.n - 1
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
_graph_ids = _Counter()
|
|
312
322
|
_NM_CACHE = {}
|
|
313
323
|
|
|
314
324
|
|
|
@@ -320,14 +330,14 @@ def _nm_for(namespaces):
|
|
|
320
330
|
produced — the same sharing pattern as instantiateBGP. The cache is
|
|
321
331
|
invalidated by a snapshot of the content (identity alone is not enough:
|
|
322
332
|
block scope obliges)."""
|
|
323
|
-
if not namespaces:
|
|
333
|
+
if not namespaces or not _backend.HAS_NAMESPACE_MANAGER:
|
|
324
334
|
return None
|
|
325
335
|
key = id(namespaces)
|
|
326
336
|
snap = tuple(sorted((p, str(u)) for p, u in namespaces.items()))
|
|
327
337
|
hit = _NM_CACHE.get(key)
|
|
328
338
|
if hit is not None and hit[0] == snap:
|
|
329
339
|
return hit[1]
|
|
330
|
-
holder =
|
|
340
|
+
holder = _Graph()
|
|
331
341
|
nm = holder.namespace_manager
|
|
332
342
|
for prefix, ns in namespaces.items():
|
|
333
343
|
nm.bind(prefix, ns, replace=True)
|
|
@@ -335,7 +345,14 @@ def _nm_for(namespaces):
|
|
|
335
345
|
return nm
|
|
336
346
|
|
|
337
347
|
|
|
338
|
-
|
|
348
|
+
def _new_emitted(base):
|
|
349
|
+
"""A fresh emitted graph with the next identifier — lazy over rdflib,
|
|
350
|
+
eager over urdflib (the class is chosen once, below)."""
|
|
351
|
+
return _EMITTED(base=base,
|
|
352
|
+
identifier=_URIRef("urn:x-ldpy:g%d" % next(_graph_ids)))
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
class _EmittedGraph(_Graph):
|
|
339
356
|
"""The graph emitted by g{...}, materialised LAZILY.
|
|
340
357
|
|
|
341
358
|
The triples wait in a list; the store is only populated on the first real
|
|
@@ -392,9 +409,7 @@ class _EmittedGraph(rdflib.Graph):
|
|
|
392
409
|
pending = self._pending
|
|
393
410
|
if pending is None:
|
|
394
411
|
pending = list(super().__iter__())
|
|
395
|
-
g =
|
|
396
|
-
identifier=rdflib.URIRef("urn:x-ldpy:g%d"
|
|
397
|
-
% next(_graph_ids)))
|
|
412
|
+
g = _new_emitted(self.base)
|
|
398
413
|
nm = self._Graph__namespace_manager
|
|
399
414
|
if nm is not None:
|
|
400
415
|
g.namespace_manager = nm
|
|
@@ -419,9 +434,7 @@ class _EmittedGraph(rdflib.Graph):
|
|
|
419
434
|
|
|
420
435
|
def _merged(self, left, right):
|
|
421
436
|
"""Nouveau graphe paresseux contenant left puis right (listes)."""
|
|
422
|
-
g =
|
|
423
|
-
identifier=rdflib.URIRef("urn:x-ldpy:g%d"
|
|
424
|
-
% next(_graph_ids)))
|
|
437
|
+
g = _new_emitted(self.base)
|
|
425
438
|
nm = self._Graph__namespace_manager
|
|
426
439
|
if nm is not None:
|
|
427
440
|
g.namespace_manager = nm
|
|
@@ -435,7 +448,7 @@ class _EmittedGraph(rdflib.Graph):
|
|
|
435
448
|
O(total) instead of O(n²) store insertions; deduplication (union
|
|
436
449
|
semantics) is still guaranteed at flush time."""
|
|
437
450
|
pending = self._pending
|
|
438
|
-
if pending is not None and isinstance(other,
|
|
451
|
+
if pending is not None and isinstance(other, _Graph):
|
|
439
452
|
if type(other) is _EmittedGraph and other._pending is not None:
|
|
440
453
|
return self._merged(pending, other._pending)
|
|
441
454
|
return self._merged(pending, list(other))
|
|
@@ -446,7 +459,7 @@ class _EmittedGraph(rdflib.Graph):
|
|
|
446
459
|
boucle d'accumulation `g = g{ } ; g += g{…}` reste O(total) en
|
|
447
460
|
store insertions instead of paying one per turn."""
|
|
448
461
|
pending = self._pending
|
|
449
|
-
if pending is not None and isinstance(other,
|
|
462
|
+
if pending is not None and isinstance(other, _Graph):
|
|
450
463
|
if type(other) is _EmittedGraph and other._pending is not None:
|
|
451
464
|
self._pending = pending + other._pending
|
|
452
465
|
else:
|
|
@@ -457,11 +470,72 @@ class _EmittedGraph(rdflib.Graph):
|
|
|
457
470
|
def __radd__(self, other):
|
|
458
471
|
"""Graph() + g{...} (and therefore sum(...)) with no materialisation."""
|
|
459
472
|
pending = self._pending
|
|
460
|
-
if pending is not None and isinstance(other,
|
|
473
|
+
if pending is not None and isinstance(other, _Graph):
|
|
461
474
|
return self._merged(list(other), pending)
|
|
462
475
|
return NotImplemented
|
|
463
476
|
|
|
464
477
|
|
|
478
|
+
class _EagerEmittedGraph(_Graph):
|
|
479
|
+
"""The graph emitted by g{...} over urdflib: EAGER. The laziness
|
|
480
|
+
above rides on rdflib's private store attribute; a C graph has none,
|
|
481
|
+
and adds in place at C speed, so the triples go straight in. The
|
|
482
|
+
algebra the emitted code relies on stays: g1 + g2, g += x, g{ }(b)."""
|
|
483
|
+
|
|
484
|
+
def __init__(self, base=None, identifier=None):
|
|
485
|
+
_Graph.__init__(self, identifier=identifier) if identifier is not None \
|
|
486
|
+
else _Graph.__init__(self)
|
|
487
|
+
self.base = base
|
|
488
|
+
self._pending = None
|
|
489
|
+
self._Graph__namespace_manager = None
|
|
490
|
+
self._nm_namespaces = None
|
|
491
|
+
|
|
492
|
+
@property
|
|
493
|
+
def namespace_manager(self):
|
|
494
|
+
return self._Graph__namespace_manager
|
|
495
|
+
|
|
496
|
+
@namespace_manager.setter
|
|
497
|
+
def namespace_manager(self, nm):
|
|
498
|
+
self._Graph__namespace_manager = nm
|
|
499
|
+
|
|
500
|
+
def _fill(self, triples):
|
|
501
|
+
for tr in triples:
|
|
502
|
+
self.add(tr)
|
|
503
|
+
return self
|
|
504
|
+
|
|
505
|
+
def __call__(self, bindings=None):
|
|
506
|
+
g = _new_emitted(self.base)
|
|
507
|
+
term0 = _materializer(bindings if bindings is not None else {},
|
|
508
|
+
keep_vars=False)
|
|
509
|
+
bm = {}
|
|
510
|
+
|
|
511
|
+
def term(x):
|
|
512
|
+
v = term0(x)
|
|
513
|
+
if type(v) is BNode:
|
|
514
|
+
b = bm.get(v)
|
|
515
|
+
if b is None:
|
|
516
|
+
b = bm[v] = BNode()
|
|
517
|
+
return b
|
|
518
|
+
return v
|
|
519
|
+
|
|
520
|
+
return g._fill(tr for tr in
|
|
521
|
+
((term(s), term(p), term(o)) for s, p, o in self)
|
|
522
|
+
if tr[0] is not None and tr[1] is not None
|
|
523
|
+
and tr[2] is not None)
|
|
524
|
+
|
|
525
|
+
def __add__(self, other):
|
|
526
|
+
return _new_emitted(self.base)._fill(self)._fill(other)
|
|
527
|
+
|
|
528
|
+
def __iadd__(self, other):
|
|
529
|
+
return self._fill(other)
|
|
530
|
+
|
|
531
|
+
def __radd__(self, other):
|
|
532
|
+
return _new_emitted(self.base)._fill(other)._fill(self)
|
|
533
|
+
|
|
534
|
+
|
|
535
|
+
#: The emitted-graph class of this backend: a C graph has no store hook.
|
|
536
|
+
_EMITTED = _EmittedGraph if _backend.NAME == "rdflib" else _EagerEmittedGraph
|
|
537
|
+
|
|
538
|
+
|
|
465
539
|
class Bindings(dict):
|
|
466
540
|
"""The current bindings (record ldpy/017): a dict with str or Variable
|
|
467
541
|
keys (normalised to str), values coerced into RDF terms on assignment.
|
|
@@ -625,10 +699,12 @@ def _materializer(bindings=None, keep_vars=True):
|
|
|
625
699
|
def new_graph(namespaces, base, identifier=None):
|
|
626
700
|
"""A graph created by '@graph as g' (record ldpy/014): a plain
|
|
627
701
|
rdflib.Graph, with the serialisation bindings of the prefixes in scope."""
|
|
628
|
-
g =
|
|
702
|
+
g = _backend.new_graph(base=base, identifier=identifier)
|
|
629
703
|
nm = _nm_for(namespaces)
|
|
630
704
|
if nm is not None:
|
|
631
705
|
g.namespace_manager = nm
|
|
706
|
+
elif namespaces and not _backend.HAS_NAMESPACE_MANAGER:
|
|
707
|
+
_backend.bind_namespaces(g, namespaces)
|
|
632
708
|
return g
|
|
633
709
|
|
|
634
710
|
|
|
@@ -641,7 +717,6 @@ def add_to(graph, *triples, bindings=None):
|
|
|
641
717
|
s2, p2, o2 = term(s), term(p), term(o)
|
|
642
718
|
if s2 is not None and p2 is not None and o2 is not None:
|
|
643
719
|
graph.add((s2, p2, o2))
|
|
644
|
-
return graph
|
|
645
720
|
|
|
646
721
|
|
|
647
722
|
def remove_from(graph, *patterns, bindings=None):
|
|
@@ -665,13 +740,12 @@ def remove_from(graph, *patterns, bindings=None):
|
|
|
665
740
|
to_remove.add(inst)
|
|
666
741
|
for tr in to_remove:
|
|
667
742
|
graph.remove(tr)
|
|
668
|
-
return
|
|
743
|
+
return
|
|
669
744
|
term = _materializer(bindings, keep_vars=True)
|
|
670
745
|
for s, p, o in patterns:
|
|
671
746
|
tr = tuple(None if isinstance(x, (Variable, bn)) else x
|
|
672
747
|
for x in (term(s), term(p), term(o)))
|
|
673
748
|
graph.remove(tr)
|
|
674
|
-
return graph
|
|
675
749
|
|
|
676
750
|
|
|
677
751
|
def graph(namespaces, base, *triples, bindings=None):
|
|
@@ -684,21 +758,25 @@ def graph(namespaces, base, *triples, bindings=None):
|
|
|
684
758
|
With no bindings, a g{ } with variables or e{ } stays a template; with
|
|
685
759
|
the current bindings in scope, it is instantiated (record ldpy/017) — a
|
|
686
760
|
triple with a term still unbound is dropped."""
|
|
687
|
-
g =
|
|
688
|
-
identifier=rdflib.URIRef("urn:x-ldpy:g%d"
|
|
689
|
-
% next(_graph_ids)))
|
|
761
|
+
g = _new_emitted(base)
|
|
690
762
|
nm = _nm_for(namespaces)
|
|
691
763
|
if nm is not None:
|
|
692
764
|
g.namespace_manager = nm
|
|
765
|
+
elif namespaces and not _backend.HAS_NAMESPACE_MANAGER:
|
|
766
|
+
_backend.bind_namespaces(g, namespaces)
|
|
693
767
|
_term = _materializer(bindings, keep_vars=(bindings is None))
|
|
694
768
|
if bindings is None:
|
|
695
|
-
|
|
769
|
+
pending = [(_term(s), _term(p), _term(o)) for s, p, o in triples]
|
|
696
770
|
else:
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
771
|
+
pending = [tr for tr in
|
|
772
|
+
((_term(s), _term(p), _term(o))
|
|
773
|
+
for s, p, o in triples)
|
|
774
|
+
if tr[0] is not None and tr[1] is not None
|
|
775
|
+
and tr[2] is not None]
|
|
776
|
+
if _backend.NAME == "rdflib":
|
|
777
|
+
g._pending = pending
|
|
778
|
+
else:
|
|
779
|
+
g._fill(pending)
|
|
702
780
|
return g
|
|
703
781
|
|
|
704
782
|
|
|
@@ -730,6 +808,51 @@ class Row(tuple):
|
|
|
730
808
|
for i, f in enumerate(self._fields))
|
|
731
809
|
|
|
732
810
|
|
|
811
|
+
class _RowSeq:
|
|
812
|
+
"""The same Row for MicroPython, which cannot subclass tuple with
|
|
813
|
+
__new__: a small sequence that unpacks, compares to a tuple, and
|
|
814
|
+
answers to names and variables."""
|
|
815
|
+
|
|
816
|
+
__slots__ = ("_values", "_fields")
|
|
817
|
+
|
|
818
|
+
def __init__(self, values, fields):
|
|
819
|
+
self._values = tuple(values)
|
|
820
|
+
self._fields = fields
|
|
821
|
+
|
|
822
|
+
def __iter__(self):
|
|
823
|
+
return iter(self._values)
|
|
824
|
+
|
|
825
|
+
def __len__(self):
|
|
826
|
+
return len(self._values)
|
|
827
|
+
|
|
828
|
+
def __eq__(self, other):
|
|
829
|
+
return self._values == (other._values if isinstance(other, _RowSeq)
|
|
830
|
+
else other)
|
|
831
|
+
|
|
832
|
+
def __hash__(self):
|
|
833
|
+
return hash(self._values)
|
|
834
|
+
|
|
835
|
+
def __getattr__(self, name):
|
|
836
|
+
try:
|
|
837
|
+
return self._values[self._fields.index(name)]
|
|
838
|
+
except ValueError:
|
|
839
|
+
raise AttributeError(name)
|
|
840
|
+
|
|
841
|
+
def __getitem__(self, key):
|
|
842
|
+
if isinstance(key, (str, Variable)):
|
|
843
|
+
return self._values[self._fields.index(str(key))]
|
|
844
|
+
return self._values[key]
|
|
845
|
+
|
|
846
|
+
def __repr__(self):
|
|
847
|
+
return "Row(%s)" % ", ".join(
|
|
848
|
+
"%s=%r" % (f, v) for f, v in zip(self._fields, self._values))
|
|
849
|
+
|
|
850
|
+
|
|
851
|
+
#: The row class of this Python: tuple-based, unless tuple cannot be
|
|
852
|
+
#: subclassed with __new__ (MicroPython).
|
|
853
|
+
_ROW = Row if hasattr(tuple, "__new__") else _RowSeq
|
|
854
|
+
|
|
855
|
+
|
|
733
856
|
def _match_prepare(patterns, bindings):
|
|
734
857
|
"""Patterns ready for matching: bn -> an anonymous variable shared by
|
|
735
858
|
index, slot -> a value, variables bound by the bindings -> a term."""
|
|
@@ -824,7 +947,7 @@ class Match:
|
|
|
824
947
|
else:
|
|
825
948
|
vs = [Variable(x) for x in proj]
|
|
826
949
|
for sm in self.solutions():
|
|
827
|
-
yield
|
|
950
|
+
yield _ROW((sm.get(x) for x in vs), list(proj))
|
|
828
951
|
|
|
829
952
|
def __bool__(self):
|
|
830
953
|
for _ in self.solutions():
|
|
@@ -877,9 +1000,7 @@ def _prepare_sparql(text, namespaces, update):
|
|
|
877
1000
|
hit = _SPARQL_CACHE.get(key)
|
|
878
1001
|
if hit is not None:
|
|
879
1002
|
return hit
|
|
880
|
-
|
|
881
|
-
prep = (prepareUpdate if update else prepareQuery)(
|
|
882
|
-
text, initNs=dict(namespaces))
|
|
1003
|
+
prep = _backend.prepare_sparql(text, namespaces, update)
|
|
883
1004
|
if len(_SPARQL_CACHE) >= _SPARQL_CACHE_MAX:
|
|
884
1005
|
_SPARQL_CACHE.pop(next(iter(_SPARQL_CACHE)))
|
|
885
1006
|
_SPARQL_CACHE[key] = prep
|
|
@@ -964,8 +1085,9 @@ def instantiateBGP(input, solutionMappings, initialGraph=None):
|
|
|
964
1085
|
|
|
965
1086
|
Carried over from v1 (ldpy/ldpy.py), functionally unchanged."""
|
|
966
1087
|
if initialGraph is None:
|
|
967
|
-
initialGraph =
|
|
968
|
-
|
|
1088
|
+
initialGraph = _backend.new_graph(base=input.base)
|
|
1089
|
+
if _backend.HAS_NAMESPACE_MANAGER:
|
|
1090
|
+
initialGraph.namespace_manager = input.namespace_manager
|
|
969
1091
|
if solutionMappings is None:
|
|
970
1092
|
return initialGraph
|
|
971
1093
|
if isinstance(solutionMappings, dict):
|
ldpy/sparql.py
CHANGED
|
@@ -11,14 +11,14 @@ later, against a *solution mapping*:
|
|
|
11
11
|
The semantics follow SPARQL 1.1: numeric promotion (integer < decimal <
|
|
12
12
|
float < double, integer division -> decimal), errors PROPAGATED (unbound
|
|
13
13
|
variable, incomparable types) and absorbed by ``||``/``&&``/``IF``/
|
|
14
|
-
``COALESCE`` per SPARQL's three-valued truth table. No
|
|
15
|
-
|
|
14
|
+
``COALESCE`` per SPARQL's three-valued truth table. No parser of any RDF
|
|
15
|
+
library is used: the terms come from ``ldpy.backend`` — rdflib or urdflib —
|
|
16
|
+
so these expressions run on a device as well as on the host.
|
|
16
17
|
"""
|
|
17
18
|
|
|
18
19
|
import re as _re
|
|
19
20
|
|
|
20
|
-
from
|
|
21
|
-
from rdflib.term import Node
|
|
21
|
+
from ldpy.backend import BNode, Literal, URIRef, Variable, XSD, Node
|
|
22
22
|
|
|
23
23
|
from ldpy.runtime import node as _node
|
|
24
24
|
|
ldpy/transpiler/core.py
CHANGED
|
@@ -217,7 +217,18 @@ def _term_kind(text):
|
|
|
217
217
|
class Transpiler:
|
|
218
218
|
"""Une instance par fichier. Usage : Transpiler(src, filename).run()."""
|
|
219
219
|
|
|
220
|
-
|
|
220
|
+
#: The targets a programme can be transpiled for. `None` is the host;
|
|
221
|
+
#: "micropython" is a device whose backend has no SPARQL engine, so
|
|
222
|
+
#: s{ } is refused HERE, at build time on the host, where the message
|
|
223
|
+
#: can be read — not on the device, at the first query.
|
|
224
|
+
TARGETS = (None, "micropython")
|
|
225
|
+
|
|
226
|
+
def __init__(self, text, filename="<ldpy>", emit_prelude=True,
|
|
227
|
+
target=None):
|
|
228
|
+
if target not in self.TARGETS:
|
|
229
|
+
raise ValueError("unknown target %r (one of %s)"
|
|
230
|
+
% (target, ", ".join(repr(t) for t in self.TARGETS)))
|
|
231
|
+
self.target = target
|
|
221
232
|
self.text = text
|
|
222
233
|
self.emit_prelude = emit_prelude
|
|
223
234
|
self.n = len(text)
|
|
@@ -345,13 +356,16 @@ class Transpiler:
|
|
|
345
356
|
self.operand = False
|
|
346
357
|
self.stmt_start = False
|
|
347
358
|
|
|
348
|
-
def _error(self, msg, line=None, col=None):
|
|
359
|
+
def _error(self, msg, line=None, col=None, incomplete=True):
|
|
349
360
|
exc = LdpySyntaxError(msg, self.filename,
|
|
350
361
|
self.src_line if line is None else line,
|
|
351
362
|
self.src_col if col is None else col)
|
|
352
363
|
# la console s'en sert pour distinguer « entrée incomplète » (îlot
|
|
353
|
-
# non fermé en fin de tampon) d'une vraie erreur de syntaxe
|
|
354
|
-
|
|
364
|
+
# non fermé en fin de tampon) d'une vraie erreur de syntaxe.
|
|
365
|
+
# `incomplete=False` : erreur qu'une suite d'entrée ne répare pas
|
|
366
|
+
# (elle porte sur ce qui a déjà été lu), même si le curseur est en
|
|
367
|
+
# fin de tampon — sinon la console attendrait indéfiniment.
|
|
368
|
+
exc.at_eof = incomplete and self.i >= self.n
|
|
355
369
|
raise exc
|
|
356
370
|
|
|
357
371
|
def _warn(self, msg):
|
|
@@ -675,7 +689,7 @@ class Transpiler:
|
|
|
675
689
|
if re.match(r"@(prefix|base|graph|bindings)\b", t[j:j + 10]):
|
|
676
690
|
self._close_copy()
|
|
677
691
|
self._take(m.end() - self.i) # le mot-clé
|
|
678
|
-
while self._peek() in " \t":
|
|
692
|
+
while self._peek() in (" ", "\t"):
|
|
679
693
|
self._take(1)
|
|
680
694
|
if not self._try_prefix_or_base(scope_mode=name):
|
|
681
695
|
self._error("déclaration d'îlot attendue après "
|
|
@@ -1388,7 +1402,8 @@ class Transpiler:
|
|
|
1388
1402
|
if not encl:
|
|
1389
1403
|
what = "prefix %s:" % name if kind == "prefix" else kind
|
|
1390
1404
|
self._error("nonlocal @%s : aucune déclaration englobante "
|
|
1391
|
-
"(comme le nonlocal de Python)" % what
|
|
1405
|
+
"(comme le nonlocal de Python)" % what,
|
|
1406
|
+
incomplete=False)
|
|
1392
1407
|
keep = encl[-1]
|
|
1393
1408
|
target_col = self._scope_stack[keep][0]
|
|
1394
1409
|
touched = [i for i in matches if i > keep]
|
|
@@ -1562,7 +1577,7 @@ class Transpiler:
|
|
|
1562
1577
|
return self._take(m.end() - self.i)
|
|
1563
1578
|
|
|
1564
1579
|
def _graph_ws_inline(self):
|
|
1565
|
-
while self._peek() in " \t":
|
|
1580
|
+
while self._peek() in (" ", "\t"):
|
|
1566
1581
|
self._take(1)
|
|
1567
1582
|
|
|
1568
1583
|
def _addremove_island(self, sign):
|
|
@@ -1581,7 +1596,7 @@ class Transpiler:
|
|
|
1581
1596
|
self._error("'%s{ ... }' sans graphe courant : déclarez-le avec "
|
|
1582
1597
|
"'@graph <expression>' ou '@graph as g' (fiche 014), "
|
|
1583
1598
|
"ou donnez-le en suffixe — %s{ ... }(g)"
|
|
1584
|
-
% (sign, sign))
|
|
1599
|
+
% (sign, sign), incomplete=False)
|
|
1585
1600
|
bvar = sfx_bindings if sfx_bindings else self._bindings_var
|
|
1586
1601
|
fn = "add_to" if sign == "+" else "remove_from"
|
|
1587
1602
|
args = [gvar]
|
|
@@ -2086,6 +2101,11 @@ class Transpiler:
|
|
|
2086
2101
|
transpilation (l'oracle, fiche 015)."""
|
|
2087
2102
|
mark = self._begin_island()
|
|
2088
2103
|
isl_line, isl_col = self.src_line, self.src_col
|
|
2104
|
+
if self.target == "micropython":
|
|
2105
|
+
self._error("s{ } is not available on the micropython target: "
|
|
2106
|
+
"a SPARQL query needs rdflib's engine, which the "
|
|
2107
|
+
"device does not have — write the pattern with "
|
|
2108
|
+
"m{ } and the filter with e{ }", incomplete=False)
|
|
2089
2109
|
self._take(2) # s{
|
|
2090
2110
|
t = self.text
|
|
2091
2111
|
pieces = []
|
|
@@ -2482,7 +2502,7 @@ class Transpiler:
|
|
|
2482
2502
|
if word.upper() == "BOUND" and after == "(":
|
|
2483
2503
|
self._take(len(word) + 1)
|
|
2484
2504
|
self._e_ws()
|
|
2485
|
-
if self._peek() not in "
|
|
2505
|
+
if self._peek() not in ("?", "$"):
|
|
2486
2506
|
self._error("BOUND attend une variable ?v")
|
|
2487
2507
|
self._take(1)
|
|
2488
2508
|
vm = _NAME_RE.match(t, self.i)
|
|
@@ -2693,9 +2713,10 @@ class _GraphCtx:
|
|
|
2693
2713
|
return "%s.bn(%d)" % (RUNTIME_ALIAS, self.labels[label])
|
|
2694
2714
|
|
|
2695
2715
|
|
|
2696
|
-
def transpile(source, filename="<ldpy>"):
|
|
2716
|
+
def transpile(source, filename="<ldpy>", target=None):
|
|
2697
2717
|
"""Transpile un source Linked-Data Python en Python.
|
|
2698
2718
|
|
|
2719
|
+
`target` : None (l'hôte) ou "micropython" — voir Transpiler.TARGETS.
|
|
2699
2720
|
Retourne un TranspileResult(code, map, prefixes, base, warnings).
|
|
2700
2721
|
"""
|
|
2701
|
-
return Transpiler(source, filename).run()
|
|
2722
|
+
return Transpiler(source, filename, target=target).run()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: linked-data-python
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.0
|
|
4
4
|
Summary: Python extended with Semantic Web primitives: IRIs, RDF literals and Turtle-notation graphs as expressions, transpiled by island parsing.
|
|
5
5
|
Author-email: Maxime Lefrançois <maxime.lefrancois@emse.fr>
|
|
6
6
|
License: MIT
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
ldpy/__init__.py,sha256=IyuzHrp5OtuGO_NrrdmMUQW6bTYcyS5uob7CeoXg8mY,1943
|
|
2
|
-
ldpy/__main__.py,sha256=
|
|
3
|
-
ldpy/
|
|
4
|
-
ldpy/
|
|
5
|
-
ldpy/
|
|
2
|
+
ldpy/__main__.py,sha256=J1drZdQZ5jSAE5ri_NFB43jzkUuMgWHp-KL0Mjhbrhc,3328
|
|
3
|
+
ldpy/backend.py,sha256=SffPO4UQT_1p1JPhNiBtdlbNYNmpMySUPiLXvgR0Z2k,3585
|
|
4
|
+
ldpy/build.py,sha256=llHQ-7ndgg_yeMKukexrEsu0cAIFFU0JQSaL5_ywQss,5170
|
|
5
|
+
ldpy/console.py,sha256=vMSnFFoOIOcanw6_niNxqgrt-F4gzMOrD1GopkXru5s,4837
|
|
6
|
+
ldpy/debug.py,sha256=JUuzeKc8ya4SG30AxaUVeihTEbzySo1cS0sU0s78Mv0,10493
|
|
6
7
|
ldpy/formatter.py,sha256=VaDPECxhivsMe8jnX_fOL7BUyLEu5r4xREdr4Hav-0c,12695
|
|
7
8
|
ldpy/importer.py,sha256=QXAhSujPN6rFMJSQuj0UsWyI4CajirKt1SOrykJ_fUw,3670
|
|
8
9
|
ldpy/pygments_lexer.py,sha256=mcQ0u9nZjLnSWpqzcxd6bZjk69LfKSNTsf9A3rqfhhk,25320
|
|
9
|
-
ldpy/runtime.py,sha256=
|
|
10
|
-
ldpy/sparql.py,sha256=
|
|
10
|
+
ldpy/runtime.py,sha256=vkk9HQvUpi6pET0S4EqH1sA3797eA89NeTzHz9dLT_w,39632
|
|
11
|
+
ldpy/sparql.py,sha256=yPe-ZR1tpY--nM6q5LYXyXP7zlkE4gqhIRYAKwfpTBg,14851
|
|
11
12
|
ldpy/lsp/__init__.py,sha256=rK5GYOITi9PN09-WVlJ2aPKodW8dWTpgI_X6dFbP1zA,542
|
|
12
13
|
ldpy/lsp/__main__.py,sha256=MIp4ktqaoWZCNIMfv14bSC9pqK86JBS5XVjd1Z3c24o,72
|
|
13
14
|
ldpy/lsp/backend.py,sha256=8DnSGaBk1dsIMOBo7D1WQxxF3MZjJbYd78jp4ucWORY,5375
|
|
@@ -17,12 +18,12 @@ ldpy/lsp/rpc.py,sha256=-TF_9U8dum23Pi4O1FZLogwoZgrGrt14J5BFB1OYVZw,3554
|
|
|
17
18
|
ldpy/lsp/server.py,sha256=PAMMookGUn00vAW_-a5BGy3qkVe9PoUdfzAzpdlvoF8,16359
|
|
18
19
|
ldpy/lsp/translate.py,sha256=HU9fBP_xWyYLih6QwQfSoyfF-MH-oInFcPX3o8Vc2GI,6116
|
|
19
20
|
ldpy/transpiler/__init__.py,sha256=9mQx0e47ixz0iMqSbCBbtGiwJ7PLxTcsVSIp6KNTMG4,504
|
|
20
|
-
ldpy/transpiler/core.py,sha256=
|
|
21
|
+
ldpy/transpiler/core.py,sha256=LaZT8hE4jocIcTLyb2kc_mFtwu8QTSm0FhOMNetY_2k,114831
|
|
21
22
|
ldpy/transpiler/errors.py,sha256=zEEwsNvC2Tawq8jIYls7hrbcWSvQtd7eo89k_SKp0k0,1142
|
|
22
23
|
ldpy/transpiler/linemap.py,sha256=ecLlCUkD4-6QQtUjGfAL28ql7LPITTS4fjLcDIbAwpQ,11386
|
|
23
|
-
linked_data_python-0.
|
|
24
|
-
linked_data_python-0.
|
|
25
|
-
linked_data_python-0.
|
|
26
|
-
linked_data_python-0.
|
|
27
|
-
linked_data_python-0.
|
|
28
|
-
linked_data_python-0.
|
|
24
|
+
linked_data_python-0.6.0.dist-info/licenses/LICENSE.md,sha256=R6KjQ098eAFd2k5mKaSRJqMH1LhY4o7xkHrKT5vjRfs,1074
|
|
25
|
+
linked_data_python-0.6.0.dist-info/METADATA,sha256=q7sq2gWlVV18IGg6zFfFYc9EPyuTuP50JwP1quO0ZAU,7974
|
|
26
|
+
linked_data_python-0.6.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
27
|
+
linked_data_python-0.6.0.dist-info/entry_points.txt,sha256=n8Vs52oH81BrKOe1DNUvdFLSOlEufRMpK2waPFa6aYk,224
|
|
28
|
+
linked_data_python-0.6.0.dist-info/top_level.txt,sha256=dkG_F4jYkh6CS6gdpQeV_gKW3yaJBvl1ukUu0zo2KA8,5
|
|
29
|
+
linked_data_python-0.6.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
{linked_data_python-0.5.1.dist-info → linked_data_python-0.6.0.dist-info}/licenses/LICENSE.md
RENAMED
|
File without changes
|
|
File without changes
|