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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: unit-syntax
3
- Version: 0.1.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")`. The high precedence means units apply to the literal not the whole expression.
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 '/' units
79
- | NAME '*' units
80
- | NAME units
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? How?
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
- * 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
+ - 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
- * Move to tree-sitter, which will be simpler and has a chance of providing syntax highlighting
112
- * Test against various ipython and python versions
113
- * Support standalone scripts through sys.meta_path
114
- * Check units at parse time
115
- * 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)
116
- * Pint does not do the right thing when applied to generator expressions, e.g `(a for a in range(0, 4)) meters`
117
- * Demo colab notebook: https://colab.research.google.com/drive/1PInyLGZHnUzEuUVgMsLrUUNdCurXK7v1#scrollTo=JszzXmATY0TV
118
- * Describe parsing ambuguity like `1 meters * sin(45 degrees)`
119
- * Figure out story around parenthesization
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
- * Parenthisized units expressions
137
- * Demo colab notebook
138
- * Move to tree-sitter so there's a chance of getting syntax highlighting
139
- * Jupyter syntax checks
140
- * Typography of output
141
- * Test against various ipython and python versions
142
- * Support standalone scripts through sys.meta_path
143
- * Check units at parse time
144
- * 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)
145
- * Does not do the right thing when applied to generator expressions, e.g `(a for a in range(0, 4)) meters`
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")`. The high precedence means units apply to the literal not the whole expression.
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 '/' units
60
- | NAME '*' units
61
- | NAME units
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? How?
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
- * 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
+ - 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
- * Move to tree-sitter, which will be simpler and has a chance of providing syntax highlighting
93
- * Test against various ipython and python versions
94
- * Support standalone scripts through sys.meta_path
95
- * Check units at parse time
96
- * 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)
97
- * Pint does not do the right thing when applied to generator expressions, e.g `(a for a in range(0, 4)) meters`
98
- * Demo colab notebook: https://colab.research.google.com/drive/1PInyLGZHnUzEuUVgMsLrUUNdCurXK7v1#scrollTo=JszzXmATY0TV
99
- * Describe parsing ambuguity like `1 meters * sin(45 degrees)`
100
- * Figure out story around parenthesization
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
- * Parenthisized units expressions
118
- * Demo colab notebook
119
- * Move to tree-sitter so there's a chance of getting syntax highlighting
120
- * Jupyter syntax checks
121
- * Typography of output
122
- * Test against various ipython and python versions
123
- * Support standalone scripts through sys.meta_path
124
- * Check units at parse time
125
- * 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)
126
- * Does not do the right thing when applied to generator expressions, e.g `(a for a in range(0, 4)) meters`
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.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: '(' units ')' | NAME '/' units | NAME '*' units | NAME units | NAME '**' NUMBER | NAME
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
- (units := self.units())
2287
+ (units_group := self.units_group())
2297
2288
  ):
2298
- return [name, literal, units];
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
- (units := self.units())
2296
+ (units_group := self.units_group())
2306
2297
  ):
2307
- return [name, literal, units];
2298
+ return [name, literal, units_group];
2308
2299
  self._reset(mark)
2309
2300
  if (
2310
2301
  (name := self.name())
2311
2302
  and
2312
- (units := self.units())
2303
+ (units_group := self.units_group())
2313
2304
  ):
2314
- return [name, units];
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