tidy3syntax 0.1.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.
@@ -0,0 +1,29 @@
1
+ # Changelog
2
+
3
+ ## 0.1.1 — 2026-11-01
4
+
5
+ The first release as `tidy3syntax`, the tidy3 family's name; tidy3 0.15.1,
6
+ tidy3a 0.16.1, plot3 0.8.1 and stats3 0.6.1 use it. The code is 0.1.0's.
7
+ 0.1.0 was published as `tidysyntax`, which this name supersedes.
8
+
9
+ ## 0.1.0 — 2026-11-01
10
+
11
+ First version, published as `tidysyntax`: phases 1 to 5 of
12
+ [the design](docs/design.md).
13
+
14
+ - `Dialect` describes one package's R syntax as data: which calls take `!`,
15
+ `name = value` before a positional argument, `left ~ right` rules (tidy3's
16
+ tuples), `y ~ x` formulas (plot3's and stats3's strings), `^` as power and
17
+ one-sided formulas, and a package's own repair stages.
18
+ - `repair()` rewrites text that is not Python, by the call's name: formulas,
19
+ rules, negation, backtick assignments, keywords before positional
20
+ arguments, and backtick names, which become the neutral placeholders
21
+ `__r_name__` and `__r_assign__`.
22
+ - `rewrite()` adds the reinterpretations, `^` → `**` and `~x` → `"~x"`, only in
23
+ calls the dialect's package owns, by tidy3's rule (the latest binding in the
24
+ cell, then the context), or in packages listed in `always=`.
25
+ - `imported_names()` builds a context from earlier cells' imports, for exports
26
+ that have no live namespace.
27
+ - `Dialect.prefixes` says which module prefixes make `prefix.name(...)` one of
28
+ a dialect's calls: any (the default), none (bare calls only), or a set of
29
+ names, such as stats3's `{"stats3"}`.
@@ -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,3 @@
1
+ include CHANGELOG.md
2
+ recursive-include docs *.md
3
+ recursive-include tests *.py
@@ -0,0 +1,128 @@
1
+ Metadata-Version: 2.4
2
+ Name: tidy3syntax
3
+ Version: 0.1.1
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/tidy3syntax
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
+ # tidy3syntax
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 tidy3syntax 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.1, tidy3a
90
+ 0.16.1, plot3 0.8.1 and stats3 0.6.1. 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
+ # tidy3syntax
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 tidy3syntax 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.1, tidy3a
71
+ 0.16.1, plot3 0.8.1 and stats3 0.6.1. 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.