linked-data-python 0.0.4__py3-none-any.whl → 0.2.1__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 +47 -11
- ldpy/__main__.py +75 -250
- ldpy/build.py +91 -0
- ldpy/console.py +116 -0
- ldpy/debug.py +239 -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.1.dist-info/METADATA +158 -0
- linked_data_python-0.2.1.dist-info/RECORD +26 -0
- {linked_data_python-0.0.4.dist-info → linked_data_python-0.2.1.dist-info}/WHEEL +1 -1
- linked_data_python-0.2.1.dist-info/entry_points.txt +9 -0
- {linked_data_python-0.0.4.dist-info → linked_data_python-0.2.1.dist-info/licenses}/LICENSE.md +0 -0
- {linked_data_python-0.0.4.dist-info → linked_data_python-0.2.1.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
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""Errors and warnings of the Linked-Data Python transpiler."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class LdpySyntaxError(SyntaxError):
|
|
5
|
+
"""A syntax error in a .ldpy source.
|
|
6
|
+
|
|
7
|
+
line and col are 0-based internally; SyntaxError.lineno/offset are filled
|
|
8
|
+
in 1-based, as Python requires.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
def __init__(self, message, filename="<ldpy>", line=0, col=0):
|
|
12
|
+
super().__init__(message)
|
|
13
|
+
self.msg = message
|
|
14
|
+
self.filename = filename
|
|
15
|
+
self.lineno = line + 1
|
|
16
|
+
self.offset = col + 1
|
|
17
|
+
self.line = line
|
|
18
|
+
self.col = col
|
|
19
|
+
|
|
20
|
+
def __str__(self):
|
|
21
|
+
return "%s (%s, ligne %d:%d)" % (self.msg, self.filename, self.lineno, self.offset)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class LdpyWarning:
|
|
25
|
+
"""A non-blocking warning emitted during transpilation."""
|
|
26
|
+
|
|
27
|
+
def __init__(self, message, filename="<ldpy>", line=0, col=0):
|
|
28
|
+
self.message = message
|
|
29
|
+
self.filename = filename
|
|
30
|
+
self.line = line
|
|
31
|
+
self.col = col
|
|
32
|
+
|
|
33
|
+
def __str__(self):
|
|
34
|
+
return "LdpyWarning: %s (%s, ligne %d:%d)" % (
|
|
35
|
+
self.message, self.filename, self.line + 1, self.col + 1)
|
|
36
|
+
|
|
37
|
+
def __repr__(self):
|
|
38
|
+
return "LdpyWarning(%r)" % (self.message,)
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
"""Segment-level language map between a .ldpy source and the generated Python.
|
|
2
|
+
|
|
3
|
+
Voir docs/reference/language-map.md.
|
|
4
|
+
|
|
5
|
+
Positions 0-based, fins exclusives. Trois sortes de segments :
|
|
6
|
+
- "copy" : text copied verbatim -> exact position translation;
|
|
7
|
+
- "island:*" : rewritten RDF island -> translation at region granularity;
|
|
8
|
+
- "synthetic" : generated text with no origin (the runtime import prelude).
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import json
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Segment:
|
|
15
|
+
"""One source/generated correspondence segment (copy, island:*, synthetic)."""
|
|
16
|
+
|
|
17
|
+
__slots__ = ("kind", "src", "gen")
|
|
18
|
+
|
|
19
|
+
def __init__(self, kind, src, gen):
|
|
20
|
+
self.kind = kind
|
|
21
|
+
self.src = src # (line0, col0, line1, col1), or None for synthetic
|
|
22
|
+
self.gen = gen # (line0, col0, line1, col1)
|
|
23
|
+
|
|
24
|
+
def __repr__(self):
|
|
25
|
+
return "Segment(%r, src=%r, gen=%r)" % (self.kind, self.src, self.gen)
|
|
26
|
+
|
|
27
|
+
def to_dict(self):
|
|
28
|
+
"""Forme JSON du segment."""
|
|
29
|
+
d = {"kind": self.kind, "gen": list(self.gen)}
|
|
30
|
+
if self.src is not None:
|
|
31
|
+
d["src"] = list(self.src)
|
|
32
|
+
return d
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _pos_in(range4, line, col):
|
|
36
|
+
l0, c0, l1, c1 = range4
|
|
37
|
+
if line < l0 or line > l1:
|
|
38
|
+
return False
|
|
39
|
+
if line == l0 and col < c0:
|
|
40
|
+
return False
|
|
41
|
+
if line == l1 and col >= c1 and not (l0 == l1 and c0 == c1):
|
|
42
|
+
return False
|
|
43
|
+
return True
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _translate_copy(range_from, range_to, line, col):
|
|
47
|
+
fl0, fc0, _, _ = range_from
|
|
48
|
+
tl0, tc0, _, _ = range_to
|
|
49
|
+
if line == fl0:
|
|
50
|
+
return (tl0, col - fc0 + tc0)
|
|
51
|
+
return (line - fl0 + tl0, col)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class LanguageMap:
|
|
55
|
+
"""Bidirectional .ldpy <-> generated Python correspondence
|
|
56
|
+
(an ordered list of Segments; see docs/reference/language-map.md)."""
|
|
57
|
+
|
|
58
|
+
def __init__(self, source_name="<ldpy>", generated_name=None):
|
|
59
|
+
self.source_name = source_name
|
|
60
|
+
self.generated_name = generated_name
|
|
61
|
+
self.segments = [] # ordered by increasing gen AND src positions
|
|
62
|
+
|
|
63
|
+
def add(self, kind, src, gen):
|
|
64
|
+
"""Add a segment (segments empty on both sides are ignored)."""
|
|
65
|
+
# ignore segments that are empty on both sides
|
|
66
|
+
if src is not None and src[:2] == src[2:] and gen[:2] == gen[2:]:
|
|
67
|
+
return
|
|
68
|
+
self.segments.append(Segment(kind, src, gen))
|
|
69
|
+
|
|
70
|
+
# -- traduction ---------------------------------------------------------
|
|
71
|
+
|
|
72
|
+
def to_src(self, line, col):
|
|
73
|
+
"""Generated position -> source position (None if synthetic)."""
|
|
74
|
+
for seg in self.segments:
|
|
75
|
+
if _pos_in(seg.gen, line, col):
|
|
76
|
+
if seg.src is None:
|
|
77
|
+
return None
|
|
78
|
+
if seg.kind == "copy":
|
|
79
|
+
return _translate_copy(seg.gen, seg.src, line, col)
|
|
80
|
+
return (seg.src[0], seg.src[1])
|
|
81
|
+
return None
|
|
82
|
+
|
|
83
|
+
def to_gen(self, line, col):
|
|
84
|
+
"""Source position -> generated position."""
|
|
85
|
+
for seg in self.segments:
|
|
86
|
+
if seg.src is not None and _pos_in(seg.src, line, col):
|
|
87
|
+
if seg.kind == "copy":
|
|
88
|
+
return _translate_copy(seg.src, seg.gen, line, col)
|
|
89
|
+
return (seg.gen[0], seg.gen[1])
|
|
90
|
+
return None
|
|
91
|
+
|
|
92
|
+
def src_line_for_gen_line(self, line):
|
|
93
|
+
"""Source line matching a generated line (for tracebacks)."""
|
|
94
|
+
best = None
|
|
95
|
+
for seg in self.segments:
|
|
96
|
+
if seg.src is None:
|
|
97
|
+
continue
|
|
98
|
+
if seg.gen[0] <= line <= seg.gen[2]:
|
|
99
|
+
if seg.kind == "copy":
|
|
100
|
+
return _translate_copy(seg.gen, seg.src, line,
|
|
101
|
+
seg.gen[1] if line == seg.gen[0] else 0)[0]
|
|
102
|
+
best = seg.src[0]
|
|
103
|
+
return best
|
|
104
|
+
|
|
105
|
+
# -- serialisation ------------------------------------------------------
|
|
106
|
+
|
|
107
|
+
def to_dict(self):
|
|
108
|
+
"""Forme JSON (version 1, format maison)."""
|
|
109
|
+
return {
|
|
110
|
+
"version": 1,
|
|
111
|
+
"source": self.source_name,
|
|
112
|
+
"generated": self.generated_name,
|
|
113
|
+
"segments": [s.to_dict() for s in self.segments],
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
def to_json(self, **kw):
|
|
117
|
+
"""Serialise to JSON (kwargs passed to json.dumps)."""
|
|
118
|
+
return json.dumps(self.to_dict(), **kw)
|
|
119
|
+
|
|
120
|
+
@classmethod
|
|
121
|
+
def from_dict(cls, d):
|
|
122
|
+
"""Reconstruit une map depuis sa forme JSON."""
|
|
123
|
+
m = cls(d.get("source", "<ldpy>"), d.get("generated"))
|
|
124
|
+
for sd in d.get("segments", []):
|
|
125
|
+
src = tuple(sd["src"]) if "src" in sd else None
|
|
126
|
+
m.segments.append(Segment(sd["kind"], src, tuple(sd["gen"])))
|
|
127
|
+
return m
|
|
128
|
+
|
|
129
|
+
@classmethod
|
|
130
|
+
def from_json(cls, s):
|
|
131
|
+
"""Rebuild a map from a JSON string."""
|
|
132
|
+
return cls.from_dict(json.loads(s))
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def snap_breakpoint_line(lmap, line_1based):
|
|
136
|
+
"""The .ldpy line where a breakpoint set on `line_1based` will REALLY bind.
|
|
137
|
+
|
|
138
|
+
A multi-line island collapses into one statement whose code object carries
|
|
139
|
+
the START line (record ldpy/011): no interior line is executable. Yet
|
|
140
|
+
pydevd answers `verified: true` to a breakpoint set there, and never stops
|
|
141
|
+
on it — a silent lie (measured, record vscode/103). So we snap the line to
|
|
142
|
+
the island's start, and tooling can MOVE the dot to say so.
|
|
143
|
+
l'outillage peut DÉPLACER la pastille pour le dire.
|
|
144
|
+
|
|
145
|
+
Returns the same line when there is nothing to snap."""
|
|
146
|
+
line0 = line_1based - 1
|
|
147
|
+
for seg in lmap.segments:
|
|
148
|
+
if seg.src is None or seg.kind == "copy":
|
|
149
|
+
continue
|
|
150
|
+
if seg.src[0] < line0 <= seg.src[2]:
|
|
151
|
+
return seg.src[0] + 1
|
|
152
|
+
return line_1based
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def snap_breakpoint_lines(lmap, lines_1based):
|
|
156
|
+
"""`snap_breakpoint_line` over a list (order preserved)."""
|
|
157
|
+
return [snap_breakpoint_line(lmap, l) for l in lines_1based]
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
# ---------------------------------------------------------------------------
|
|
161
|
+
# "Remapped" compilation: the generated code is compiled with the line numbers
|
|
162
|
+
# OF THE .ldpy SOURCE (through the map), so that tracebacks, pdb and debugpy
|
|
163
|
+
# all speak directly in .ldpy coordinates (record ldpy/011).
|
|
164
|
+
# ---------------------------------------------------------------------------
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def remap_ast_lines(tree, lmap):
|
|
168
|
+
"""Rewrite lineno/end_lineno of every node of the GENERATED code's AST to
|
|
169
|
+
the .ldpy source lines. A generated line with no origin (the synthetic
|
|
170
|
+
prelude) snaps to line 1; the inside of a collapsed island snaps to the
|
|
171
|
+
island's start line. Columns are kept as they are (co_positions are
|
|
172
|
+
approximate on rewritten lines)."""
|
|
173
|
+
import ast
|
|
174
|
+
cache = {}
|
|
175
|
+
|
|
176
|
+
def src_line(gen_1based):
|
|
177
|
+
if gen_1based not in cache:
|
|
178
|
+
s = lmap.src_line_for_gen_line(gen_1based - 1)
|
|
179
|
+
cache[gen_1based] = (s + 1) if s is not None else None
|
|
180
|
+
return cache[gen_1based]
|
|
181
|
+
|
|
182
|
+
for node in ast.walk(tree):
|
|
183
|
+
lineno = getattr(node, "lineno", None)
|
|
184
|
+
if lineno is None:
|
|
185
|
+
continue
|
|
186
|
+
new_lineno = src_line(lineno) or 1
|
|
187
|
+
node.lineno = new_lineno
|
|
188
|
+
end = getattr(node, "end_lineno", None)
|
|
189
|
+
if end is not None:
|
|
190
|
+
new_end = src_line(end) or new_lineno
|
|
191
|
+
node.end_lineno = max(new_end, new_lineno)
|
|
192
|
+
return tree
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def compile_mapped(gen_code, lmap, filename, mode="exec",
|
|
196
|
+
dont_inherit=True, optimize=-1):
|
|
197
|
+
"""Compile the generated Python code with `filename` (the .ldpy) and the
|
|
198
|
+
source line numbers, through `remap_ast_lines`. On an unexpected AST
|
|
199
|
+
parse failure, fall back to an ordinary compilation (generated lines)."""
|
|
200
|
+
import ast
|
|
201
|
+
try:
|
|
202
|
+
tree = ast.parse(gen_code, filename, mode)
|
|
203
|
+
except SyntaxError:
|
|
204
|
+
return compile(gen_code, filename, mode,
|
|
205
|
+
dont_inherit=dont_inherit, optimize=optimize)
|
|
206
|
+
remap_ast_lines(tree, lmap)
|
|
207
|
+
return compile(tree, filename, mode,
|
|
208
|
+
dont_inherit=dont_inherit, optimize=optimize)
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
# ---------------------------------------------------------------------------
|
|
212
|
+
# Export Source Map v3 : le format standard
|
|
213
|
+
# JavaScript tooling, to interoperate with the tools that read it.
|
|
214
|
+
# https://tc39.es/ecma426/ — champs [genCol, srcIdx, srcLine, srcCol] en
|
|
215
|
+
# base64-VLQ, as deltas; one ";" entry per generated line.
|
|
216
|
+
# ---------------------------------------------------------------------------
|
|
217
|
+
|
|
218
|
+
_B64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def _vlq(value):
|
|
222
|
+
"""Encode a signed integer in base64-VLQ (zigzag + groups of 5 bits)."""
|
|
223
|
+
v = (value << 1) if value >= 0 else ((-value << 1) | 1)
|
|
224
|
+
out = []
|
|
225
|
+
while True:
|
|
226
|
+
digit = v & 0x1F
|
|
227
|
+
v >>= 5
|
|
228
|
+
if v:
|
|
229
|
+
digit |= 0x20
|
|
230
|
+
out.append(_B64[digit])
|
|
231
|
+
if not v:
|
|
232
|
+
return "".join(out)
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def _mapping_points(lmap):
|
|
236
|
+
"""Points (gen_line, gen_col, src_line, src_col), sorted, deduplicated.
|
|
237
|
+
|
|
238
|
+
One point per island start; for a copy segment, one point per generated
|
|
239
|
+
line covered (the standard debugger granularity)."""
|
|
240
|
+
points = {}
|
|
241
|
+
for seg in lmap.segments:
|
|
242
|
+
if seg.src is None:
|
|
243
|
+
continue
|
|
244
|
+
gl0, gc0, gl1, gc1 = seg.gen
|
|
245
|
+
sl0, sc0, _, _ = seg.src
|
|
246
|
+
if seg.kind == "copy":
|
|
247
|
+
# exclusive end: if the segment ends at column 0, its last
|
|
248
|
+
# "line" is empty and carries no point
|
|
249
|
+
last = gl1 if gc1 > 0 else gl1 - 1
|
|
250
|
+
for l in range(gl0, last + 1):
|
|
251
|
+
gcol = gc0 if l == gl0 else 0
|
|
252
|
+
scol = sc0 if l == gl0 else 0
|
|
253
|
+
points.setdefault((l, gcol), (sl0 + (l - gl0), scol))
|
|
254
|
+
else:
|
|
255
|
+
points.setdefault((gl0, gc0), (sl0, sc0))
|
|
256
|
+
return sorted((g[0], g[1], s[0], s[1]) for g, s in points.items())
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def _to_sourcemap_v3(self):
|
|
260
|
+
"""Return the Source Map v3 dict equivalent to this map."""
|
|
261
|
+
points = _mapping_points(self)
|
|
262
|
+
lines = []
|
|
263
|
+
prev_gcol = prev_sline = prev_scol = 0
|
|
264
|
+
cur_line = 0
|
|
265
|
+
buf = []
|
|
266
|
+
for gline, gcol, sline, scol in points:
|
|
267
|
+
while cur_line < gline:
|
|
268
|
+
lines.append(",".join(buf))
|
|
269
|
+
buf = []
|
|
270
|
+
prev_gcol = 0
|
|
271
|
+
cur_line += 1
|
|
272
|
+
seg = (_vlq(gcol - prev_gcol) + _vlq(0) +
|
|
273
|
+
_vlq(sline - prev_sline) + _vlq(scol - prev_scol))
|
|
274
|
+
buf.append(seg)
|
|
275
|
+
prev_gcol, prev_sline, prev_scol = gcol, sline, scol
|
|
276
|
+
lines.append(",".join(buf))
|
|
277
|
+
return {
|
|
278
|
+
"version": 3,
|
|
279
|
+
"file": self.generated_name or "",
|
|
280
|
+
"sources": [self.source_name],
|
|
281
|
+
"names": [],
|
|
282
|
+
"mappings": ";".join(lines),
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def _to_sourcemap_v3_json(self, **kw):
|
|
287
|
+
import json as _json
|
|
288
|
+
return _json.dumps(self.to_sourcemap_v3(), **kw)
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
LanguageMap.to_sourcemap_v3 = _to_sourcemap_v3
|
|
292
|
+
LanguageMap.to_sourcemap_v3_json = _to_sourcemap_v3_json
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: linked-data-python
|
|
3
|
+
Version: 0.2.1
|
|
4
|
+
Summary: Python extended with Semantic Web primitives: IRIs, RDF literals and Turtle-notation graphs as expressions, transpiled by island parsing.
|
|
5
|
+
Author-email: Maxime Lefrançois <maxime.lefrancois@emse.fr>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/linked-data-python/ldpy
|
|
8
|
+
Project-URL: Documentation, https://linked-data-python.readthedocs.io/
|
|
9
|
+
Project-URL: Source, https://github.com/linked-data-python/ldpy
|
|
10
|
+
Keywords: rdf,semantic-web,turtle,sparql,transpiler,linked-data,knowledge-graph
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Intended Audience :: Science/Research
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Topic :: Software Development :: Compilers
|
|
20
|
+
Classifier: Topic :: Software Development :: Pre-processors
|
|
21
|
+
Classifier: Topic :: Text Processing :: Markup
|
|
22
|
+
Requires-Python: >=3.10
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
License-File: LICENSE.md
|
|
25
|
+
Requires-Dist: rdflib>=6.0
|
|
26
|
+
Provides-Extra: lsp
|
|
27
|
+
Requires-Dist: python-lsp-server; extra == "lsp"
|
|
28
|
+
Requires-Dist: pyflakes; extra == "lsp"
|
|
29
|
+
Provides-Extra: debug
|
|
30
|
+
Requires-Dist: debugpy; extra == "debug"
|
|
31
|
+
Provides-Extra: dev
|
|
32
|
+
Requires-Dist: pytest; extra == "dev"
|
|
33
|
+
Provides-Extra: highlight
|
|
34
|
+
Requires-Dist: pygments>=2.14; extra == "highlight"
|
|
35
|
+
Provides-Extra: format
|
|
36
|
+
Requires-Dist: black>=24; extra == "format"
|
|
37
|
+
Provides-Extra: docs
|
|
38
|
+
Requires-Dist: mkdocs; extra == "docs"
|
|
39
|
+
Requires-Dist: mkdocs-material; extra == "docs"
|
|
40
|
+
Requires-Dist: pygments>=2.14; extra == "docs"
|
|
41
|
+
Dynamic: license-file
|
|
42
|
+
|
|
43
|
+
# Linked-Data Python
|
|
44
|
+
|
|
45
|
+
**Python, with the Semantic Web in its syntax.** IRIs, prefixed names, RDF
|
|
46
|
+
literals, SPARQL variables and whole graphs written in Turtle's notation are
|
|
47
|
+
expressions of the language — interpolated with arbitrary Python, transpiled to
|
|
48
|
+
plain Python, running on rdflib.
|
|
49
|
+
|
|
50
|
+

|
|
51
|
+
|
|
52
|
+
```text
|
|
53
|
+
@prefix sosa: <http://www.w3.org/ns/sosa/> .
|
|
54
|
+
@base <http://example.org/building/> .
|
|
55
|
+
|
|
56
|
+
def observation(sensor, value):
|
|
57
|
+
return g{ f<sensor/{sensor}> a sosa:Sensor ;
|
|
58
|
+
sosa:madeObservation [ sosa:hasSimpleResult {value} ] }
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
The language also reads and queries, writes into a current graph, and turns
|
|
62
|
+
rows into triples:
|
|
63
|
+
|
|
64
|
+
```text
|
|
65
|
+
@prefix ex: <http://example.org/> .
|
|
66
|
+
@graph as kg
|
|
67
|
+
for @bindings in csv.DictReader(f): # any iterable of mappings
|
|
68
|
+
+{ e<http://example.org/{?id}> ex:value ?v }
|
|
69
|
+
|
|
70
|
+
for s, v in m{ ?s ex:value ?v }: # a graph pattern, no engine
|
|
71
|
+
...
|
|
72
|
+
rows = s{ SELECT ?s WHERE { ?s ex:value ?v } } # all of SPARQL, checked early
|
|
73
|
+
adult = e{ ?age >= 18 && BOUND(?name) } # deferred, over bindings
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
`.ldpy` files are **transpiled to plain Python** by an *island parser*: the
|
|
77
|
+
Python is copied verbatim — every valid Python file is a valid ldpy file,
|
|
78
|
+
returned byte-identical — and only the RDF islands are parsed and rewritten.
|
|
79
|
+
The transpiler is ~1 500 lines with no parsing dependency and sustains
|
|
80
|
+
56 000–110 000 source lines/s depending on island density.
|
|
81
|
+
|
|
82
|
+
## Quick start
|
|
83
|
+
|
|
84
|
+
```text
|
|
85
|
+
git clone https://github.com/linked-data-python/ldpy.git
|
|
86
|
+
cd linked-data-python && pip install -e . # or: pip install -e .[lsp,debug]
|
|
87
|
+
|
|
88
|
+
ldpy program.ldpy # run a file
|
|
89
|
+
ldpy # interactive console
|
|
90
|
+
ldpy-lsp # language server (LSP, stdio)
|
|
91
|
+
ldpy-debug program.ldpy # debug via the shadow .py + debugpy
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
From Python: `import ldpy; ldpy.install()` then `import yourmodule` finds
|
|
95
|
+
`yourmodule.ldpy` on `sys.path`.
|
|
96
|
+
|
|
97
|
+
## Documentation
|
|
98
|
+
|
|
99
|
+
Read it at **<https://linked-data-python.readthedocs.io/>** — start with the
|
|
100
|
+
home page for an overview, then:
|
|
101
|
+
|
|
102
|
+
- **Tutorials** — [first steps](https://linked-data-python.readthedocs.io/en/latest/tutorials/getting-started/), then
|
|
103
|
+
[build a knowledge graph](https://linked-data-python.readthedocs.io/en/latest/tutorials/build-a-knowledge-graph/) from
|
|
104
|
+
tabular data.
|
|
105
|
+
- **How-to guides** — [run & import](https://linked-data-python.readthedocs.io/en/latest/how-to/run-and-import/),
|
|
106
|
+
[build graphs from tables](https://linked-data-python.readthedocs.io/en/latest/how-to/build-graphs-from-tables/),
|
|
107
|
+
[read and query](https://linked-data-python.readthedocs.io/en/latest/how-to/query-a-graph/),
|
|
108
|
+
[migrate from rdflib](https://linked-data-python.readthedocs.io/en/latest/how-to/migrate-from-rdflib/),
|
|
109
|
+
[VS Code](https://linked-data-python.readthedocs.io/en/latest/how-to/use-vscode/), [debugging](https://linked-data-python.readthedocs.io/en/latest/how-to/debug/),
|
|
110
|
+
[language server](https://linked-data-python.readthedocs.io/en/latest/how-to/language-server/),
|
|
111
|
+
[highlighting](https://linked-data-python.readthedocs.io/en/latest/how-to/highlight-ldpy/).
|
|
112
|
+
- **Reference** — [the language](https://linked-data-python.readthedocs.io/en/latest/reference/language/), one page
|
|
113
|
+
per island family; [SPARQL expressions](https://linked-data-python.readthedocs.io/en/latest/reference/sparql-expressions/);
|
|
114
|
+
[CLI](https://linked-data-python.readthedocs.io/en/latest/reference/cli/); [Python API](https://linked-data-python.readthedocs.io/en/latest/reference/api/);
|
|
115
|
+
[language map formats](https://linked-data-python.readthedocs.io/en/latest/reference/language-map/).
|
|
116
|
+
- **Explanation** — [why](https://linked-data-python.readthedocs.io/en/latest/explanation/why/),
|
|
117
|
+
[what real RDF code does](https://linked-data-python.readthedocs.io/en/latest/explanation/what-real-code-does/) (the
|
|
118
|
+
corpus study that drove the language's second wave),
|
|
119
|
+
[designing the syntax](https://linked-data-python.readthedocs.io/en/latest/explanation/designing-the-syntax/),
|
|
120
|
+
[island parsing](https://linked-data-python.readthedocs.io/en/latest/explanation/island-parsing/),
|
|
121
|
+
[emission & semantics](https://linked-data-python.readthedocs.io/en/latest/explanation/emission-and-semantics/),
|
|
122
|
+
[tooling](https://linked-data-python.readthedocs.io/en/latest/explanation/tooling/),
|
|
123
|
+
[how this is tested](https://linked-data-python.readthedocs.io/en/latest/explanation/how-it-is-tested/).
|
|
124
|
+
|
|
125
|
+
Every `ldpy` and `python` block in the documentation is executed by the test
|
|
126
|
+
suite, and its assertions are the test.
|
|
127
|
+
|
|
128
|
+
## Tooling
|
|
129
|
+
|
|
130
|
+
- **VS Code extension** (`vscode-ldpy`): highlighting (TextMate + LSP semantic
|
|
131
|
+
tokens), diagnostics as you type, completion/hover/definition, run and debug.
|
|
132
|
+
- **Language server**: dependency-free, LSP over stdio; delegates Python
|
|
133
|
+
intelligence to an unmodified `pylsp` through the language map.
|
|
134
|
+
- **Debugging**: `.ldpy` code compiles in `.ldpy` coordinates, so `pdb` and
|
|
135
|
+
`debugpy` work directly; `ldpy.build` also materialises real `.py` shadow
|
|
136
|
+
files with JSON and Source Map v3 maps.
|
|
137
|
+
- **Highlighting anywhere else**: the package registers a Pygments lexer built
|
|
138
|
+
on the language map — MkDocs, Sphinx and `pygmentize` colour `.ldpy` with no
|
|
139
|
+
further setup.
|
|
140
|
+
- **Benchmark harness** (`bench/`): seeded random program generator and
|
|
141
|
+
reproducible throughput campaigns.
|
|
142
|
+
|
|
143
|
+
## Project
|
|
144
|
+
|
|
145
|
+
- Tests: `python -m pytest tests/ -q` — byte-identity over the CPython standard
|
|
146
|
+
library, golden transpilation, RDF isomorphism against rdflib as an oracle,
|
|
147
|
+
LSP end to end, executable documentation.
|
|
148
|
+
- Licence: MIT. Author: Maxime Lefrançois (Mines Saint-Étienne).
|
|
149
|
+
- The 2023 ANTLR-based release (v1, PyPI 0.0.4) is preliminary work, superseded
|
|
150
|
+
by this island-parsing rewrite (the `main` branch of this repository; the 2023
|
|
151
|
+
code remains on the legacy gitlab.com/coswot/ldpy).
|
|
152
|
+
|
|
153
|
+
## Design records
|
|
154
|
+
|
|
155
|
+
Every non-trivial choice in this repository is written down, one file per
|
|
156
|
+
decision, in the [`pilotage`](https://github.com/linked-data-python/pilotage) repository. Comments and docs
|
|
157
|
+
cite them by identifier — `ldpy/024`, `vscode/103` — which resolves to
|
|
158
|
+
[`design/`](https://github.com/linked-data-python/pilotage/tree/main/design).
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
ldpy/__init__.py,sha256=IyuzHrp5OtuGO_NrrdmMUQW6bTYcyS5uob7CeoXg8mY,1943
|
|
2
|
+
ldpy/__main__.py,sha256=YKTEjmNQuh_0p-KiEus9HngFs_ZDmS56RXzHTspiWVI,3077
|
|
3
|
+
ldpy/build.py,sha256=495F9KyFMar-hTCuaQkF3FvzWQX4mE9H0U0V-JuRDmA,3394
|
|
4
|
+
ldpy/console.py,sha256=1FrroxoTTWG7fIBzaDQvOxPGj6NkyXJDJhhl5RXyE_0,4204
|
|
5
|
+
ldpy/debug.py,sha256=5o8oVYIbFk6rB9-ShTIfym_bhVG3imTw_l0taJZ9qH0,9462
|
|
6
|
+
ldpy/formatter.py,sha256=VaDPECxhivsMe8jnX_fOL7BUyLEu5r4xREdr4Hav-0c,12695
|
|
7
|
+
ldpy/importer.py,sha256=QXAhSujPN6rFMJSQuj0UsWyI4CajirKt1SOrykJ_fUw,3670
|
|
8
|
+
ldpy/pygments_lexer.py,sha256=mcQ0u9nZjLnSWpqzcxd6bZjk69LfKSNTsf9A3rqfhhk,25320
|
|
9
|
+
ldpy/runtime.py,sha256=k1k_DfblW4Dxacu4BkZpfuEPV1mMN83PkLtML9SAoms,33390
|
|
10
|
+
ldpy/sparql.py,sha256=CHSB4v5lEuVQKRPuwcM8xI3jN2FVHkmPVb675QkAD9Y,14770
|
|
11
|
+
ldpy/lsp/__init__.py,sha256=rK5GYOITi9PN09-WVlJ2aPKodW8dWTpgI_X6dFbP1zA,542
|
|
12
|
+
ldpy/lsp/__main__.py,sha256=MIp4ktqaoWZCNIMfv14bSC9pqK86JBS5XVjd1Z3c24o,72
|
|
13
|
+
ldpy/lsp/backend.py,sha256=c5Jx1fZ1ZhoYet9__RB1mUqFUtcCl63tO8vgnv8iMII,4644
|
|
14
|
+
ldpy/lsp/rpc.py,sha256=-TF_9U8dum23Pi4O1FZLogwoZgrGrt14J5BFB1OYVZw,3554
|
|
15
|
+
ldpy/lsp/server.py,sha256=Xpl1ApcNlauLjEiabITOTJ6i8aVGDdPB7SgPxK2ePJs,14329
|
|
16
|
+
ldpy/lsp/translate.py,sha256=_xDnKrJdx02puRkcuzhGwCLdEByuvhTfSIc9dAFR6a8,4931
|
|
17
|
+
ldpy/transpiler/__init__.py,sha256=9mQx0e47ixz0iMqSbCBbtGiwJ7PLxTcsVSIp6KNTMG4,504
|
|
18
|
+
ldpy/transpiler/core.py,sha256=s0jzTHSsazF7Xm-32q_Km6dKCxyjWEucmyVqd0eTP1s,106350
|
|
19
|
+
ldpy/transpiler/errors.py,sha256=zEEwsNvC2Tawq8jIYls7hrbcWSvQtd7eo89k_SKp0k0,1142
|
|
20
|
+
ldpy/transpiler/linemap.py,sha256=6b7y0nNmgnAMr_bqomgty7V0RgWPXFAp_8BZE4VYqzc,10580
|
|
21
|
+
linked_data_python-0.2.1.dist-info/licenses/LICENSE.md,sha256=R6KjQ098eAFd2k5mKaSRJqMH1LhY4o7xkHrKT5vjRfs,1074
|
|
22
|
+
linked_data_python-0.2.1.dist-info/METADATA,sha256=5fwrzPfP6GRlap3pL9JSnjS3AKil4J3ACiBVdisCdrg,7974
|
|
23
|
+
linked_data_python-0.2.1.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
24
|
+
linked_data_python-0.2.1.dist-info/entry_points.txt,sha256=n8Vs52oH81BrKOe1DNUvdFLSOlEufRMpK2waPFa6aYk,224
|
|
25
|
+
linked_data_python-0.2.1.dist-info/top_level.txt,sha256=dkG_F4jYkh6CS6gdpQeV_gKW3yaJBvl1ukUu0zo2KA8,5
|
|
26
|
+
linked_data_python-0.2.1.dist-info/RECORD,,
|
{linked_data_python-0.0.4.dist-info → linked_data_python-0.2.1.dist-info/licenses}/LICENSE.md
RENAMED
|
File without changes
|
|
File without changes
|
ldpy/grun/lib.py
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
# from https://github.com/charmoniumQ/antlr4-python-grun
|
|
2
|
-
|
|
3
|
-
import enum
|
|
4
|
-
import json
|
|
5
|
-
import importlib
|
|
6
|
-
import subprocess
|
|
7
|
-
import shutil
|
|
8
|
-
from pathlib import Path
|
|
9
|
-
import os
|
|
10
|
-
from typing import Tuple, Iterable
|
|
11
|
-
import antlr4 # type: ignore
|
|
12
|
-
from . import util
|
|
13
|
-
|
|
14
|
-
def readTokenTypes(path):
|
|
15
|
-
tokenTypes = dict()
|
|
16
|
-
tokenTypes[-1] = "EOF"
|
|
17
|
-
with open(path, 'r') as tokensFile:
|
|
18
|
-
for line in tokensFile:
|
|
19
|
-
i = line.rindex("=")
|
|
20
|
-
tokenTypes[int(line[i+1:])] = line[:i]
|
|
21
|
-
return tokenTypes
|
|
22
|
-
|
|
23
|
-
def format_tree(
|
|
24
|
-
root: antlr4.ParserRuleContext,
|
|
25
|
-
pretty: bool,
|
|
26
|
-
tokenTypes: dict()
|
|
27
|
-
) -> str:
|
|
28
|
-
stack = [(root, True)]
|
|
29
|
-
depth = 0
|
|
30
|
-
buf = []
|
|
31
|
-
while stack:
|
|
32
|
-
node, last = stack.pop()
|
|
33
|
-
if node == "end":
|
|
34
|
-
depth -= 1
|
|
35
|
-
# buf.append(depth * " " if pretty else "")
|
|
36
|
-
# buf.append(")")
|
|
37
|
-
# if not last:
|
|
38
|
-
# buf.append(" ")
|
|
39
|
-
elif isinstance(node, antlr4.tree.Tree.TerminalNodeImpl):
|
|
40
|
-
buf.append(depth * " " if pretty else "")
|
|
41
|
-
token = node.symbol
|
|
42
|
-
lexer = token.getTokenSource()
|
|
43
|
-
token_type_map = {
|
|
44
|
-
symbolic_id + len(lexer.literalNames) - 1: symbolic_name
|
|
45
|
-
for symbolic_id, symbolic_name in enumerate(lexer.symbolicNames)
|
|
46
|
-
}
|
|
47
|
-
type_ = token_type_map.get(token.type, "literal")
|
|
48
|
-
buf.append(f"({tokenTypes[token.type] if token.type in tokenTypes else token.type} {token.text})")
|
|
49
|
-
if not last:
|
|
50
|
-
buf.append(" ")
|
|
51
|
-
if pretty:
|
|
52
|
-
buf.append("\n")
|
|
53
|
-
else:
|
|
54
|
-
buf.append(depth * " " if pretty else "")
|
|
55
|
-
rule_name = node.parser.ruleNames[node.getRuleIndex()]
|
|
56
|
-
buf.append(f"({rule_name}")
|
|
57
|
-
depth += 1
|
|
58
|
-
children = [] if node.children is None else node.children
|
|
59
|
-
stack.append(("end", last))
|
|
60
|
-
stack.extend(util.first_sentinel(list(children)[::-1]))
|
|
61
|
-
if pretty:
|
|
62
|
-
buf.append("\n")
|
|
63
|
-
return "".join(buf)
|
ldpy/grun/util.py
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
# from https://github.com/charmoniumQ/antlr4-python-grun
|
|
2
|
-
from typing import TypeVar, Iterator, Tuple, Iterable
|
|
3
|
-
|
|
4
|
-
Value = TypeVar("Value")
|
|
5
|
-
|
|
6
|
-
def first_sentinel(iterable: Iterable[Value]) -> Iterator[Tuple[Value, bool]]:
|
|
7
|
-
iterator = iter(iterable)
|
|
8
|
-
yield (next(iterator), True)
|
|
9
|
-
for elem in iterator:
|
|
10
|
-
yield (elem, False)
|
|
11
|
-
|