snail-lang 0.3.8__tar.gz → 0.3.9__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.
Files changed (63) hide show
  1. {snail_lang-0.3.8 → snail_lang-0.3.9}/Cargo.lock +7 -7
  2. {snail_lang-0.3.8 → snail_lang-0.3.9}/PKG-INFO +96 -194
  3. {snail_lang-0.3.8 → snail_lang-0.3.9}/README.md +95 -193
  4. {snail_lang-0.3.8 → snail_lang-0.3.9}/crates/snail-ast/Cargo.toml +1 -1
  5. {snail_lang-0.3.8 → snail_lang-0.3.9}/crates/snail-core/Cargo.toml +1 -1
  6. {snail_lang-0.3.8 → snail_lang-0.3.9}/crates/snail-error/Cargo.toml +1 -1
  7. {snail_lang-0.3.8 → snail_lang-0.3.9}/crates/snail-lower/Cargo.toml +1 -1
  8. {snail_lang-0.3.8 → snail_lang-0.3.9}/crates/snail-lower/README.md +1 -1
  9. {snail_lang-0.3.8 → snail_lang-0.3.9}/crates/snail-parser/Cargo.toml +1 -1
  10. {snail_lang-0.3.8 → snail_lang-0.3.9}/crates/snail-python/Cargo.toml +1 -1
  11. {snail_lang-0.3.8 → snail_lang-0.3.9}/pyproject.toml +1 -1
  12. {snail_lang-0.3.8 → snail_lang-0.3.9}/Cargo.toml +0 -0
  13. {snail_lang-0.3.8 → snail_lang-0.3.9}/LICENSE +0 -0
  14. {snail_lang-0.3.8 → snail_lang-0.3.9}/crates/snail-ast/README.md +0 -0
  15. {snail_lang-0.3.8 → snail_lang-0.3.9}/crates/snail-ast/src/ast.rs +0 -0
  16. {snail_lang-0.3.8 → snail_lang-0.3.9}/crates/snail-ast/src/awk.rs +0 -0
  17. {snail_lang-0.3.8 → snail_lang-0.3.9}/crates/snail-ast/src/lib.rs +0 -0
  18. {snail_lang-0.3.8 → snail_lang-0.3.9}/crates/snail-core/README.md +0 -0
  19. {snail_lang-0.3.8 → snail_lang-0.3.9}/crates/snail-core/src/lib.rs +0 -0
  20. {snail_lang-0.3.8 → snail_lang-0.3.9}/crates/snail-error/README.md +0 -0
  21. {snail_lang-0.3.8 → snail_lang-0.3.9}/crates/snail-error/src/lib.rs +0 -0
  22. {snail_lang-0.3.8 → snail_lang-0.3.9}/crates/snail-lower/src/awk.rs +0 -0
  23. {snail_lang-0.3.8 → snail_lang-0.3.9}/crates/snail-lower/src/constants.rs +0 -0
  24. {snail_lang-0.3.8 → snail_lang-0.3.9}/crates/snail-lower/src/expr.rs +0 -0
  25. {snail_lang-0.3.8 → snail_lang-0.3.9}/crates/snail-lower/src/helpers.rs +0 -0
  26. {snail_lang-0.3.8 → snail_lang-0.3.9}/crates/snail-lower/src/lib.rs +0 -0
  27. {snail_lang-0.3.8 → snail_lang-0.3.9}/crates/snail-lower/src/operators.rs +0 -0
  28. {snail_lang-0.3.8 → snail_lang-0.3.9}/crates/snail-lower/src/program.rs +0 -0
  29. {snail_lang-0.3.8 → snail_lang-0.3.9}/crates/snail-lower/src/py_ast.rs +0 -0
  30. {snail_lang-0.3.8 → snail_lang-0.3.9}/crates/snail-lower/src/stmt.rs +0 -0
  31. {snail_lang-0.3.8 → snail_lang-0.3.9}/crates/snail-parser/README.md +0 -0
  32. {snail_lang-0.3.8 → snail_lang-0.3.9}/crates/snail-parser/src/awk.rs +0 -0
  33. {snail_lang-0.3.8 → snail_lang-0.3.9}/crates/snail-parser/src/expr.rs +0 -0
  34. {snail_lang-0.3.8 → snail_lang-0.3.9}/crates/snail-parser/src/lib.rs +0 -0
  35. {snail_lang-0.3.8 → snail_lang-0.3.9}/crates/snail-parser/src/literal.rs +0 -0
  36. {snail_lang-0.3.8 → snail_lang-0.3.9}/crates/snail-parser/src/snail.pest +0 -0
  37. {snail_lang-0.3.8 → snail_lang-0.3.9}/crates/snail-parser/src/stmt.rs +0 -0
  38. {snail_lang-0.3.8 → snail_lang-0.3.9}/crates/snail-parser/src/string.rs +0 -0
  39. {snail_lang-0.3.8 → snail_lang-0.3.9}/crates/snail-parser/src/util.rs +0 -0
  40. {snail_lang-0.3.8 → snail_lang-0.3.9}/crates/snail-parser/tests/common.rs +0 -0
  41. {snail_lang-0.3.8 → snail_lang-0.3.9}/crates/snail-parser/tests/errors.rs +0 -0
  42. {snail_lang-0.3.8 → snail_lang-0.3.9}/crates/snail-parser/tests/parser.rs +0 -0
  43. {snail_lang-0.3.8 → snail_lang-0.3.9}/crates/snail-parser/tests/statements.rs +0 -0
  44. {snail_lang-0.3.8 → snail_lang-0.3.9}/crates/snail-parser/tests/syntax_expressions.rs +0 -0
  45. {snail_lang-0.3.8 → snail_lang-0.3.9}/crates/snail-parser/tests/syntax_strings.rs +0 -0
  46. {snail_lang-0.3.8 → snail_lang-0.3.9}/crates/snail-python/src/lib.rs +0 -0
  47. {snail_lang-0.3.8 → snail_lang-0.3.9}/python/snail/__init__.py +0 -0
  48. {snail_lang-0.3.8 → snail_lang-0.3.9}/python/snail/cli.py +0 -0
  49. {snail_lang-0.3.8 → snail_lang-0.3.9}/python/snail/runtime/__init__.py +0 -0
  50. {snail_lang-0.3.8 → snail_lang-0.3.9}/python/snail/runtime/compact_try.py +0 -0
  51. {snail_lang-0.3.8 → snail_lang-0.3.9}/python/snail/runtime/regex.py +0 -0
  52. {snail_lang-0.3.8 → snail_lang-0.3.9}/python/snail/runtime/structured_accessor.py +0 -0
  53. {snail_lang-0.3.8 → snail_lang-0.3.9}/python/snail/runtime/subprocess.py +0 -0
  54. {snail_lang-0.3.8 → snail_lang-0.3.9}/python/snail/vendor/__init__.py +0 -0
  55. {snail_lang-0.3.8 → snail_lang-0.3.9}/python/snail/vendor/jmespath/LICENSE +0 -0
  56. {snail_lang-0.3.8 → snail_lang-0.3.9}/python/snail/vendor/jmespath/__init__.py +0 -0
  57. {snail_lang-0.3.8 → snail_lang-0.3.9}/python/snail/vendor/jmespath/ast.py +0 -0
  58. {snail_lang-0.3.8 → snail_lang-0.3.9}/python/snail/vendor/jmespath/compat.py +0 -0
  59. {snail_lang-0.3.8 → snail_lang-0.3.9}/python/snail/vendor/jmespath/exceptions.py +0 -0
  60. {snail_lang-0.3.8 → snail_lang-0.3.9}/python/snail/vendor/jmespath/functions.py +0 -0
  61. {snail_lang-0.3.8 → snail_lang-0.3.9}/python/snail/vendor/jmespath/lexer.py +0 -0
  62. {snail_lang-0.3.8 → snail_lang-0.3.9}/python/snail/vendor/jmespath/parser.py +0 -0
  63. {snail_lang-0.3.8 → snail_lang-0.3.9}/python/snail/vendor/jmespath/visitor.py +0 -0
@@ -485,11 +485,11 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
485
485
 
486
486
  [[package]]
487
487
  name = "snail-ast"
488
- version = "0.3.8"
488
+ version = "0.3.9"
489
489
 
490
490
  [[package]]
491
491
  name = "snail-core"
492
- version = "0.3.8"
492
+ version = "0.3.9"
493
493
  dependencies = [
494
494
  "pyo3",
495
495
  "snail-ast",
@@ -500,14 +500,14 @@ dependencies = [
500
500
 
501
501
  [[package]]
502
502
  name = "snail-error"
503
- version = "0.3.8"
503
+ version = "0.3.9"
504
504
  dependencies = [
505
505
  "snail-ast",
506
506
  ]
507
507
 
508
508
  [[package]]
509
509
  name = "snail-lower"
510
- version = "0.3.8"
510
+ version = "0.3.9"
511
511
  dependencies = [
512
512
  "pyo3",
513
513
  "snail-ast",
@@ -516,7 +516,7 @@ dependencies = [
516
516
 
517
517
  [[package]]
518
518
  name = "snail-parser"
519
- version = "0.3.8"
519
+ version = "0.3.9"
520
520
  dependencies = [
521
521
  "pest",
522
522
  "pest_derive",
@@ -526,7 +526,7 @@ dependencies = [
526
526
 
527
527
  [[package]]
528
528
  name = "snail-proptest"
529
- version = "0.3.8"
529
+ version = "0.3.9"
530
530
  dependencies = [
531
531
  "proptest",
532
532
  "pyo3",
@@ -540,7 +540,7 @@ dependencies = [
540
540
 
541
541
  [[package]]
542
542
  name = "snail-python"
543
- version = "0.3.8"
543
+ version = "0.3.9"
544
544
  dependencies = [
545
545
  "pyo3",
546
546
  "snail-core",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: snail-lang
3
- Version: 0.3.8
3
+ Version: 0.3.9
4
4
  Requires-Dist: maturin>=1.5 ; extra == 'dev'
5
5
  Requires-Dist: pytest ; extra == 'dev'
6
6
  Provides-Extra: dev
@@ -24,11 +24,21 @@ in interesting and horrible ways.</h1>
24
24
 
25
25
  **Snail** is a programming language that compiles to Python, combining Python's power with Perl/awk-inspired syntax for quick scripts and one-liners. No more whitespace sensitivity—just curly braces and concise expressions.
26
26
 
27
+ ## Installing Snail
28
+
29
+ Install [uv](https://docs.astral.sh/uv/getting-started/installation/) and then run:
30
+
31
+ ```bash
32
+ uv tool install -p 3.12 snail-lang
33
+ ```
34
+
35
+ That installs the `snail` CLI for your user; try it with `snail "print('hello')"` once the install completes.
36
+
27
37
  ## ✨ What Makes Snail Unique
28
38
 
29
39
  ### Curly Braces, Not Indentation
30
40
 
31
- Write Python logic without worrying about tabs vs spaces:
41
+ Write Python logic without worrying about whitespace:
32
42
 
33
43
  ```snail
34
44
  def process(items) {
@@ -39,69 +49,48 @@ def process(items) {
39
49
  }
40
50
  ```
41
51
 
42
- ### Built-in Subprocess Pipelines
52
+ Note, since it is jarring to write python with semicolons everywhere,
53
+ semicolons are optional. You can separate statements with newlines.
43
54
 
44
- Shell commands are first-class citizens with `$()` capture and `|` piping:
45
-
46
- ```snail
47
- # Capture command output with interpolation
48
- name = "world"
49
- greeting = $(echo hello {name})
55
+ ### Awk Mode
50
56
 
51
- # Pipe data through commands
52
- result = "foo\nbar\nbaz" | $(grep bar) | $(cat -n)
57
+ Process files line-by-line with familiar awk semantics:
53
58
 
54
- # Check command status
55
- @(make build)? # returns exit code on failure instead of raising
59
+ ```snail-awk("5\n4\n3\n2\n1\nbanana\n")
60
+ BEGIN { total = 0 }
61
+ /^[0-9]+/ { total = total + int($1) }
62
+ END { print("Sum:", total); assert total == 15}
56
63
  ```
57
64
 
65
+ Built-in variables: `$l` (line), `$f` (fields), `$n` (line number), `$fn` (per-file line number), `$p` (file path), `$m` (last match).
66
+
67
+
58
68
  ### Compact Error Handling
59
69
 
60
70
  The `?` operator makes error handling terse yet expressive:
61
71
 
62
72
  ```snail
63
- # Swallow exception, get the error object
64
- err = risky_operation()?
73
+ # Swallow exception, return None
74
+ err = risky()?
75
+
76
+ # Swallow exception, return exception object
77
+ err = risky():$e?
65
78
 
66
79
  # Provide a fallback value (exception available as $e)
67
- value = js(data):{}?
68
- details = fetch_url(url):"Error: {$e}"?
80
+ value = js("malformed json"):{}?
81
+ details = fetch_url("foo.com"):"default html"?
82
+ exception_info = fetch_url("example.com"):$e.http_response_code?
69
83
 
70
84
  # Access attributes directly
71
- name = risky()?.__class__.__name__
72
- args = risky()?.args[0]
73
- ```
74
-
75
- ### Regex Literals
76
-
77
- Pattern matching without `import re`:
78
-
79
- ```snail
80
- if email in /^[\w.]+@[\w.]+$/ {
81
- print("Valid email")
82
- }
83
-
84
- # Compiled regex for reuse
85
- pattern = /\d{3}-\d{4}/
86
- match = pattern.search(phone)
85
+ name = risky("")?.__class__.__name__
86
+ args = risky("becomes a list"):[1,2,3]?[0]
87
87
  ```
88
88
 
89
- ### Awk Mode
90
-
91
- Process files line-by-line with familiar awk semantics:
92
-
93
- ```snail
94
- #!/usr/bin/env -S snail --awk -f
95
- BEGIN { total = 0 }
96
- /^[0-9]+/ { total = total + int($f[0]) }
97
- END { print("Sum:", total) }
98
- ```
99
-
100
- Built-in variables: `$l` (line), `$f` (fields), `$n` (line number), `$fn` (per-file line number), `$p` (file path), `$m` (last match).
101
-
102
89
  ### Pipeline Operator
103
90
 
104
- The `|` operator enables data pipelining through pipeline-aware callables:
91
+ The `|` operator enables data pipelining as syntactic sugar for nested
92
+ function calls. `x | y | z` becomes `z(y(x))`. This lets you stay in a
93
+ shell mindset.
105
94
 
106
95
  ```snail
107
96
  # Pipe data to subprocess stdin
@@ -115,8 +104,11 @@ class Doubler {
115
104
  def __call__(self, x) { return x * 2 }
116
105
  }
117
106
  doubled = 21 | Doubler() # yields 42
107
+ ```
118
108
 
119
- # Use placeholders to control where piped values land in calls
109
+ Arbitrary callables make up pipelines, even if they have multiple parameters.
110
+ Snail supports this via placeholders.
111
+ ```snail
120
112
  greeting = "World" | greet("Hello ", _) # greet("Hello ", "World")
121
113
  excited = "World" | greet(_, "!") # greet("World", "!")
122
114
  formal = "World" | greet("Hello ", suffix=_) # greet("Hello ", "World")
@@ -128,99 +120,83 @@ the piped value at that position (including keyword arguments). Only one
128
120
  placeholder is allowed in a piped call. Outside of pipeline calls, `_` remains a
129
121
  normal identifier.
130
122
 
123
+ ### Built-in Subprocess
124
+
125
+ Shell commands are first-class citizens with capturing and non-capturing
126
+ forms.
127
+
128
+ ```snail
129
+ # Capture command output with interpolation
130
+ greeting = $(echo hello {name})
131
+
132
+ # Pipe data through commands
133
+ result = "foo\nbar\nbaz" | $(grep bar) | $(cat -n)
134
+
135
+ # Check command status
136
+ @(make build)? # returns exit code on failure instead of raising
137
+ ```
138
+
139
+
140
+ ### Regex Literals
141
+
142
+ Snail supports first class patterns. Think of them as an infinte set.
143
+
144
+ ```snail
145
+ if bad_email in /^[\w.]+@[\w.]+$/ {
146
+ print("Valid email")
147
+ }
148
+
149
+ # Compiled regex for reuse
150
+ pattern = /\d{3}-\d{4}/
151
+ match = pattern.search(phone)
152
+ ```
153
+
154
+ NOTE: this feature is WIP.
155
+
131
156
  ### JSON Queries with JMESPath
132
157
 
133
158
  Parse and query JSON data with the `js()` function and structured pipeline accessor:
134
159
 
135
160
  ```snail
136
161
  # Parse JSON and query with $[jmespath]
137
- data = js($(curl -s api.example.com/users))
138
- names = data | $[users[*].name]
139
- first_email = data | $[users[0].email]
162
+
163
+ # JSON query with JMESPath
164
+ data = js($(curl -s https://api.github.com/repos/sudonym1/snail))
165
+ counts = data | $[stargazers_count]
140
166
 
141
167
  # Inline parsing and querying
142
- result = js('{"foo": 12}') | $[foo]
168
+ result = js('{{"foo": 12}}') | $[foo]
143
169
 
144
170
  # JSONL parsing returns a list
145
- names = js('{"name": "Ada"}\n{"name": "Lin"}') | $[[*].name]
171
+ names = js('{{"name": "Ada"}}\n{{"name": "Lin"}}') | $[[*].name]
146
172
  ```
147
173
 
148
174
  ### Full Python Interoperability
149
175
 
150
- Snail compiles to Python AST—import any Python module, use any library:
151
-
152
- ```snail
153
- import pandas as pd
154
- from pathlib import Path
155
-
156
- df = pd.read_csv(Path("data.csv"))
157
- filtered = df[df["value"] > 100]
158
- ```
176
+ Snail compiles to Python AST—import any Python module, use any library, in any
177
+ environment. Assuming that you are using Python 3.10 or later.
159
178
 
160
179
  ## 🚀 Quick Start
161
180
 
162
181
  ```bash
163
- # Install from PyPI
164
- pip install snail-lang
182
+ # One-liner: arithmetic + interpolation
183
+ snail 'name="Snail"; print("{name} says: {6 * 7}")'
165
184
 
166
- # Run a one-liner
167
- snail "print('Hello, Snail!')"
185
+ # JSON query with JMESPath
186
+ snail 'js($(curl -s https://api.github.com/repos/sudonym1/snail)) | $[stargazers_count]'
168
187
 
169
- # Execute a script
170
- snail -f script.snail
188
+ # Compact error handling with fallback
189
+ snail 'result = int("oops"):"bad int {$e}"?; print(result)'
171
190
 
172
- # Awk mode for text processing
173
- cat data.txt | snail --awk '/error/ { print($l) }'
191
+ # Regex match and capture
192
+ snail 'm = "user@example.com" in /^[\\w.]+@([\\w.]+)$/; if m { print(m[1]) }'
193
+
194
+ # Awk mode: print line numbers for matches
195
+ rg -n "TODO" README.md | snail --awk '/TODO/ { print("{$n}: {$l}") }'
174
196
  ```
175
197
 
176
198
  ## 🏗️ Architecture
177
199
 
178
- Snail compiles to Python through a multi-stage pipeline:
179
-
180
- ```mermaid
181
- flowchart TB
182
- subgraph Input
183
- A[Snail Source Code]
184
- end
185
-
186
- subgraph Parsing["Parsing (Pest PEG Parser)"]
187
- B1[crates/snail-parser/src/snail.pest<br/>Grammar Definition]
188
- B2[crates/snail-parser/<br/>Parser Implementation]
189
- end
190
-
191
- subgraph AST["Abstract Syntax Tree"]
192
- C1[crates/snail-ast/src/ast.rs<br/>Program AST]
193
- C2[crates/snail-ast/src/awk.rs<br/>AwkProgram AST]
194
- end
195
-
196
- subgraph Lowering["Lowering"]
197
- D1[crates/snail-lower/<br/>AST → Python AST Transform]
198
- D2[python/snail/runtime/<br/>Runtime Helpers]
199
- end
200
-
201
- subgraph Execution
202
- E1[python/snail/cli.py<br/>CLI Interface]
203
- E2[pyo3 extension<br/>in-process exec]
204
- end
205
-
206
- A -->|Regular Mode| B1
207
- A -->|Awk Mode| B1
208
- B1 --> B2
209
- B2 -->|Regular| C1
210
- B2 -->|Awk| C2
211
- C1 --> D1
212
- C2 --> D1
213
- D1 --> D2
214
- D1 --> E1
215
- D2 --> E1
216
- E1 --> E2
217
- E2 --> F[Python Execution]
218
-
219
- style A fill:#e1f5ff
220
- style F fill:#e1ffe1
221
- style D2 fill:#fff4e1
222
- ```
223
-
224
200
  **Key Components:**
225
201
 
226
202
  - **Parser**: Uses [Pest](https://pest.rs/) parser generator with PEG grammar defined in `src/snail.pest`
@@ -265,91 +241,17 @@ Installation per platform:
265
241
 
266
242
  **No Python packages required**: Snail vendors jmespath under `snail.vendor`.
267
243
 
268
- **Rust toolchain** (cargo and rustc)
269
-
270
- Install Rust using [rustup](https://rustup.rs):
271
-
272
- ```bash
273
- curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
274
- ```
275
-
276
- This installs `cargo` (Rust's package manager) and `rustc` (the Rust compiler). After installation, restart your shell or run:
277
-
278
- ```bash
279
- source $HOME/.cargo/env
280
- ```
281
-
282
- Verify installation:
283
-
284
- ```bash
285
- cargo --version # Should show cargo 1.70+
286
- rustc --version # Should show rustc 1.70+
287
- python3 --version # Should show Python 3.10+
288
- ```
289
-
290
- **maturin** (build tool)
291
-
292
- ```bash
293
- pip install maturin
294
- ```
295
-
296
- ### Build and Install
244
+ ### Build, Test, and Install
297
245
 
298
246
  ```bash
299
247
  # Clone the repository
300
248
  git clone https://github.com/sudonym1/snail.git
301
249
  cd snail
302
250
 
303
- # Create and activate a venv (recommended)
304
- python3 -m venv .venv
305
- source .venv/bin/activate # On Windows: .venv\Scripts\activate
306
-
307
- # Build and install into the venv
308
- maturin develop
309
-
310
- # Or build wheels for distribution
311
- maturin build --release
312
- ```
313
-
314
- ### Running Tests
315
-
316
- ```bash
317
- # Run all Rust tests (parser, lowering, awk mode; excludes proptests by default)
318
- cargo test
319
-
320
- # Run tests including property-based tests (proptests)
321
- cargo test --features run-proptests
322
-
323
- # Check code formatting and linting
324
- cargo fmt --check
325
- cargo clippy -- -D warnings
326
-
327
- # Build with all features enabled (required before committing)
328
- cargo build --features run-proptests
329
-
330
- # Run Python CLI tests
331
- python -m pytest python/tests
332
- ```
333
-
334
- **Note on Proptests**: The `snail-proptest` crate contains property-based tests that are skipped by default to keep development iteration fast. Use `--features run-proptests` to run them. Before committing, verify that `cargo build --features run-proptests` compiles successfully.
335
-
336
- ### Troubleshooting
337
-
338
- **Using with virtual environments:**
339
-
340
- Activate the environment before running snail so it uses the same interpreter:
341
-
342
- ```bash
343
- # Create and activate a venv
344
- python3 -m venv myenv
345
- source myenv/bin/activate # On Windows: myenv\Scripts\activate
346
-
347
- # Install and run
348
- pip install snail-lang
349
- snail "import sys; print(sys.prefix)"
251
+ make test
252
+ make install
350
253
  ```
351
254
 
352
- ## 📋 Project Status
353
255
 
354
- See [docs/PLANNING.md](docs/PLANNING.md) for the development roadmap.
256
+ **Note on Proptests**: The `snail-proptest` crate contains property-based tests that are skipped by default to keep development iteration fast.
355
257
 
@@ -13,11 +13,21 @@ in interesting and horrible ways.</h1>
13
13
 
14
14
  **Snail** is a programming language that compiles to Python, combining Python's power with Perl/awk-inspired syntax for quick scripts and one-liners. No more whitespace sensitivity—just curly braces and concise expressions.
15
15
 
16
+ ## Installing Snail
17
+
18
+ Install [uv](https://docs.astral.sh/uv/getting-started/installation/) and then run:
19
+
20
+ ```bash
21
+ uv tool install -p 3.12 snail-lang
22
+ ```
23
+
24
+ That installs the `snail` CLI for your user; try it with `snail "print('hello')"` once the install completes.
25
+
16
26
  ## ✨ What Makes Snail Unique
17
27
 
18
28
  ### Curly Braces, Not Indentation
19
29
 
20
- Write Python logic without worrying about tabs vs spaces:
30
+ Write Python logic without worrying about whitespace:
21
31
 
22
32
  ```snail
23
33
  def process(items) {
@@ -28,69 +38,48 @@ def process(items) {
28
38
  }
29
39
  ```
30
40
 
31
- ### Built-in Subprocess Pipelines
41
+ Note, since it is jarring to write python with semicolons everywhere,
42
+ semicolons are optional. You can separate statements with newlines.
32
43
 
33
- Shell commands are first-class citizens with `$()` capture and `|` piping:
34
-
35
- ```snail
36
- # Capture command output with interpolation
37
- name = "world"
38
- greeting = $(echo hello {name})
44
+ ### Awk Mode
39
45
 
40
- # Pipe data through commands
41
- result = "foo\nbar\nbaz" | $(grep bar) | $(cat -n)
46
+ Process files line-by-line with familiar awk semantics:
42
47
 
43
- # Check command status
44
- @(make build)? # returns exit code on failure instead of raising
48
+ ```snail-awk("5\n4\n3\n2\n1\nbanana\n")
49
+ BEGIN { total = 0 }
50
+ /^[0-9]+/ { total = total + int($1) }
51
+ END { print("Sum:", total); assert total == 15}
45
52
  ```
46
53
 
54
+ Built-in variables: `$l` (line), `$f` (fields), `$n` (line number), `$fn` (per-file line number), `$p` (file path), `$m` (last match).
55
+
56
+
47
57
  ### Compact Error Handling
48
58
 
49
59
  The `?` operator makes error handling terse yet expressive:
50
60
 
51
61
  ```snail
52
- # Swallow exception, get the error object
53
- err = risky_operation()?
62
+ # Swallow exception, return None
63
+ err = risky()?
64
+
65
+ # Swallow exception, return exception object
66
+ err = risky():$e?
54
67
 
55
68
  # Provide a fallback value (exception available as $e)
56
- value = js(data):{}?
57
- details = fetch_url(url):"Error: {$e}"?
69
+ value = js("malformed json"):{}?
70
+ details = fetch_url("foo.com"):"default html"?
71
+ exception_info = fetch_url("example.com"):$e.http_response_code?
58
72
 
59
73
  # Access attributes directly
60
- name = risky()?.__class__.__name__
61
- args = risky()?.args[0]
62
- ```
63
-
64
- ### Regex Literals
65
-
66
- Pattern matching without `import re`:
67
-
68
- ```snail
69
- if email in /^[\w.]+@[\w.]+$/ {
70
- print("Valid email")
71
- }
72
-
73
- # Compiled regex for reuse
74
- pattern = /\d{3}-\d{4}/
75
- match = pattern.search(phone)
74
+ name = risky("")?.__class__.__name__
75
+ args = risky("becomes a list"):[1,2,3]?[0]
76
76
  ```
77
77
 
78
- ### Awk Mode
79
-
80
- Process files line-by-line with familiar awk semantics:
81
-
82
- ```snail
83
- #!/usr/bin/env -S snail --awk -f
84
- BEGIN { total = 0 }
85
- /^[0-9]+/ { total = total + int($f[0]) }
86
- END { print("Sum:", total) }
87
- ```
88
-
89
- Built-in variables: `$l` (line), `$f` (fields), `$n` (line number), `$fn` (per-file line number), `$p` (file path), `$m` (last match).
90
-
91
78
  ### Pipeline Operator
92
79
 
93
- The `|` operator enables data pipelining through pipeline-aware callables:
80
+ The `|` operator enables data pipelining as syntactic sugar for nested
81
+ function calls. `x | y | z` becomes `z(y(x))`. This lets you stay in a
82
+ shell mindset.
94
83
 
95
84
  ```snail
96
85
  # Pipe data to subprocess stdin
@@ -104,8 +93,11 @@ class Doubler {
104
93
  def __call__(self, x) { return x * 2 }
105
94
  }
106
95
  doubled = 21 | Doubler() # yields 42
96
+ ```
107
97
 
108
- # Use placeholders to control where piped values land in calls
98
+ Arbitrary callables make up pipelines, even if they have multiple parameters.
99
+ Snail supports this via placeholders.
100
+ ```snail
109
101
  greeting = "World" | greet("Hello ", _) # greet("Hello ", "World")
110
102
  excited = "World" | greet(_, "!") # greet("World", "!")
111
103
  formal = "World" | greet("Hello ", suffix=_) # greet("Hello ", "World")
@@ -117,99 +109,83 @@ the piped value at that position (including keyword arguments). Only one
117
109
  placeholder is allowed in a piped call. Outside of pipeline calls, `_` remains a
118
110
  normal identifier.
119
111
 
112
+ ### Built-in Subprocess
113
+
114
+ Shell commands are first-class citizens with capturing and non-capturing
115
+ forms.
116
+
117
+ ```snail
118
+ # Capture command output with interpolation
119
+ greeting = $(echo hello {name})
120
+
121
+ # Pipe data through commands
122
+ result = "foo\nbar\nbaz" | $(grep bar) | $(cat -n)
123
+
124
+ # Check command status
125
+ @(make build)? # returns exit code on failure instead of raising
126
+ ```
127
+
128
+
129
+ ### Regex Literals
130
+
131
+ Snail supports first class patterns. Think of them as an infinte set.
132
+
133
+ ```snail
134
+ if bad_email in /^[\w.]+@[\w.]+$/ {
135
+ print("Valid email")
136
+ }
137
+
138
+ # Compiled regex for reuse
139
+ pattern = /\d{3}-\d{4}/
140
+ match = pattern.search(phone)
141
+ ```
142
+
143
+ NOTE: this feature is WIP.
144
+
120
145
  ### JSON Queries with JMESPath
121
146
 
122
147
  Parse and query JSON data with the `js()` function and structured pipeline accessor:
123
148
 
124
149
  ```snail
125
150
  # Parse JSON and query with $[jmespath]
126
- data = js($(curl -s api.example.com/users))
127
- names = data | $[users[*].name]
128
- first_email = data | $[users[0].email]
151
+
152
+ # JSON query with JMESPath
153
+ data = js($(curl -s https://api.github.com/repos/sudonym1/snail))
154
+ counts = data | $[stargazers_count]
129
155
 
130
156
  # Inline parsing and querying
131
- result = js('{"foo": 12}') | $[foo]
157
+ result = js('{{"foo": 12}}') | $[foo]
132
158
 
133
159
  # JSONL parsing returns a list
134
- names = js('{"name": "Ada"}\n{"name": "Lin"}') | $[[*].name]
160
+ names = js('{{"name": "Ada"}}\n{{"name": "Lin"}}') | $[[*].name]
135
161
  ```
136
162
 
137
163
  ### Full Python Interoperability
138
164
 
139
- Snail compiles to Python AST—import any Python module, use any library:
140
-
141
- ```snail
142
- import pandas as pd
143
- from pathlib import Path
144
-
145
- df = pd.read_csv(Path("data.csv"))
146
- filtered = df[df["value"] > 100]
147
- ```
165
+ Snail compiles to Python AST—import any Python module, use any library, in any
166
+ environment. Assuming that you are using Python 3.10 or later.
148
167
 
149
168
  ## 🚀 Quick Start
150
169
 
151
170
  ```bash
152
- # Install from PyPI
153
- pip install snail-lang
171
+ # One-liner: arithmetic + interpolation
172
+ snail 'name="Snail"; print("{name} says: {6 * 7}")'
154
173
 
155
- # Run a one-liner
156
- snail "print('Hello, Snail!')"
174
+ # JSON query with JMESPath
175
+ snail 'js($(curl -s https://api.github.com/repos/sudonym1/snail)) | $[stargazers_count]'
157
176
 
158
- # Execute a script
159
- snail -f script.snail
177
+ # Compact error handling with fallback
178
+ snail 'result = int("oops"):"bad int {$e}"?; print(result)'
160
179
 
161
- # Awk mode for text processing
162
- cat data.txt | snail --awk '/error/ { print($l) }'
180
+ # Regex match and capture
181
+ snail 'm = "user@example.com" in /^[\\w.]+@([\\w.]+)$/; if m { print(m[1]) }'
182
+
183
+ # Awk mode: print line numbers for matches
184
+ rg -n "TODO" README.md | snail --awk '/TODO/ { print("{$n}: {$l}") }'
163
185
  ```
164
186
 
165
187
  ## 🏗️ Architecture
166
188
 
167
- Snail compiles to Python through a multi-stage pipeline:
168
-
169
- ```mermaid
170
- flowchart TB
171
- subgraph Input
172
- A[Snail Source Code]
173
- end
174
-
175
- subgraph Parsing["Parsing (Pest PEG Parser)"]
176
- B1[crates/snail-parser/src/snail.pest<br/>Grammar Definition]
177
- B2[crates/snail-parser/<br/>Parser Implementation]
178
- end
179
-
180
- subgraph AST["Abstract Syntax Tree"]
181
- C1[crates/snail-ast/src/ast.rs<br/>Program AST]
182
- C2[crates/snail-ast/src/awk.rs<br/>AwkProgram AST]
183
- end
184
-
185
- subgraph Lowering["Lowering"]
186
- D1[crates/snail-lower/<br/>AST → Python AST Transform]
187
- D2[python/snail/runtime/<br/>Runtime Helpers]
188
- end
189
-
190
- subgraph Execution
191
- E1[python/snail/cli.py<br/>CLI Interface]
192
- E2[pyo3 extension<br/>in-process exec]
193
- end
194
-
195
- A -->|Regular Mode| B1
196
- A -->|Awk Mode| B1
197
- B1 --> B2
198
- B2 -->|Regular| C1
199
- B2 -->|Awk| C2
200
- C1 --> D1
201
- C2 --> D1
202
- D1 --> D2
203
- D1 --> E1
204
- D2 --> E1
205
- E1 --> E2
206
- E2 --> F[Python Execution]
207
-
208
- style A fill:#e1f5ff
209
- style F fill:#e1ffe1
210
- style D2 fill:#fff4e1
211
- ```
212
-
213
189
  **Key Components:**
214
190
 
215
191
  - **Parser**: Uses [Pest](https://pest.rs/) parser generator with PEG grammar defined in `src/snail.pest`
@@ -254,90 +230,16 @@ Installation per platform:
254
230
 
255
231
  **No Python packages required**: Snail vendors jmespath under `snail.vendor`.
256
232
 
257
- **Rust toolchain** (cargo and rustc)
258
-
259
- Install Rust using [rustup](https://rustup.rs):
260
-
261
- ```bash
262
- curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
263
- ```
264
-
265
- This installs `cargo` (Rust's package manager) and `rustc` (the Rust compiler). After installation, restart your shell or run:
266
-
267
- ```bash
268
- source $HOME/.cargo/env
269
- ```
270
-
271
- Verify installation:
272
-
273
- ```bash
274
- cargo --version # Should show cargo 1.70+
275
- rustc --version # Should show rustc 1.70+
276
- python3 --version # Should show Python 3.10+
277
- ```
278
-
279
- **maturin** (build tool)
280
-
281
- ```bash
282
- pip install maturin
283
- ```
284
-
285
- ### Build and Install
233
+ ### Build, Test, and Install
286
234
 
287
235
  ```bash
288
236
  # Clone the repository
289
237
  git clone https://github.com/sudonym1/snail.git
290
238
  cd snail
291
239
 
292
- # Create and activate a venv (recommended)
293
- python3 -m venv .venv
294
- source .venv/bin/activate # On Windows: .venv\Scripts\activate
295
-
296
- # Build and install into the venv
297
- maturin develop
298
-
299
- # Or build wheels for distribution
300
- maturin build --release
301
- ```
302
-
303
- ### Running Tests
304
-
305
- ```bash
306
- # Run all Rust tests (parser, lowering, awk mode; excludes proptests by default)
307
- cargo test
308
-
309
- # Run tests including property-based tests (proptests)
310
- cargo test --features run-proptests
311
-
312
- # Check code formatting and linting
313
- cargo fmt --check
314
- cargo clippy -- -D warnings
315
-
316
- # Build with all features enabled (required before committing)
317
- cargo build --features run-proptests
318
-
319
- # Run Python CLI tests
320
- python -m pytest python/tests
321
- ```
322
-
323
- **Note on Proptests**: The `snail-proptest` crate contains property-based tests that are skipped by default to keep development iteration fast. Use `--features run-proptests` to run them. Before committing, verify that `cargo build --features run-proptests` compiles successfully.
324
-
325
- ### Troubleshooting
326
-
327
- **Using with virtual environments:**
328
-
329
- Activate the environment before running snail so it uses the same interpreter:
330
-
331
- ```bash
332
- # Create and activate a venv
333
- python3 -m venv myenv
334
- source myenv/bin/activate # On Windows: myenv\Scripts\activate
335
-
336
- # Install and run
337
- pip install snail-lang
338
- snail "import sys; print(sys.prefix)"
240
+ make test
241
+ make install
339
242
  ```
340
243
 
341
- ## 📋 Project Status
342
244
 
343
- See [docs/PLANNING.md](docs/PLANNING.md) for the development roadmap.
245
+ **Note on Proptests**: The `snail-proptest` crate contains property-based tests that are skipped by default to keep development iteration fast.
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "snail-ast"
3
- version = "0.3.8"
3
+ version = "0.3.9"
4
4
  edition = "2024"
5
5
  readme = "README.md"
6
6
 
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "snail-core"
3
- version = "0.3.8"
3
+ version = "0.3.9"
4
4
  edition.workspace = true
5
5
  readme = "README.md"
6
6
 
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "snail-error"
3
- version = "0.3.8"
3
+ version = "0.3.9"
4
4
  edition = "2024"
5
5
  readme = "README.md"
6
6
 
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "snail-lower"
3
- version = "0.3.8"
3
+ version = "0.3.9"
4
4
  edition = "2024"
5
5
  readme = "README.md"
6
6
 
@@ -21,7 +21,7 @@ This crate is the semantic transformation core of the Snail compiler. It takes S
21
21
  ## Snail Feature Transformations
22
22
 
23
23
  - **Compact try operator** (`expr?`): Transformed into `__snail_compact_try(lambda: expr)` call
24
- - **Compact try with fallback** (`expr ? fallback`): Transformed with fallback lambda
24
+ - **Compact try with fallback** (`expr:fallback?`): Transformed with fallback lambda
25
25
  - **Subprocess capture** (`$(cmd)`): Transformed into `__SnailSubprocessCapture(cmd)` instance
26
26
  - **Subprocess status** (`@(cmd)`): Transformed into `__SnailSubprocessStatus(cmd)` instance
27
27
  - **Regex expressions** (`/pattern/`): Transformed into `__snail_regex_compile(pattern)` call
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "snail-parser"
3
- version = "0.3.8"
3
+ version = "0.3.9"
4
4
  edition = "2024"
5
5
  readme = "README.md"
6
6
 
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "snail-python"
3
- version = "0.3.8"
3
+ version = "0.3.9"
4
4
  edition.workspace = true
5
5
 
6
6
  [lib]
@@ -4,7 +4,7 @@ build-backend = "maturin"
4
4
 
5
5
  [project]
6
6
  name = "snail-lang"
7
- version = "0.3.8"
7
+ version = "0.3.9"
8
8
  description = "Snail programming language interpreter"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
File without changes
File without changes