tidysyntax 0.1.0__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.
- tidysyntax-0.1.0/CHANGELOG.md +23 -0
- tidysyntax-0.1.0/LICENSE +21 -0
- tidysyntax-0.1.0/MANIFEST.in +3 -0
- tidysyntax-0.1.0/PKG-INFO +128 -0
- tidysyntax-0.1.0/README.md +109 -0
- tidysyntax-0.1.0/docs/design.md +456 -0
- tidysyntax-0.1.0/pyproject.toml +41 -0
- tidysyntax-0.1.0/setup.cfg +4 -0
- tidysyntax-0.1.0/src/tidysyntax/__init__.py +39 -0
- tidysyntax-0.1.0/src/tidysyntax/__version__.py +1 -0
- tidysyntax-0.1.0/src/tidysyntax/dialect.py +94 -0
- tidysyntax-0.1.0/src/tidysyntax/ownership.py +207 -0
- tidysyntax-0.1.0/src/tidysyntax/pipeline.py +116 -0
- tidysyntax-0.1.0/src/tidysyntax/placeholders.py +47 -0
- tidysyntax-0.1.0/src/tidysyntax/py.typed +0 -0
- tidysyntax-0.1.0/src/tidysyntax/repairs.py +308 -0
- tidysyntax-0.1.0/src/tidysyntax/scanner.py +220 -0
- tidysyntax-0.1.0/src/tidysyntax.egg-info/PKG-INFO +128 -0
- tidysyntax-0.1.0/src/tidysyntax.egg-info/SOURCES.txt +25 -0
- tidysyntax-0.1.0/src/tidysyntax.egg-info/dependency_links.txt +1 -0
- tidysyntax-0.1.0/src/tidysyntax.egg-info/requires.txt +3 -0
- tidysyntax-0.1.0/src/tidysyntax.egg-info/top_level.txt +1 -0
- tidysyntax-0.1.0/tests/dialects.py +65 -0
- tidysyntax-0.1.0/tests/test_properties.py +92 -0
- tidysyntax-0.1.0/tests/test_reinterpretations.py +92 -0
- tidysyntax-0.1.0/tests/test_repairs.py +159 -0
- tidysyntax-0.1.0/tests/test_scanner.py +60 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0 — 2026-11-01
|
|
4
|
+
|
|
5
|
+
First version: phases 1 to 5 of [the design](docs/design.md); tidy3, tidy3a,
|
|
6
|
+
plot3 and stats3 use it from their next releases.
|
|
7
|
+
|
|
8
|
+
- `Dialect` describes one package's R syntax as data: which calls take `!`,
|
|
9
|
+
`name = value` before a positional argument, `left ~ right` rules (tidy3's
|
|
10
|
+
tuples), `y ~ x` formulas (plot3's and stats3's strings), `^` as power and
|
|
11
|
+
one-sided formulas, and a package's own repair stages.
|
|
12
|
+
- `repair()` rewrites text that is not Python, by the call's name: formulas,
|
|
13
|
+
rules, negation, backtick assignments, keywords before positional
|
|
14
|
+
arguments, and backtick names, which become the neutral placeholders
|
|
15
|
+
`__r_name__` and `__r_assign__`.
|
|
16
|
+
- `rewrite()` adds the reinterpretations, `^` → `**` and `~x` → `"~x"`, only in
|
|
17
|
+
calls the dialect's package owns, by tidy3's rule (the latest binding in the
|
|
18
|
+
cell, then the context), or in packages listed in `always=`.
|
|
19
|
+
- `imported_names()` builds a context from earlier cells' imports, for exports
|
|
20
|
+
that have no live namespace.
|
|
21
|
+
- `Dialect.prefixes` says which module prefixes make `prefix.name(...)` one of
|
|
22
|
+
a dialect's calls: any (the default), none (bare calls only), or a set of
|
|
23
|
+
names, such as stats3's `{"stats3"}`.
|
tidysyntax-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Rigoberto Leyva Salmeron
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tidysyntax
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: R syntax in notebook cells, rewritten to Python: shared by tidy3, tidy3a, plot3 and stats3
|
|
5
|
+
Author: Rigoberto Leyva Salmeron
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/rleyvasal/tidysyntax
|
|
8
|
+
Keywords: notebooks,ipython,jupyter,R,formulas,tidyverse
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Framework :: IPython
|
|
11
|
+
Classifier: Framework :: Jupyter
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Requires-Python: >=3.10
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
License-File: LICENSE
|
|
16
|
+
Provides-Extra: dev
|
|
17
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
18
|
+
Dynamic: license-file
|
|
19
|
+
|
|
20
|
+
# tidysyntax
|
|
21
|
+
|
|
22
|
+
R syntax in notebook cells, rewritten to Python. It is the shared notebook
|
|
23
|
+
layer of [tidy3](https://github.com/rleyvasal/tidy3),
|
|
24
|
+
[tidy3a](https://github.com/rleyvasal/tidy3a),
|
|
25
|
+
[plot3](https://github.com/rleyvasal/plot3) and
|
|
26
|
+
[stats3](https://github.com/rleyvasal/stats3), released on its own so a fix
|
|
27
|
+
reaches all four at once. It needs only the standard library.
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
import tidysyntax as ts
|
|
31
|
+
|
|
32
|
+
stats3 = ts.Dialect("stats3", formula_calls={"t_test", "lm"})
|
|
33
|
+
tidy3 = ts.Dialect("tidy3", negation_calls={"filter"}, ordered_calls={"select"},
|
|
34
|
+
rule_calls={"case_when"})
|
|
35
|
+
|
|
36
|
+
ts.rewrite("penguins >> t_test(body_mass_g ~ sex)", [stats3, tidy3])
|
|
37
|
+
# "penguins >> t_test('body_mass_g ~ sex')"
|
|
38
|
+
ts.rewrite('df >> filter(!is_na(x)) >> select(Cus = NUM, Row)', [stats3, tidy3])
|
|
39
|
+
# "df >> filter(~is_na(x)) >> select(__r_assign__('Cus', (NUM)), Row)"
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Two kinds of rewrite
|
|
43
|
+
|
|
44
|
+
**Repairs** change only text that is a syntax error in Python, so no valid
|
|
45
|
+
program can mean anything else by it. They run by the call's name, whoever
|
|
46
|
+
owns the call:
|
|
47
|
+
|
|
48
|
+
| Written | Becomes | In calls listed as |
|
|
49
|
+
|---|---|---|
|
|
50
|
+
| `` `First Name` `` | `__r_name__('First Name')` | anywhere in code |
|
|
51
|
+
| `` f(`new col` = x) `` | `f(__r_assign__('new col', (x)))` | any call |
|
|
52
|
+
| `filter(!x)` | `filter(~x)` | `negation_calls` |
|
|
53
|
+
| `select(a = x, b)` | `select(__r_assign__('a', (x)), b)` | `ordered_calls` |
|
|
54
|
+
| `case_when(x > 6 ~ "big")` | `case_when((x > 6, "big"))` | `rule_calls` |
|
|
55
|
+
| `t_test(y ~ g)`, `facet_grid(drv ~ cyl)` | `t_test('y ~ g')` | `formula_calls` |
|
|
56
|
+
|
|
57
|
+
**Reinterpretations** change valid Python, so they run only in calls the
|
|
58
|
+
dialect's package owns:
|
|
59
|
+
|
|
60
|
+
| Written | Becomes | In calls listed as | Python's meaning |
|
|
61
|
+
|---|---|---|---|
|
|
62
|
+
| `geom_function(lambda x: x^2)` | `x**2` | `power_calls` | XOR |
|
|
63
|
+
| `facet_wrap(~cyl)` | `facet_wrap('~cyl')` | `unary_formula_calls` | bitwise not |
|
|
64
|
+
|
|
65
|
+
Who owns a call follows tidy3's rule: a function's local variable is the
|
|
66
|
+
notebook's; otherwise the latest top-level binding in the cell before the call
|
|
67
|
+
decides (an import gives its module, any other binding makes the name the
|
|
68
|
+
notebook's); otherwise the `context` does (an IPython namespace, or
|
|
69
|
+
`imported_names(...)` of earlier cells); a name the context lacks is the
|
|
70
|
+
exporting package's. A cell that does not parse gets no reinterpretation.
|
|
71
|
+
With `always={"plot3"}` a caller turns plot3's reinterpretations on everywhere.
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
plot3 = ts.Dialect("plot3", power_calls={"geom_function"}, exports={"geom_function"})
|
|
75
|
+
|
|
76
|
+
ts.rewrite("def geom_function(f):\n return f(3)\nr = geom_function(lambda x: x ^ 2)", [plot3])
|
|
77
|
+
# unchanged: the notebook's own geom_function keeps XOR
|
|
78
|
+
ts.rewrite("from plot3 import *\ngeom_function(lambda x: x ^ 2)", [plot3])
|
|
79
|
+
# "from plot3 import *\ngeom_function(lambda x: x ** 2)"
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
The placeholders' run-time values are `RName` and `RAssign`
|
|
83
|
+
(`runtime_values()` gives both, for a namespace); each package's own AST pass
|
|
84
|
+
turns them into its column references inside its calls.
|
|
85
|
+
|
|
86
|
+
## Status
|
|
87
|
+
|
|
88
|
+
Phases 1 to 5 of [the design](docs/design.md): the scanner, every repair,
|
|
89
|
+
the ownership rule and the two reinterpretations, used by tidy3 0.15, tidy3a
|
|
90
|
+
0.16, plot3 0.8 and stats3 0.6. Not yet
|
|
91
|
+
here: the IPython coordinator that installs one transformer for all packages,
|
|
92
|
+
continuation lines (`>>` for tidy3, `+` for plot3), and entry points.
|
|
93
|
+
|
|
94
|
+
Until every package's AST pass reads the neutral placeholders, each package
|
|
95
|
+
calls only the repairs whose output the others already read, and tidy3 and
|
|
96
|
+
plot3 turn `__r_name__` and `__r_assign__` back into their own names
|
|
97
|
+
(`__tidy3_bt__`, `__tidy3_assign__`, `__plot3_bt__`). tidy3a writes
|
|
98
|
+
`__r_assign__` and hands it to tidy3 as `__tidy3_assign__` in tidy3's calls.
|
|
99
|
+
|
|
100
|
+
Where the output differs from a package's own rewrite before the switch:
|
|
101
|
+
|
|
102
|
+
- stats3: a multi-line formula keeps its indentation and trailing comma
|
|
103
|
+
(`t_test(\n 'y ~ g',\n)`), and backtick names in formulas are kept.
|
|
104
|
+
- tidy3: a keyword before a positional argument in a method call
|
|
105
|
+
(`df.select(a = x, b)`) is rewritten like the verb, as `!` in one already
|
|
106
|
+
was; backticks inside strings and comments stay; a backtick assignment is
|
|
107
|
+
rewritten only as a call argument.
|
|
108
|
+
- tidy3a: its placeholder is `__r_assign__` (it still reads `__tidy3a_assign__`).
|
|
109
|
+
- plot3: a placeholder another package left inside a one-sided facet formula
|
|
110
|
+
(`facet_wrap(~__tidy3_bt__('a b'))`) becomes the name again: `'~a b'`.
|
|
111
|
+
|
|
112
|
+
`scripts/family/` runs all five suites together on another machine: `sync.sh`
|
|
113
|
+
copies the working trees and starts `run.sh` in a tmux session there, and
|
|
114
|
+
`status.sh` reports each suite's result and failures.
|
|
115
|
+
|
|
116
|
+
Known limits: a Python 3.12 f-string that nests the same quote
|
|
117
|
+
(`f"{d["k"]}"`) is read as two strings, so a rewrite may see code inside it.
|
|
118
|
+
|
|
119
|
+
## Development
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
python -m pip install -e ".[dev]"
|
|
123
|
+
python -m pytest -q
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
The tests include properties: repairs leave every cell that parses unchanged,
|
|
127
|
+
`rewrite` leaves the standard library's own source unchanged with every
|
|
128
|
+
dialect registered, and rewriting is idempotent.
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# tidysyntax
|
|
2
|
+
|
|
3
|
+
R syntax in notebook cells, rewritten to Python. It is the shared notebook
|
|
4
|
+
layer of [tidy3](https://github.com/rleyvasal/tidy3),
|
|
5
|
+
[tidy3a](https://github.com/rleyvasal/tidy3a),
|
|
6
|
+
[plot3](https://github.com/rleyvasal/plot3) and
|
|
7
|
+
[stats3](https://github.com/rleyvasal/stats3), released on its own so a fix
|
|
8
|
+
reaches all four at once. It needs only the standard library.
|
|
9
|
+
|
|
10
|
+
```python
|
|
11
|
+
import tidysyntax as ts
|
|
12
|
+
|
|
13
|
+
stats3 = ts.Dialect("stats3", formula_calls={"t_test", "lm"})
|
|
14
|
+
tidy3 = ts.Dialect("tidy3", negation_calls={"filter"}, ordered_calls={"select"},
|
|
15
|
+
rule_calls={"case_when"})
|
|
16
|
+
|
|
17
|
+
ts.rewrite("penguins >> t_test(body_mass_g ~ sex)", [stats3, tidy3])
|
|
18
|
+
# "penguins >> t_test('body_mass_g ~ sex')"
|
|
19
|
+
ts.rewrite('df >> filter(!is_na(x)) >> select(Cus = NUM, Row)', [stats3, tidy3])
|
|
20
|
+
# "df >> filter(~is_na(x)) >> select(__r_assign__('Cus', (NUM)), Row)"
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Two kinds of rewrite
|
|
24
|
+
|
|
25
|
+
**Repairs** change only text that is a syntax error in Python, so no valid
|
|
26
|
+
program can mean anything else by it. They run by the call's name, whoever
|
|
27
|
+
owns the call:
|
|
28
|
+
|
|
29
|
+
| Written | Becomes | In calls listed as |
|
|
30
|
+
|---|---|---|
|
|
31
|
+
| `` `First Name` `` | `__r_name__('First Name')` | anywhere in code |
|
|
32
|
+
| `` f(`new col` = x) `` | `f(__r_assign__('new col', (x)))` | any call |
|
|
33
|
+
| `filter(!x)` | `filter(~x)` | `negation_calls` |
|
|
34
|
+
| `select(a = x, b)` | `select(__r_assign__('a', (x)), b)` | `ordered_calls` |
|
|
35
|
+
| `case_when(x > 6 ~ "big")` | `case_when((x > 6, "big"))` | `rule_calls` |
|
|
36
|
+
| `t_test(y ~ g)`, `facet_grid(drv ~ cyl)` | `t_test('y ~ g')` | `formula_calls` |
|
|
37
|
+
|
|
38
|
+
**Reinterpretations** change valid Python, so they run only in calls the
|
|
39
|
+
dialect's package owns:
|
|
40
|
+
|
|
41
|
+
| Written | Becomes | In calls listed as | Python's meaning |
|
|
42
|
+
|---|---|---|---|
|
|
43
|
+
| `geom_function(lambda x: x^2)` | `x**2` | `power_calls` | XOR |
|
|
44
|
+
| `facet_wrap(~cyl)` | `facet_wrap('~cyl')` | `unary_formula_calls` | bitwise not |
|
|
45
|
+
|
|
46
|
+
Who owns a call follows tidy3's rule: a function's local variable is the
|
|
47
|
+
notebook's; otherwise the latest top-level binding in the cell before the call
|
|
48
|
+
decides (an import gives its module, any other binding makes the name the
|
|
49
|
+
notebook's); otherwise the `context` does (an IPython namespace, or
|
|
50
|
+
`imported_names(...)` of earlier cells); a name the context lacks is the
|
|
51
|
+
exporting package's. A cell that does not parse gets no reinterpretation.
|
|
52
|
+
With `always={"plot3"}` a caller turns plot3's reinterpretations on everywhere.
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
plot3 = ts.Dialect("plot3", power_calls={"geom_function"}, exports={"geom_function"})
|
|
56
|
+
|
|
57
|
+
ts.rewrite("def geom_function(f):\n return f(3)\nr = geom_function(lambda x: x ^ 2)", [plot3])
|
|
58
|
+
# unchanged: the notebook's own geom_function keeps XOR
|
|
59
|
+
ts.rewrite("from plot3 import *\ngeom_function(lambda x: x ^ 2)", [plot3])
|
|
60
|
+
# "from plot3 import *\ngeom_function(lambda x: x ** 2)"
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
The placeholders' run-time values are `RName` and `RAssign`
|
|
64
|
+
(`runtime_values()` gives both, for a namespace); each package's own AST pass
|
|
65
|
+
turns them into its column references inside its calls.
|
|
66
|
+
|
|
67
|
+
## Status
|
|
68
|
+
|
|
69
|
+
Phases 1 to 5 of [the design](docs/design.md): the scanner, every repair,
|
|
70
|
+
the ownership rule and the two reinterpretations, used by tidy3 0.15, tidy3a
|
|
71
|
+
0.16, plot3 0.8 and stats3 0.6. Not yet
|
|
72
|
+
here: the IPython coordinator that installs one transformer for all packages,
|
|
73
|
+
continuation lines (`>>` for tidy3, `+` for plot3), and entry points.
|
|
74
|
+
|
|
75
|
+
Until every package's AST pass reads the neutral placeholders, each package
|
|
76
|
+
calls only the repairs whose output the others already read, and tidy3 and
|
|
77
|
+
plot3 turn `__r_name__` and `__r_assign__` back into their own names
|
|
78
|
+
(`__tidy3_bt__`, `__tidy3_assign__`, `__plot3_bt__`). tidy3a writes
|
|
79
|
+
`__r_assign__` and hands it to tidy3 as `__tidy3_assign__` in tidy3's calls.
|
|
80
|
+
|
|
81
|
+
Where the output differs from a package's own rewrite before the switch:
|
|
82
|
+
|
|
83
|
+
- stats3: a multi-line formula keeps its indentation and trailing comma
|
|
84
|
+
(`t_test(\n 'y ~ g',\n)`), and backtick names in formulas are kept.
|
|
85
|
+
- tidy3: a keyword before a positional argument in a method call
|
|
86
|
+
(`df.select(a = x, b)`) is rewritten like the verb, as `!` in one already
|
|
87
|
+
was; backticks inside strings and comments stay; a backtick assignment is
|
|
88
|
+
rewritten only as a call argument.
|
|
89
|
+
- tidy3a: its placeholder is `__r_assign__` (it still reads `__tidy3a_assign__`).
|
|
90
|
+
- plot3: a placeholder another package left inside a one-sided facet formula
|
|
91
|
+
(`facet_wrap(~__tidy3_bt__('a b'))`) becomes the name again: `'~a b'`.
|
|
92
|
+
|
|
93
|
+
`scripts/family/` runs all five suites together on another machine: `sync.sh`
|
|
94
|
+
copies the working trees and starts `run.sh` in a tmux session there, and
|
|
95
|
+
`status.sh` reports each suite's result and failures.
|
|
96
|
+
|
|
97
|
+
Known limits: a Python 3.12 f-string that nests the same quote
|
|
98
|
+
(`f"{d["k"]}"`) is read as two strings, so a rewrite may see code inside it.
|
|
99
|
+
|
|
100
|
+
## Development
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
python -m pip install -e ".[dev]"
|
|
104
|
+
python -m pytest -q
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
The tests include properties: repairs leave every cell that parses unchanged,
|
|
108
|
+
`rewrite` leaves the standard library's own source unchanged with every
|
|
109
|
+
dialect registered, and rewriting is idempotent.
|