linked-data-python 0.0.4__tar.gz → 0.2.1__tar.gz
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.
- {linked-data-python-0.0.4 → linked_data_python-0.2.1}/LICENSE.md +0 -0
- linked_data_python-0.2.1/PKG-INFO +158 -0
- linked_data_python-0.2.1/README.md +116 -0
- linked_data_python-0.2.1/ldpy/__init__.py +47 -0
- linked_data_python-0.2.1/ldpy/__main__.py +82 -0
- linked_data_python-0.2.1/ldpy/build.py +91 -0
- linked_data_python-0.2.1/ldpy/console.py +116 -0
- linked_data_python-0.2.1/ldpy/debug.py +239 -0
- linked_data_python-0.2.1/ldpy/formatter.py +329 -0
- linked_data_python-0.2.1/ldpy/importer.py +110 -0
- linked_data_python-0.2.1/ldpy/lsp/__init__.py +11 -0
- linked_data_python-0.2.1/ldpy/lsp/__main__.py +4 -0
- linked_data_python-0.2.1/ldpy/lsp/backend.py +118 -0
- linked_data_python-0.2.1/ldpy/lsp/rpc.py +104 -0
- linked_data_python-0.2.1/ldpy/lsp/server.py +353 -0
- linked_data_python-0.2.1/ldpy/lsp/translate.py +140 -0
- linked_data_python-0.2.1/ldpy/pygments_lexer.py +613 -0
- linked_data_python-0.2.1/ldpy/runtime.py +931 -0
- linked_data_python-0.2.1/ldpy/sparql.py +551 -0
- linked_data_python-0.2.1/ldpy/transpiler/__init__.py +14 -0
- linked_data_python-0.2.1/ldpy/transpiler/core.py +2558 -0
- linked_data_python-0.2.1/ldpy/transpiler/errors.py +38 -0
- linked_data_python-0.2.1/ldpy/transpiler/linemap.py +292 -0
- linked_data_python-0.2.1/linked_data_python.egg-info/PKG-INFO +158 -0
- linked_data_python-0.2.1/linked_data_python.egg-info/SOURCES.txt +63 -0
- {linked-data-python-0.0.4 → linked_data_python-0.2.1}/linked_data_python.egg-info/dependency_links.txt +0 -0
- linked_data_python-0.2.1/linked_data_python.egg-info/entry_points.txt +9 -0
- linked_data_python-0.2.1/linked_data_python.egg-info/requires.txt +22 -0
- {linked-data-python-0.0.4 → linked_data_python-0.2.1}/linked_data_python.egg-info/top_level.txt +0 -0
- linked_data_python-0.2.1/pyproject.toml +66 -0
- {linked-data-python-0.0.4 → linked_data_python-0.2.1}/setup.cfg +0 -0
- linked_data_python-0.2.1/tests/test_bench.py +62 -0
- linked_data_python-0.2.1/tests/test_bindings.py +270 -0
- linked_data_python-0.2.1/tests/test_build.py +48 -0
- linked_data_python-0.2.1/tests/test_call_suffix.py +144 -0
- linked_data_python-0.2.1/tests/test_charsets.py +108 -0
- linked_data_python-0.2.1/tests/test_coercion.py +141 -0
- linked_data_python-0.2.1/tests/test_console.py +96 -0
- linked_data_python-0.2.1/tests/test_current_graph.py +237 -0
- linked_data_python-0.2.1/tests/test_debug.py +171 -0
- linked_data_python-0.2.1/tests/test_debug_stepping.py +368 -0
- linked_data_python-0.2.1/tests/test_disambiguation.py +236 -0
- linked_data_python-0.2.1/tests/test_docs.py +46 -0
- linked_data_python-0.2.1/tests/test_docstrings.py +72 -0
- linked_data_python-0.2.1/tests/test_examples.py +34 -0
- linked_data_python-0.2.1/tests/test_formatter.py +274 -0
- linked_data_python-0.2.1/tests/test_generator.py +77 -0
- linked_data_python-0.2.1/tests/test_graphs.py +469 -0
- linked_data_python-0.2.1/tests/test_identity.py +109 -0
- linked_data_python-0.2.1/tests/test_import_hook.py +55 -0
- linked_data_python-0.2.1/tests/test_islands_terms.py +184 -0
- linked_data_python-0.2.1/tests/test_linemap.py +118 -0
- linked_data_python-0.2.1/tests/test_lsp_rpc.py +72 -0
- linked_data_python-0.2.1/tests/test_lsp_server.py +339 -0
- linked_data_python-0.2.1/tests/test_lsp_translate.py +147 -0
- linked_data_python-0.2.1/tests/test_match_island.py +182 -0
- linked_data_python-0.2.1/tests/test_micropython_subset.py +93 -0
- linked_data_python-0.2.1/tests/test_prefix_import.py +262 -0
- linked_data_python-0.2.1/tests/test_prefix_scoping.py +302 -0
- linked_data_python-0.2.1/tests/test_pygments_lexer.py +313 -0
- linked_data_python-0.2.1/tests/test_scope_modifiers.py +200 -0
- linked_data_python-0.2.1/tests/test_semantics.py +177 -0
- linked_data_python-0.2.1/tests/test_sourcemap_v3.py +120 -0
- linked_data_python-0.2.1/tests/test_sparql_expr.py +255 -0
- linked_data_python-0.2.1/tests/test_sparql_island.py +161 -0
- linked-data-python-0.0.4/CHANGELOG.md +0 -20
- linked-data-python-0.0.4/MANIFEST.in +0 -8
- linked-data-python-0.0.4/PKG-INFO +0 -134
- linked-data-python-0.0.4/README.md +0 -92
- linked-data-python-0.0.4/examples/async_stmt.ldpy +0 -14
- linked-data-python-0.0.4/examples/bnode_1.ldpy +0 -6
- linked-data-python-0.0.4/examples/bnode_2.ldpy +0 -7
- linked-data-python-0.0.4/examples/classdef.ldpy +0 -16
- linked-data-python-0.0.4/examples/decorated.ldpy +0 -30
- linked-data-python-0.0.4/examples/errors/error1.ldpy +0 -7
- linked-data-python-0.0.4/examples/errors/error2.ldpy +0 -5
- linked-data-python-0.0.4/examples/errors/error3.ldpy +0 -3
- linked-data-python-0.0.4/examples/for_stmt.ldpy +0 -31
- linked-data-python-0.0.4/examples/funcdef.ldpy +0 -28
- linked-data-python-0.0.4/examples/if_stmt.ldpy +0 -19
- linked-data-python-0.0.4/examples/instantiateBGP.ldpy +0 -27
- linked-data-python-0.0.4/examples/lists.ldpy +0 -6
- linked-data-python-0.0.4/examples/terms.ldpy +0 -40
- linked-data-python-0.0.4/examples/while_stmt.ldpy +0 -10
- linked-data-python-0.0.4/ldpy/__init__.py +0 -11
- linked-data-python-0.0.4/ldpy/__main__.py +0 -257
- linked-data-python-0.0.4/ldpy/grun/lib.py +0 -63
- linked-data-python-0.0.4/ldpy/grun/util.py +0 -11
- linked-data-python-0.0.4/ldpy/ldpy.py +0 -183
- linked-data-python-0.0.4/ldpy/rewriter/IndentedStringWriter.py +0 -54
- linked-data-python-0.0.4/ldpy/rewriter/LDPythonRewriter.py +0 -677
- linked-data-python-0.0.4/ldpy/rewriter/MultiChannelTokenStream.py +0 -127
- linked-data-python-0.0.4/ldpy/rewriter/Result.py +0 -49
- linked-data-python-0.0.4/ldpy/rewriter/__init__.py +0 -7
- linked-data-python-0.0.4/ldpy/rewriter/antlr/LDPythonLexer.py +0 -870
- linked-data-python-0.0.4/ldpy/rewriter/antlr/LDPythonParser.py +0 -9336
- linked-data-python-0.0.4/ldpy/rewriter/antlr/LDPythonVisitor.py +0 -573
- linked-data-python-0.0.4/ldpy/sparql/builtin.py +0 -326
- linked-data-python-0.0.4/ldpyIcon.png +0 -0
- linked-data-python-0.0.4/linked_data_python.egg-info/PKG-INFO +0 -134
- linked-data-python-0.0.4/linked_data_python.egg-info/SOURCES.txt +0 -42
- linked-data-python-0.0.4/linked_data_python.egg-info/entry_points.txt +0 -3
- linked-data-python-0.0.4/linked_data_python.egg-info/requires.txt +0 -3
- linked-data-python-0.0.4/setup.py +0 -58
- linked-data-python-0.0.4/test/.gitkeep +0 -0
|
File without changes
|
|
@@ -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,116 @@
|
|
|
1
|
+
# Linked-Data Python
|
|
2
|
+
|
|
3
|
+
**Python, with the Semantic Web in its syntax.** IRIs, prefixed names, RDF
|
|
4
|
+
literals, SPARQL variables and whole graphs written in Turtle's notation are
|
|
5
|
+
expressions of the language — interpolated with arbitrary Python, transpiled to
|
|
6
|
+
plain Python, running on rdflib.
|
|
7
|
+
|
|
8
|
+

|
|
9
|
+
|
|
10
|
+
```text
|
|
11
|
+
@prefix sosa: <http://www.w3.org/ns/sosa/> .
|
|
12
|
+
@base <http://example.org/building/> .
|
|
13
|
+
|
|
14
|
+
def observation(sensor, value):
|
|
15
|
+
return g{ f<sensor/{sensor}> a sosa:Sensor ;
|
|
16
|
+
sosa:madeObservation [ sosa:hasSimpleResult {value} ] }
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
The language also reads and queries, writes into a current graph, and turns
|
|
20
|
+
rows into triples:
|
|
21
|
+
|
|
22
|
+
```text
|
|
23
|
+
@prefix ex: <http://example.org/> .
|
|
24
|
+
@graph as kg
|
|
25
|
+
for @bindings in csv.DictReader(f): # any iterable of mappings
|
|
26
|
+
+{ e<http://example.org/{?id}> ex:value ?v }
|
|
27
|
+
|
|
28
|
+
for s, v in m{ ?s ex:value ?v }: # a graph pattern, no engine
|
|
29
|
+
...
|
|
30
|
+
rows = s{ SELECT ?s WHERE { ?s ex:value ?v } } # all of SPARQL, checked early
|
|
31
|
+
adult = e{ ?age >= 18 && BOUND(?name) } # deferred, over bindings
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
`.ldpy` files are **transpiled to plain Python** by an *island parser*: the
|
|
35
|
+
Python is copied verbatim — every valid Python file is a valid ldpy file,
|
|
36
|
+
returned byte-identical — and only the RDF islands are parsed and rewritten.
|
|
37
|
+
The transpiler is ~1 500 lines with no parsing dependency and sustains
|
|
38
|
+
56 000–110 000 source lines/s depending on island density.
|
|
39
|
+
|
|
40
|
+
## Quick start
|
|
41
|
+
|
|
42
|
+
```text
|
|
43
|
+
git clone https://github.com/linked-data-python/ldpy.git
|
|
44
|
+
cd linked-data-python && pip install -e . # or: pip install -e .[lsp,debug]
|
|
45
|
+
|
|
46
|
+
ldpy program.ldpy # run a file
|
|
47
|
+
ldpy # interactive console
|
|
48
|
+
ldpy-lsp # language server (LSP, stdio)
|
|
49
|
+
ldpy-debug program.ldpy # debug via the shadow .py + debugpy
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
From Python: `import ldpy; ldpy.install()` then `import yourmodule` finds
|
|
53
|
+
`yourmodule.ldpy` on `sys.path`.
|
|
54
|
+
|
|
55
|
+
## Documentation
|
|
56
|
+
|
|
57
|
+
Read it at **<https://linked-data-python.readthedocs.io/>** — start with the
|
|
58
|
+
home page for an overview, then:
|
|
59
|
+
|
|
60
|
+
- **Tutorials** — [first steps](https://linked-data-python.readthedocs.io/en/latest/tutorials/getting-started/), then
|
|
61
|
+
[build a knowledge graph](https://linked-data-python.readthedocs.io/en/latest/tutorials/build-a-knowledge-graph/) from
|
|
62
|
+
tabular data.
|
|
63
|
+
- **How-to guides** — [run & import](https://linked-data-python.readthedocs.io/en/latest/how-to/run-and-import/),
|
|
64
|
+
[build graphs from tables](https://linked-data-python.readthedocs.io/en/latest/how-to/build-graphs-from-tables/),
|
|
65
|
+
[read and query](https://linked-data-python.readthedocs.io/en/latest/how-to/query-a-graph/),
|
|
66
|
+
[migrate from rdflib](https://linked-data-python.readthedocs.io/en/latest/how-to/migrate-from-rdflib/),
|
|
67
|
+
[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/),
|
|
68
|
+
[language server](https://linked-data-python.readthedocs.io/en/latest/how-to/language-server/),
|
|
69
|
+
[highlighting](https://linked-data-python.readthedocs.io/en/latest/how-to/highlight-ldpy/).
|
|
70
|
+
- **Reference** — [the language](https://linked-data-python.readthedocs.io/en/latest/reference/language/), one page
|
|
71
|
+
per island family; [SPARQL expressions](https://linked-data-python.readthedocs.io/en/latest/reference/sparql-expressions/);
|
|
72
|
+
[CLI](https://linked-data-python.readthedocs.io/en/latest/reference/cli/); [Python API](https://linked-data-python.readthedocs.io/en/latest/reference/api/);
|
|
73
|
+
[language map formats](https://linked-data-python.readthedocs.io/en/latest/reference/language-map/).
|
|
74
|
+
- **Explanation** — [why](https://linked-data-python.readthedocs.io/en/latest/explanation/why/),
|
|
75
|
+
[what real RDF code does](https://linked-data-python.readthedocs.io/en/latest/explanation/what-real-code-does/) (the
|
|
76
|
+
corpus study that drove the language's second wave),
|
|
77
|
+
[designing the syntax](https://linked-data-python.readthedocs.io/en/latest/explanation/designing-the-syntax/),
|
|
78
|
+
[island parsing](https://linked-data-python.readthedocs.io/en/latest/explanation/island-parsing/),
|
|
79
|
+
[emission & semantics](https://linked-data-python.readthedocs.io/en/latest/explanation/emission-and-semantics/),
|
|
80
|
+
[tooling](https://linked-data-python.readthedocs.io/en/latest/explanation/tooling/),
|
|
81
|
+
[how this is tested](https://linked-data-python.readthedocs.io/en/latest/explanation/how-it-is-tested/).
|
|
82
|
+
|
|
83
|
+
Every `ldpy` and `python` block in the documentation is executed by the test
|
|
84
|
+
suite, and its assertions are the test.
|
|
85
|
+
|
|
86
|
+
## Tooling
|
|
87
|
+
|
|
88
|
+
- **VS Code extension** (`vscode-ldpy`): highlighting (TextMate + LSP semantic
|
|
89
|
+
tokens), diagnostics as you type, completion/hover/definition, run and debug.
|
|
90
|
+
- **Language server**: dependency-free, LSP over stdio; delegates Python
|
|
91
|
+
intelligence to an unmodified `pylsp` through the language map.
|
|
92
|
+
- **Debugging**: `.ldpy` code compiles in `.ldpy` coordinates, so `pdb` and
|
|
93
|
+
`debugpy` work directly; `ldpy.build` also materialises real `.py` shadow
|
|
94
|
+
files with JSON and Source Map v3 maps.
|
|
95
|
+
- **Highlighting anywhere else**: the package registers a Pygments lexer built
|
|
96
|
+
on the language map — MkDocs, Sphinx and `pygmentize` colour `.ldpy` with no
|
|
97
|
+
further setup.
|
|
98
|
+
- **Benchmark harness** (`bench/`): seeded random program generator and
|
|
99
|
+
reproducible throughput campaigns.
|
|
100
|
+
|
|
101
|
+
## Project
|
|
102
|
+
|
|
103
|
+
- Tests: `python -m pytest tests/ -q` — byte-identity over the CPython standard
|
|
104
|
+
library, golden transpilation, RDF isomorphism against rdflib as an oracle,
|
|
105
|
+
LSP end to end, executable documentation.
|
|
106
|
+
- Licence: MIT. Author: Maxime Lefrançois (Mines Saint-Étienne).
|
|
107
|
+
- The 2023 ANTLR-based release (v1, PyPI 0.0.4) is preliminary work, superseded
|
|
108
|
+
by this island-parsing rewrite (the `main` branch of this repository; the 2023
|
|
109
|
+
code remains on the legacy gitlab.com/coswot/ldpy).
|
|
110
|
+
|
|
111
|
+
## Design records
|
|
112
|
+
|
|
113
|
+
Every non-trivial choice in this repository is written down, one file per
|
|
114
|
+
decision, in the [`pilotage`](https://github.com/linked-data-python/pilotage) repository. Comments and docs
|
|
115
|
+
cite them by identifier — `ldpy/024`, `vscode/103` — which resolves to
|
|
116
|
+
[`design/`](https://github.com/linked-data-python/pilotage/tree/main/design).
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""Linked-Data Python (ldpy) v2.
|
|
2
|
+
|
|
3
|
+
An "island parsing" transpiler: Python is copied through untouched, the RDF
|
|
4
|
+
islands (@prefix, @base, IRIs, prefixed names, RDF literals, variables,
|
|
5
|
+
graphs g{...}) are rewritten into Python expressions built on ldpy.runtime.
|
|
6
|
+
|
|
7
|
+
The historical ANTLR chain (v1) is still reachable through the ldpy.rewriter
|
|
8
|
+
package but is no longer imported by default.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from ldpy.transpiler import transpile, LdpySyntaxError, LdpyWarning
|
|
12
|
+
from ldpy.importer import install, uninstall, install_excepthook
|
|
13
|
+
|
|
14
|
+
#: Read from the installed distribution rather than written here: two copies
|
|
15
|
+
#: of a version number drift, and this one had — a 0.2.0 install announced
|
|
16
|
+
#: itself as 0.1.0.dev0 in the editor status bar. The fallback covers a source
|
|
17
|
+
#: tree that was never installed.
|
|
18
|
+
try:
|
|
19
|
+
from importlib.metadata import PackageNotFoundError, version as _version
|
|
20
|
+
try:
|
|
21
|
+
__version__ = _version("linked-data-python")
|
|
22
|
+
except PackageNotFoundError: # pragma: no cover
|
|
23
|
+
__version__ = "0.0.0+unknown"
|
|
24
|
+
del _version, PackageNotFoundError
|
|
25
|
+
except ImportError: # pragma: no cover
|
|
26
|
+
__version__ = "0.0.0+unknown"
|
|
27
|
+
__date__ = "2026-08-26"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def transform_source(source, filename="<ldpy>"):
|
|
31
|
+
"""Simplified v1 compatibility: returns (python_code, prefixes, map)."""
|
|
32
|
+
result = transpile(source, filename)
|
|
33
|
+
return result.code, result.prefixes, result.map
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def Coercion(rules):
|
|
37
|
+
"""Politique de conversion Python -> RDF (fiche 020) — voir
|
|
38
|
+
ldpy.runtime.Coercion; exposed here as public API."""
|
|
39
|
+
from ldpy.runtime import Coercion as _C
|
|
40
|
+
return _C(rules)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def instantiateBGP(input, solutionMappings, initialGraph=None):
|
|
44
|
+
"""Instantiate a graph template with solution mappings
|
|
45
|
+
(re-export of ldpy.runtime.instantiateBGP, v1 compatibility)."""
|
|
46
|
+
from ldpy.runtime import instantiateBGP as _f
|
|
47
|
+
return _f(input, solutionMappings, initialGraph)
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""ldpy — command-line execution.
|
|
2
|
+
|
|
3
|
+
Usage :
|
|
4
|
+
python -m ldpy source.ldpy # transpile and run
|
|
5
|
+
python -m ldpy -s source.ldpy # also print the transformed code
|
|
6
|
+
python -m ldpy -t source.ldpy # transpile seulement (stdout)
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import argparse
|
|
10
|
+
import os
|
|
11
|
+
import sys
|
|
12
|
+
|
|
13
|
+
import ldpy
|
|
14
|
+
from ldpy.transpiler import transpile, LdpySyntaxError
|
|
15
|
+
from ldpy.transpiler.linemap import compile_mapped
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def main(argv=None):
|
|
19
|
+
parser = argparse.ArgumentParser(
|
|
20
|
+
prog="ldpy",
|
|
21
|
+
description="ldpy extends Python syntax with Semantic Web "
|
|
22
|
+
"primitives (IRIs, RDF literals, graphs).")
|
|
23
|
+
parser.add_argument("-v", "--version", action="store_true",
|
|
24
|
+
help="print the version and exit.")
|
|
25
|
+
parser.add_argument("-s", "--show-changes", action="store_true",
|
|
26
|
+
help="print the transformed code before running it.")
|
|
27
|
+
parser.add_argument("-t", "--transpile-only", action="store_true",
|
|
28
|
+
help="write the transformed code to stdout, do not run.")
|
|
29
|
+
parser.add_argument("-i", "--interactive", action="store_true",
|
|
30
|
+
help="open the interactive console after the script.")
|
|
31
|
+
parser.add_argument("-m", "--map", action="store_true",
|
|
32
|
+
help="also write the language map (<source>.map).")
|
|
33
|
+
parser.add_argument("source", nargs="?",
|
|
34
|
+
help=".ldpy (or .py) file to run.")
|
|
35
|
+
args = parser.parse_args(argv)
|
|
36
|
+
|
|
37
|
+
if args.version:
|
|
38
|
+
print("ldpy " + ldpy.__version__)
|
|
39
|
+
return 0
|
|
40
|
+
if not args.source:
|
|
41
|
+
from ldpy.console import interact
|
|
42
|
+
interact()
|
|
43
|
+
return 0
|
|
44
|
+
|
|
45
|
+
with open(args.source, "r", encoding="utf-8") as f:
|
|
46
|
+
source = f.read()
|
|
47
|
+
try:
|
|
48
|
+
result = transpile(source, args.source)
|
|
49
|
+
except LdpySyntaxError as e:
|
|
50
|
+
print(str(e), file=sys.stderr)
|
|
51
|
+
return 1
|
|
52
|
+
for w in result.warnings:
|
|
53
|
+
print(str(w), file=sys.stderr)
|
|
54
|
+
if args.map:
|
|
55
|
+
with open(args.source + ".map", "w", encoding="utf-8") as f:
|
|
56
|
+
f.write(result.map.to_json(indent=1))
|
|
57
|
+
if args.transpile_only:
|
|
58
|
+
sys.stdout.write(result.code)
|
|
59
|
+
return 0
|
|
60
|
+
if args.show_changes:
|
|
61
|
+
print("ldpy>>> ======== transformed code ========", file=sys.stderr)
|
|
62
|
+
for lineno, line in enumerate(result.code.split("\n"), 1):
|
|
63
|
+
print("ldpy>>> %3d: %s" % (lineno, line), file=sys.stderr)
|
|
64
|
+
print("ldpy>>> =================================", file=sys.stderr)
|
|
65
|
+
|
|
66
|
+
ldpy.install()
|
|
67
|
+
from ldpy.importer import MAPS
|
|
68
|
+
src_path = os.path.abspath(args.source)
|
|
69
|
+
MAPS[args.source] = MAPS[src_path] = result.map
|
|
70
|
+
# remapped compilation: tracebacks, pdb and debugpy all speak in
|
|
71
|
+
# .ldpy coordinates (record ldpy/011)
|
|
72
|
+
code = compile_mapped(result.code, result.map, src_path)
|
|
73
|
+
g = {"__name__": "__main__", "__file__": src_path}
|
|
74
|
+
exec(code, g)
|
|
75
|
+
if args.interactive:
|
|
76
|
+
from ldpy.console import interact
|
|
77
|
+
interact(locals=g, prefixes=result.prefixes, base=result.base)
|
|
78
|
+
return 0
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
if __name__ == "__main__":
|
|
82
|
+
sys.exit(main())
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""Materialisation: transpile .ldpy files into a shadow directory.
|
|
2
|
+
|
|
3
|
+
`python -m ldpy.build src/ -o .ldpy-build` mirrors the tree and writes:
|
|
4
|
+
- <module>.py (generated code)
|
|
5
|
+
- <module>.ldpy.map (language map JSON)
|
|
6
|
+
|
|
7
|
+
This is the base of debugging (debugpy runs on the shadow .py files) and of
|
|
8
|
+
language server (voir docs/explanation/tooling.md)."""
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
import sys
|
|
12
|
+
import argparse
|
|
13
|
+
|
|
14
|
+
from ldpy.transpiler import transpile, LdpySyntaxError
|
|
15
|
+
|
|
16
|
+
DEFAULT_OUT = ".ldpy-build"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def build_file(src_path, out_dir, rel=None):
|
|
20
|
+
"""Transpile one file; returns (py_path, map_path, result)."""
|
|
21
|
+
with open(src_path, "r", encoding="utf-8") as f:
|
|
22
|
+
source = f.read()
|
|
23
|
+
rel = rel or os.path.basename(src_path)
|
|
24
|
+
stem = rel[:-5] if rel.endswith(".ldpy") else rel
|
|
25
|
+
py_path = os.path.join(out_dir, stem + ".py")
|
|
26
|
+
map_path = os.path.join(out_dir, stem + ".ldpy.map")
|
|
27
|
+
result = transpile(source, src_path)
|
|
28
|
+
result.map.generated_name = py_path
|
|
29
|
+
os.makedirs(os.path.dirname(py_path) or ".", exist_ok=True)
|
|
30
|
+
with open(py_path, "w", encoding="utf-8") as f:
|
|
31
|
+
f.write(result.code)
|
|
32
|
+
with open(map_path, "w", encoding="utf-8") as f:
|
|
33
|
+
f.write(result.map.to_json(indent=1))
|
|
34
|
+
# Source Map v3: for standard tooling
|
|
35
|
+
with open(py_path + ".map", "w", encoding="utf-8") as f:
|
|
36
|
+
f.write(result.map.to_sourcemap_v3_json())
|
|
37
|
+
return py_path, map_path, result
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def build_tree(root, out_dir):
|
|
41
|
+
"""Recursively transpile every .ldpy under root. Plain .py files are
|
|
42
|
+
copied as they are (a mixed package must stay importable)."""
|
|
43
|
+
built, errors = [], []
|
|
44
|
+
for dirpath, dirnames, filenames in os.walk(root):
|
|
45
|
+
dirnames[:] = [d for d in dirnames
|
|
46
|
+
if d not in (DEFAULT_OUT, "__pycache__", ".git")]
|
|
47
|
+
for name in filenames:
|
|
48
|
+
src = os.path.join(dirpath, name)
|
|
49
|
+
rel = os.path.relpath(src, root)
|
|
50
|
+
if name.endswith(".ldpy"):
|
|
51
|
+
try:
|
|
52
|
+
built.append(build_file(src, out_dir, rel))
|
|
53
|
+
except LdpySyntaxError as e:
|
|
54
|
+
errors.append(e)
|
|
55
|
+
elif name.endswith(".py"):
|
|
56
|
+
dst = os.path.join(out_dir, rel)
|
|
57
|
+
os.makedirs(os.path.dirname(dst) or ".", exist_ok=True)
|
|
58
|
+
with open(src, "rb") as fi, open(dst, "wb") as fo:
|
|
59
|
+
fo.write(fi.read())
|
|
60
|
+
return built, errors
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def main(argv=None):
|
|
64
|
+
parser = argparse.ArgumentParser(
|
|
65
|
+
prog="ldpy.build",
|
|
66
|
+
description="Transpile .ldpy files into a shadow directory "
|
|
67
|
+
"(.py + .ldpy.map).")
|
|
68
|
+
parser.add_argument("source", help=".ldpy file or directory")
|
|
69
|
+
parser.add_argument("-o", "--out", default=DEFAULT_OUT,
|
|
70
|
+
help="output directory (default: %(default)s)")
|
|
71
|
+
args = parser.parse_args(argv)
|
|
72
|
+
|
|
73
|
+
if os.path.isdir(args.source):
|
|
74
|
+
built, errors = build_tree(args.source, args.out)
|
|
75
|
+
for e in errors:
|
|
76
|
+
print(str(e), file=sys.stderr)
|
|
77
|
+
print("%d file(s) transpiled into %s" % (len(built), args.out))
|
|
78
|
+
return 1 if errors else 0
|
|
79
|
+
try:
|
|
80
|
+
py_path, _, result = build_file(args.source, args.out)
|
|
81
|
+
except LdpySyntaxError as e:
|
|
82
|
+
print(str(e), file=sys.stderr)
|
|
83
|
+
return 1
|
|
84
|
+
for w in result.warnings:
|
|
85
|
+
print(str(w), file=sys.stderr)
|
|
86
|
+
print(py_path)
|
|
87
|
+
return 0
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
if __name__ == "__main__":
|
|
91
|
+
sys.exit(main())
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"""Console interactive Linked-Data Python .
|
|
2
|
+
|
|
3
|
+
The point of the `ideas` package in v1 was to enter the interpreter and type
|
|
4
|
+
ldpy directly. This console does it without `ideas`: every entry is
|
|
5
|
+
transpiled then compiled; the state of top-level @prefix/@base persists from
|
|
6
|
+
one entry to the next (declarations made inside a block die with the entry —
|
|
7
|
+
block scope obliges).
|
|
8
|
+
|
|
9
|
+
$ python -m ldpy # console
|
|
10
|
+
$ python -m ldpy -i script.ldpy # run, then open the console
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import atexit
|
|
14
|
+
import code
|
|
15
|
+
import codeop
|
|
16
|
+
import os
|
|
17
|
+
import sys
|
|
18
|
+
|
|
19
|
+
import ldpy
|
|
20
|
+
from ldpy.transpiler import LdpySyntaxError
|
|
21
|
+
from ldpy.transpiler.core import Transpiler, PRELUDE
|
|
22
|
+
|
|
23
|
+
HISTORY_FILE = os.path.join(os.path.expanduser("~"), ".ldpy_history")
|
|
24
|
+
HISTORY_LENGTH = 1000
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _setup_readline(locals):
|
|
28
|
+
"""Line editing (arrows, Ctrl-A/E…), persistent history and Tab
|
|
29
|
+
completion on console names. A no-op if the readline module is missing
|
|
30
|
+
(Windows without pyreadline)."""
|
|
31
|
+
try:
|
|
32
|
+
import readline
|
|
33
|
+
import rlcompleter
|
|
34
|
+
except ImportError:
|
|
35
|
+
return
|
|
36
|
+
readline.set_completer(rlcompleter.Completer(locals).complete)
|
|
37
|
+
if "libedit" in (getattr(readline, "__doc__", "") or ""):
|
|
38
|
+
readline.parse_and_bind("bind ^I rl_complete") # macOS libedit
|
|
39
|
+
else:
|
|
40
|
+
readline.parse_and_bind("tab: complete")
|
|
41
|
+
try:
|
|
42
|
+
readline.read_history_file(HISTORY_FILE)
|
|
43
|
+
except OSError:
|
|
44
|
+
pass
|
|
45
|
+
readline.set_history_length(HISTORY_LENGTH)
|
|
46
|
+
|
|
47
|
+
def _save():
|
|
48
|
+
try:
|
|
49
|
+
readline.write_history_file(HISTORY_FILE)
|
|
50
|
+
except OSError:
|
|
51
|
+
pass
|
|
52
|
+
atexit.register(_save)
|
|
53
|
+
|
|
54
|
+
BANNER = ("ldpy %s — console Linked-Data Python (Python %s)\n"
|
|
55
|
+
"RDF islands are accepted: @prefix, <iri>, ex:name, g{ ... }, ?v")
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class LdpyConsole(code.InteractiveConsole):
|
|
59
|
+
"""Interactive console: transpile every entry before compiling it;
|
|
60
|
+
top-level @prefix/@base persist between entries."""
|
|
61
|
+
|
|
62
|
+
def __init__(self, locals=None, filename="<console>",
|
|
63
|
+
prefixes=None, base=None):
|
|
64
|
+
if locals is None:
|
|
65
|
+
locals = {"__name__": "__console__", "__doc__": None}
|
|
66
|
+
super().__init__(locals=locals, filename=filename)
|
|
67
|
+
# the runtime prelude is installed once and for all
|
|
68
|
+
if "__namespaces__" not in self.locals:
|
|
69
|
+
exec(PRELUDE, self.locals)
|
|
70
|
+
self._prefixes = dict(prefixes or {})
|
|
71
|
+
self._prefix_cols = {k: 0 for k in self._prefixes}
|
|
72
|
+
self._base = base
|
|
73
|
+
|
|
74
|
+
def runsource(self, source, filename=None, symbol="single"):
|
|
75
|
+
"""Transpile then compile one entry; True = incomplete entry
|
|
76
|
+
(unclosed island or Python block), False = handled."""
|
|
77
|
+
filename = filename or self.filename
|
|
78
|
+
t = Transpiler(source, filename, emit_prelude=False)
|
|
79
|
+
t.prefixes = dict(self._prefixes)
|
|
80
|
+
t._prefix_col = dict(self._prefix_cols)
|
|
81
|
+
t.base = self._base
|
|
82
|
+
try:
|
|
83
|
+
result = t.run()
|
|
84
|
+
except LdpySyntaxError as e:
|
|
85
|
+
if getattr(e, "at_eof", False):
|
|
86
|
+
return True # unclosed island: wait for more
|
|
87
|
+
self.write(str(e) + "\n")
|
|
88
|
+
return False
|
|
89
|
+
try:
|
|
90
|
+
code_obj = codeop.compile_command(result.code, filename, symbol)
|
|
91
|
+
except (SyntaxError, ValueError, OverflowError):
|
|
92
|
+
self.showsyntaxerror(filename)
|
|
93
|
+
return False
|
|
94
|
+
if code_obj is None:
|
|
95
|
+
return True # Python incomplet (def, if, ...)
|
|
96
|
+
# the entry is complete: top-level declarations persist
|
|
97
|
+
t._unwind_scopes(0)
|
|
98
|
+
self._prefixes = dict(t.prefixes)
|
|
99
|
+
self._prefix_cols = dict(t._prefix_col)
|
|
100
|
+
self._base = t.base
|
|
101
|
+
for w in result.warnings:
|
|
102
|
+
self.write(str(w) + "\n")
|
|
103
|
+
self.runcode(code_obj)
|
|
104
|
+
return False
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def interact(locals=None, prefixes=None, base=None):
|
|
108
|
+
"""Open the ldpy console (banner, Ctrl-D to leave)."""
|
|
109
|
+
console = LdpyConsole(locals=locals, prefixes=prefixes, base=base)
|
|
110
|
+
_setup_readline(console.locals)
|
|
111
|
+
banner = BANNER % (ldpy.__version__, sys.version.split()[0])
|
|
112
|
+
try:
|
|
113
|
+
console.interact(banner=banner, exitmsg="")
|
|
114
|
+
except SystemExit:
|
|
115
|
+
pass
|
|
116
|
+
return console
|