unit-syntax 0.1.1__tar.gz → 0.1.3__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.
- {unit_syntax-0.1.1 → unit_syntax-0.1.3}/PKG-INFO +34 -36
- {unit_syntax-0.1.1 → unit_syntax-0.1.3}/README.md +33 -35
- {unit_syntax-0.1.1 → unit_syntax-0.1.3}/pyproject.toml +2 -2
- {unit_syntax-0.1.1 → unit_syntax-0.1.3}/unit_syntax/parser.py +27 -16
- {unit_syntax-0.1.1 → unit_syntax-0.1.3}/unit_syntax/__init__.py +0 -0
- {unit_syntax-0.1.1 → unit_syntax-0.1.3}/unit_syntax/transform.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: unit-syntax
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3
|
|
4
4
|
Summary: Literal physical units for Jupyter and IPython
|
|
5
5
|
Home-page: https://github.com/ahupp/unit-syntax
|
|
6
6
|
Author: Adam Hupp
|
|
@@ -20,7 +20,6 @@ Description-Content-Type: text/markdown
|
|
|
20
20
|
`unit-syntax` extends the Python language in Jupyter/IPython to support expressions with physical units:
|
|
21
21
|
|
|
22
22
|
```python
|
|
23
|
-
>>> import unit_syntax.ipython
|
|
24
23
|
>>> speed = 5 meters/second
|
|
25
24
|
>>> 2 seconds * speed
|
|
26
25
|
10 meter
|
|
@@ -55,7 +54,7 @@ Units apply to the preceding value (a literal, variable, function call or indexi
|
|
|
55
54
|
x * 1.21 gigawatts
|
|
56
55
|
```
|
|
57
56
|
|
|
58
|
-
This is equivalent to `x * (1.21 gigawatts)`, and desugars to something like `x * Quantity(1.21, "gigawatts")`.
|
|
57
|
+
This is equivalent to `x * (1.21 gigawatts)`, and desugars to something like `x * Quantity(1.21, "gigawatts")`. The high precedence means units apply to the literal not the whole expression.
|
|
59
58
|
|
|
60
59
|
Values can be converted to another measurement system:
|
|
61
60
|
|
|
@@ -71,34 +70,36 @@ location = velocity * 2 seconds
|
|
|
71
70
|
distance_traveled = numpy.linalg.norm(location)
|
|
72
71
|
```
|
|
73
72
|
|
|
73
|
+
## The Grammar
|
|
74
|
+
|
|
74
75
|
The units term follows this grammar:
|
|
75
76
|
|
|
76
77
|
```
|
|
77
78
|
units:
|
|
78
|
-
| NAME '/'
|
|
79
|
-
| NAME '*'
|
|
80
|
-
| NAME
|
|
79
|
+
| NAME '/' units_group
|
|
80
|
+
| NAME '*' units_group
|
|
81
|
+
| NAME units_group
|
|
81
82
|
| NAME '**' NUMBER
|
|
82
83
|
| NAME
|
|
84
|
+
|
|
85
|
+
units_group:
|
|
86
|
+
| '(' units ')'
|
|
87
|
+
| units
|
|
83
88
|
```
|
|
84
89
|
|
|
85
|
-
## Why?
|
|
90
|
+
## Why? How?
|
|
86
91
|
|
|
87
92
|
I like using Python+[Jupyter Notebook](https://jupyter.org/) as a calculator for physical problems and often wish it had the clarity and type checking of explicit units. [Pint](https://pint.readthedocs.io/) is great, but (IMO) its necessary verbosity makes it hard to see the underlying calculation that's going.
|
|
88
93
|
|
|
89
|
-
`unit-syntax` is an IPython/Jupyter [custom input transformer](https://ipython.readthedocs.io/en/stable/config/inputtransforms.html) that rewrites expressions with units into calls to `pint.Quantity`.
|
|
90
|
-
|
|
91
|
-
This is possible without ambiguity in the python grammar because it's otherwise invalid for a "primary" (literal, function call etc) to be immediately followed by
|
|
94
|
+
`unit-syntax` is an IPython/Jupyter [custom input transformer](https://ipython.readthedocs.io/en/stable/config/inputtransforms.html) that rewrites expressions with units into calls to `pint.Quantity`. The parser is a lightly modified version of the Python grammar using the same parser generator ([pegen](https://github.com/we-like-parsers/pegen)) as Python itself.
|
|
92
95
|
|
|
93
96
|
`unit-syntax` cannot (currently) be used for standalone python scripts outside of IPython/Jupyter, but that's in principle possible through [meta_path import hooks](https://docs.python.org/3/reference/import.html#the-meta-path).
|
|
94
97
|
|
|
95
98
|
The syntax takes advantage of the fact that that in python its illegal for a NAME to follow a "primary" (literal, function call etc), so there's no ambiguity.
|
|
96
99
|
|
|
97
|
-
|
|
98
100
|
## Prior Art
|
|
99
101
|
|
|
100
|
-
The immediate inspriration of `unit-syntax` is a language called [Fortress](https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.180.6323&rep=rep1&type=pdf
|
|
101
|
-
) from Sun Microsystems. Fortress was intended as a modern Fortran, and had first-class support for units in both the syntax and type system.
|
|
102
|
+
The immediate inspriration of `unit-syntax` is a language called [Fortress](https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.180.6323&rep=rep1&type=pdf) from Sun Microsystems. Fortress was intended as a modern Fortran, and had first-class support for units in both the syntax and type system.
|
|
102
103
|
|
|
103
104
|
F# (an OCaml derivative from Microsoft) also [has first class support for units](https://en.wikibooks.org/wiki/F_Sharp_Programming/Units_of_Measure).
|
|
104
105
|
|
|
@@ -106,17 +107,17 @@ The Julia package [Unitful.jl](http://painterqubits.github.io/Unitful.jl/stable/
|
|
|
106
107
|
|
|
107
108
|
## Open questions and future work
|
|
108
109
|
|
|
109
|
-
|
|
110
|
+
- Fortress uses an `in` operator to apply units to a non-literal value, e.g `x in meters`. This has the advantage of being unambiguous regardless of parenthesization. In python this would conflcit with `value in [a, b, c]`, but `as` is
|
|
110
111
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
112
|
+
- Move to tree-sitter, which will be simpler and has a chance of providing syntax highlighting
|
|
113
|
+
- Test against various ipython and python versions
|
|
114
|
+
- Support standalone scripts through sys.meta_path
|
|
115
|
+
- Check units at parse time
|
|
116
|
+
- Unit type hints, maybe checked with [@runtime_checkable](https://docs.python.org/3/library/typing.html#typing.runtime_checkable). More Pint typechecking [discussion](https://github.com/hgrecco/pint/issues/1166)
|
|
117
|
+
- Pint does not do the right thing when applied to generator expressions, e.g `(a for a in range(0, 4)) meters`
|
|
118
|
+
- Demo colab notebook: https://colab.research.google.com/drive/1PInyLGZHnUzEuUVgMsLrUUNdCurXK7v1#scrollTo=JszzXmATY0TV
|
|
119
|
+
- Describe parsing ambuguity like `1 meters * sin(45 degrees)`
|
|
120
|
+
- Figure out story around parenthesization
|
|
120
121
|
|
|
121
122
|
## Development
|
|
122
123
|
|
|
@@ -133,17 +134,14 @@ Running tests:
|
|
|
133
134
|
|
|
134
135
|
## Future work and open questions
|
|
135
136
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
137
|
+
- Parenthisized units expressions
|
|
138
|
+
- Demo colab notebook
|
|
139
|
+
- Move to tree-sitter so there's a chance of getting syntax highlighting
|
|
140
|
+
- Jupyter syntax checks
|
|
141
|
+
- Typography of output
|
|
142
|
+
- Test against various ipython and python versions
|
|
143
|
+
- Support standalone scripts through sys.meta_path
|
|
144
|
+
- Check units at parse time
|
|
145
|
+
- Unit type hints, maybe checked with [@runtime_checkable](https://docs.python.org/3/library/typing.html#typing.runtime_checkable). More Pint typechecking [discussion](https://github.com/hgrecco/pint/issues/1166)
|
|
146
|
+
- Does not do the right thing when applied to generator expressions, e.g `(a for a in range(0, 4)) meters`
|
|
149
147
|
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
`unit-syntax` extends the Python language in Jupyter/IPython to support expressions with physical units:
|
|
2
2
|
|
|
3
3
|
```python
|
|
4
|
-
>>> import unit_syntax.ipython
|
|
5
4
|
>>> speed = 5 meters/second
|
|
6
5
|
>>> 2 seconds * speed
|
|
7
6
|
10 meter
|
|
@@ -36,7 +35,7 @@ Units apply to the preceding value (a literal, variable, function call or indexi
|
|
|
36
35
|
x * 1.21 gigawatts
|
|
37
36
|
```
|
|
38
37
|
|
|
39
|
-
This is equivalent to `x * (1.21 gigawatts)`, and desugars to something like `x * Quantity(1.21, "gigawatts")`.
|
|
38
|
+
This is equivalent to `x * (1.21 gigawatts)`, and desugars to something like `x * Quantity(1.21, "gigawatts")`. The high precedence means units apply to the literal not the whole expression.
|
|
40
39
|
|
|
41
40
|
Values can be converted to another measurement system:
|
|
42
41
|
|
|
@@ -52,34 +51,36 @@ location = velocity * 2 seconds
|
|
|
52
51
|
distance_traveled = numpy.linalg.norm(location)
|
|
53
52
|
```
|
|
54
53
|
|
|
54
|
+
## The Grammar
|
|
55
|
+
|
|
55
56
|
The units term follows this grammar:
|
|
56
57
|
|
|
57
58
|
```
|
|
58
59
|
units:
|
|
59
|
-
| NAME '/'
|
|
60
|
-
| NAME '*'
|
|
61
|
-
| NAME
|
|
60
|
+
| NAME '/' units_group
|
|
61
|
+
| NAME '*' units_group
|
|
62
|
+
| NAME units_group
|
|
62
63
|
| NAME '**' NUMBER
|
|
63
64
|
| NAME
|
|
65
|
+
|
|
66
|
+
units_group:
|
|
67
|
+
| '(' units ')'
|
|
68
|
+
| units
|
|
64
69
|
```
|
|
65
70
|
|
|
66
|
-
## Why?
|
|
71
|
+
## Why? How?
|
|
67
72
|
|
|
68
73
|
I like using Python+[Jupyter Notebook](https://jupyter.org/) as a calculator for physical problems and often wish it had the clarity and type checking of explicit units. [Pint](https://pint.readthedocs.io/) is great, but (IMO) its necessary verbosity makes it hard to see the underlying calculation that's going.
|
|
69
74
|
|
|
70
|
-
`unit-syntax` is an IPython/Jupyter [custom input transformer](https://ipython.readthedocs.io/en/stable/config/inputtransforms.html) that rewrites expressions with units into calls to `pint.Quantity`.
|
|
71
|
-
|
|
72
|
-
This is possible without ambiguity in the python grammar because it's otherwise invalid for a "primary" (literal, function call etc) to be immediately followed by
|
|
75
|
+
`unit-syntax` is an IPython/Jupyter [custom input transformer](https://ipython.readthedocs.io/en/stable/config/inputtransforms.html) that rewrites expressions with units into calls to `pint.Quantity`. The parser is a lightly modified version of the Python grammar using the same parser generator ([pegen](https://github.com/we-like-parsers/pegen)) as Python itself.
|
|
73
76
|
|
|
74
77
|
`unit-syntax` cannot (currently) be used for standalone python scripts outside of IPython/Jupyter, but that's in principle possible through [meta_path import hooks](https://docs.python.org/3/reference/import.html#the-meta-path).
|
|
75
78
|
|
|
76
79
|
The syntax takes advantage of the fact that that in python its illegal for a NAME to follow a "primary" (literal, function call etc), so there's no ambiguity.
|
|
77
80
|
|
|
78
|
-
|
|
79
81
|
## Prior Art
|
|
80
82
|
|
|
81
|
-
The immediate inspriration of `unit-syntax` is a language called [Fortress](https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.180.6323&rep=rep1&type=pdf
|
|
82
|
-
) from Sun Microsystems. Fortress was intended as a modern Fortran, and had first-class support for units in both the syntax and type system.
|
|
83
|
+
The immediate inspriration of `unit-syntax` is a language called [Fortress](https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.180.6323&rep=rep1&type=pdf) from Sun Microsystems. Fortress was intended as a modern Fortran, and had first-class support for units in both the syntax and type system.
|
|
83
84
|
|
|
84
85
|
F# (an OCaml derivative from Microsoft) also [has first class support for units](https://en.wikibooks.org/wiki/F_Sharp_Programming/Units_of_Measure).
|
|
85
86
|
|
|
@@ -87,17 +88,17 @@ The Julia package [Unitful.jl](http://painterqubits.github.io/Unitful.jl/stable/
|
|
|
87
88
|
|
|
88
89
|
## Open questions and future work
|
|
89
90
|
|
|
90
|
-
|
|
91
|
+
- Fortress uses an `in` operator to apply units to a non-literal value, e.g `x in meters`. This has the advantage of being unambiguous regardless of parenthesization. In python this would conflcit with `value in [a, b, c]`, but `as` is
|
|
91
92
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
93
|
+
- Move to tree-sitter, which will be simpler and has a chance of providing syntax highlighting
|
|
94
|
+
- Test against various ipython and python versions
|
|
95
|
+
- Support standalone scripts through sys.meta_path
|
|
96
|
+
- Check units at parse time
|
|
97
|
+
- Unit type hints, maybe checked with [@runtime_checkable](https://docs.python.org/3/library/typing.html#typing.runtime_checkable). More Pint typechecking [discussion](https://github.com/hgrecco/pint/issues/1166)
|
|
98
|
+
- Pint does not do the right thing when applied to generator expressions, e.g `(a for a in range(0, 4)) meters`
|
|
99
|
+
- Demo colab notebook: https://colab.research.google.com/drive/1PInyLGZHnUzEuUVgMsLrUUNdCurXK7v1#scrollTo=JszzXmATY0TV
|
|
100
|
+
- Describe parsing ambuguity like `1 meters * sin(45 degrees)`
|
|
101
|
+
- Figure out story around parenthesization
|
|
101
102
|
|
|
102
103
|
## Development
|
|
103
104
|
|
|
@@ -114,16 +115,13 @@ Running tests:
|
|
|
114
115
|
|
|
115
116
|
## Future work and open questions
|
|
116
117
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
118
|
+
- Parenthisized units expressions
|
|
119
|
+
- Demo colab notebook
|
|
120
|
+
- Move to tree-sitter so there's a chance of getting syntax highlighting
|
|
121
|
+
- Jupyter syntax checks
|
|
122
|
+
- Typography of output
|
|
123
|
+
- Test against various ipython and python versions
|
|
124
|
+
- Support standalone scripts through sys.meta_path
|
|
125
|
+
- Check units at parse time
|
|
126
|
+
- Unit type hints, maybe checked with [@runtime_checkable](https://docs.python.org/3/library/typing.html#typing.runtime_checkable). More Pint typechecking [discussion](https://github.com/hgrecco/pint/issues/1166)
|
|
127
|
+
- Does not do the right thing when applied to generator expressions, e.g `(a for a in range(0, 4)) meters`
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "unit-syntax"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.3"
|
|
4
4
|
authors = ["Adam Hupp <adam@hupp.org>"]
|
|
5
5
|
packages = [{include = "unit_syntax"}]
|
|
6
6
|
readme = "README.md"
|
|
@@ -17,8 +17,8 @@ pegen = "^0.2"
|
|
|
17
17
|
pytest = "^7.2"
|
|
18
18
|
black = "^22.12"
|
|
19
19
|
numpy = "^1.24.3"
|
|
20
|
-
mypi = "^0.0.3"
|
|
21
20
|
twine = "^4.0.2"
|
|
21
|
+
mypy = "^1.2.0"
|
|
22
22
|
|
|
23
23
|
[build-system]
|
|
24
24
|
requires = ["poetry-core>=1.0.0"]
|
|
@@ -2277,41 +2277,32 @@ class GeneratedParser(Parser):
|
|
|
2277
2277
|
|
|
2278
2278
|
@memoize
|
|
2279
2279
|
def units(self) -> Optional[Any]:
|
|
2280
|
-
# units:
|
|
2280
|
+
# units: NAME '/' units_group | NAME '*' units_group | NAME units_group | NAME '**' NUMBER | NAME
|
|
2281
2281
|
mark = self._mark()
|
|
2282
|
-
if (
|
|
2283
|
-
(literal := self.expect('('))
|
|
2284
|
-
and
|
|
2285
|
-
(units := self.units())
|
|
2286
|
-
and
|
|
2287
|
-
(literal_1 := self.expect(')'))
|
|
2288
|
-
):
|
|
2289
|
-
return [literal, units, literal_1];
|
|
2290
|
-
self._reset(mark)
|
|
2291
2282
|
if (
|
|
2292
2283
|
(name := self.name())
|
|
2293
2284
|
and
|
|
2294
2285
|
(literal := self.expect('/'))
|
|
2295
2286
|
and
|
|
2296
|
-
(
|
|
2287
|
+
(units_group := self.units_group())
|
|
2297
2288
|
):
|
|
2298
|
-
return [name, literal,
|
|
2289
|
+
return [name, literal, units_group];
|
|
2299
2290
|
self._reset(mark)
|
|
2300
2291
|
if (
|
|
2301
2292
|
(name := self.name())
|
|
2302
2293
|
and
|
|
2303
2294
|
(literal := self.expect('*'))
|
|
2304
2295
|
and
|
|
2305
|
-
(
|
|
2296
|
+
(units_group := self.units_group())
|
|
2306
2297
|
):
|
|
2307
|
-
return [name, literal,
|
|
2298
|
+
return [name, literal, units_group];
|
|
2308
2299
|
self._reset(mark)
|
|
2309
2300
|
if (
|
|
2310
2301
|
(name := self.name())
|
|
2311
2302
|
and
|
|
2312
|
-
(
|
|
2303
|
+
(units_group := self.units_group())
|
|
2313
2304
|
):
|
|
2314
|
-
return [name,
|
|
2305
|
+
return [name, units_group];
|
|
2315
2306
|
self._reset(mark)
|
|
2316
2307
|
if (
|
|
2317
2308
|
(name := self.name())
|
|
@@ -2329,6 +2320,26 @@ class GeneratedParser(Parser):
|
|
|
2329
2320
|
self._reset(mark)
|
|
2330
2321
|
return None;
|
|
2331
2322
|
|
|
2323
|
+
@memoize
|
|
2324
|
+
def units_group(self) -> Optional[Any]:
|
|
2325
|
+
# units_group: '(' units ')' | units
|
|
2326
|
+
mark = self._mark()
|
|
2327
|
+
if (
|
|
2328
|
+
(literal := self.expect('('))
|
|
2329
|
+
and
|
|
2330
|
+
(units := self.units())
|
|
2331
|
+
and
|
|
2332
|
+
(literal_1 := self.expect(')'))
|
|
2333
|
+
):
|
|
2334
|
+
return [literal, units, literal_1];
|
|
2335
|
+
self._reset(mark)
|
|
2336
|
+
if (
|
|
2337
|
+
(units := self.units())
|
|
2338
|
+
):
|
|
2339
|
+
return units;
|
|
2340
|
+
self._reset(mark)
|
|
2341
|
+
return None;
|
|
2342
|
+
|
|
2332
2343
|
@memoize_left_rec
|
|
2333
2344
|
def sum(self) -> Optional[Any]:
|
|
2334
2345
|
# sum: sum '+' term | sum '-' term | term
|
|
File without changes
|
|
File without changes
|