snail-lang 0.3.8__tar.gz → 0.4.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.
Files changed (64) hide show
  1. {snail_lang-0.3.8 → snail_lang-0.4.0}/Cargo.lock +7 -7
  2. {snail_lang-0.3.8 → snail_lang-0.4.0}/PKG-INFO +97 -202
  3. snail_lang-0.4.0/README.md +237 -0
  4. {snail_lang-0.3.8 → snail_lang-0.4.0}/crates/snail-ast/Cargo.toml +1 -1
  5. {snail_lang-0.3.8 → snail_lang-0.4.0}/crates/snail-core/Cargo.toml +1 -1
  6. {snail_lang-0.3.8 → snail_lang-0.4.0}/crates/snail-error/Cargo.toml +1 -1
  7. {snail_lang-0.3.8 → snail_lang-0.4.0}/crates/snail-lower/Cargo.toml +1 -1
  8. {snail_lang-0.3.8 → snail_lang-0.4.0}/crates/snail-lower/README.md +3 -3
  9. {snail_lang-0.3.8 → snail_lang-0.4.0}/crates/snail-lower/src/awk.rs +2 -2
  10. {snail_lang-0.3.8 → snail_lang-0.4.0}/crates/snail-lower/src/constants.rs +0 -4
  11. {snail_lang-0.3.8 → snail_lang-0.4.0}/crates/snail-parser/Cargo.toml +1 -1
  12. {snail_lang-0.3.8 → snail_lang-0.4.0}/crates/snail-parser/src/lib.rs +1 -1
  13. {snail_lang-0.3.8 → snail_lang-0.4.0}/crates/snail-parser/src/snail.pest +1 -1
  14. {snail_lang-0.3.8 → snail_lang-0.4.0}/crates/snail-parser/tests/errors.rs +2 -2
  15. {snail_lang-0.3.8 → snail_lang-0.4.0}/crates/snail-python/Cargo.toml +1 -1
  16. {snail_lang-0.3.8 → snail_lang-0.4.0}/pyproject.toml +4 -1
  17. {snail_lang-0.3.8 → snail_lang-0.4.0}/python/snail/runtime/structured_accessor.py +1 -1
  18. snail_lang-0.3.8/README.md +0 -343
  19. snail_lang-0.3.8/python/snail/vendor/__init__.py +0 -0
  20. snail_lang-0.3.8/python/snail/vendor/jmespath/LICENSE +0 -21
  21. snail_lang-0.3.8/python/snail/vendor/jmespath/__init__.py +0 -12
  22. snail_lang-0.3.8/python/snail/vendor/jmespath/ast.py +0 -90
  23. snail_lang-0.3.8/python/snail/vendor/jmespath/compat.py +0 -19
  24. snail_lang-0.3.8/python/snail/vendor/jmespath/exceptions.py +0 -137
  25. snail_lang-0.3.8/python/snail/vendor/jmespath/functions.py +0 -366
  26. snail_lang-0.3.8/python/snail/vendor/jmespath/lexer.py +0 -258
  27. snail_lang-0.3.8/python/snail/vendor/jmespath/parser.py +0 -526
  28. snail_lang-0.3.8/python/snail/vendor/jmespath/visitor.py +0 -329
  29. {snail_lang-0.3.8 → snail_lang-0.4.0}/Cargo.toml +0 -0
  30. {snail_lang-0.3.8 → snail_lang-0.4.0}/LICENSE +0 -0
  31. {snail_lang-0.3.8 → snail_lang-0.4.0}/crates/snail-ast/README.md +0 -0
  32. {snail_lang-0.3.8 → snail_lang-0.4.0}/crates/snail-ast/src/ast.rs +0 -0
  33. {snail_lang-0.3.8 → snail_lang-0.4.0}/crates/snail-ast/src/awk.rs +0 -0
  34. {snail_lang-0.3.8 → snail_lang-0.4.0}/crates/snail-ast/src/lib.rs +0 -0
  35. {snail_lang-0.3.8 → snail_lang-0.4.0}/crates/snail-core/README.md +0 -0
  36. {snail_lang-0.3.8 → snail_lang-0.4.0}/crates/snail-core/src/lib.rs +0 -0
  37. {snail_lang-0.3.8 → snail_lang-0.4.0}/crates/snail-error/README.md +0 -0
  38. {snail_lang-0.3.8 → snail_lang-0.4.0}/crates/snail-error/src/lib.rs +0 -0
  39. {snail_lang-0.3.8 → snail_lang-0.4.0}/crates/snail-lower/src/expr.rs +0 -0
  40. {snail_lang-0.3.8 → snail_lang-0.4.0}/crates/snail-lower/src/helpers.rs +0 -0
  41. {snail_lang-0.3.8 → snail_lang-0.4.0}/crates/snail-lower/src/lib.rs +0 -0
  42. {snail_lang-0.3.8 → snail_lang-0.4.0}/crates/snail-lower/src/operators.rs +0 -0
  43. {snail_lang-0.3.8 → snail_lang-0.4.0}/crates/snail-lower/src/program.rs +0 -0
  44. {snail_lang-0.3.8 → snail_lang-0.4.0}/crates/snail-lower/src/py_ast.rs +0 -0
  45. {snail_lang-0.3.8 → snail_lang-0.4.0}/crates/snail-lower/src/stmt.rs +0 -0
  46. {snail_lang-0.3.8 → snail_lang-0.4.0}/crates/snail-parser/README.md +0 -0
  47. {snail_lang-0.3.8 → snail_lang-0.4.0}/crates/snail-parser/src/awk.rs +0 -0
  48. {snail_lang-0.3.8 → snail_lang-0.4.0}/crates/snail-parser/src/expr.rs +0 -0
  49. {snail_lang-0.3.8 → snail_lang-0.4.0}/crates/snail-parser/src/literal.rs +0 -0
  50. {snail_lang-0.3.8 → snail_lang-0.4.0}/crates/snail-parser/src/stmt.rs +0 -0
  51. {snail_lang-0.3.8 → snail_lang-0.4.0}/crates/snail-parser/src/string.rs +0 -0
  52. {snail_lang-0.3.8 → snail_lang-0.4.0}/crates/snail-parser/src/util.rs +0 -0
  53. {snail_lang-0.3.8 → snail_lang-0.4.0}/crates/snail-parser/tests/common.rs +0 -0
  54. {snail_lang-0.3.8 → snail_lang-0.4.0}/crates/snail-parser/tests/parser.rs +0 -0
  55. {snail_lang-0.3.8 → snail_lang-0.4.0}/crates/snail-parser/tests/statements.rs +0 -0
  56. {snail_lang-0.3.8 → snail_lang-0.4.0}/crates/snail-parser/tests/syntax_expressions.rs +0 -0
  57. {snail_lang-0.3.8 → snail_lang-0.4.0}/crates/snail-parser/tests/syntax_strings.rs +0 -0
  58. {snail_lang-0.3.8 → snail_lang-0.4.0}/crates/snail-python/src/lib.rs +0 -0
  59. {snail_lang-0.3.8 → snail_lang-0.4.0}/python/snail/__init__.py +0 -0
  60. {snail_lang-0.3.8 → snail_lang-0.4.0}/python/snail/cli.py +0 -0
  61. {snail_lang-0.3.8 → snail_lang-0.4.0}/python/snail/runtime/__init__.py +0 -0
  62. {snail_lang-0.3.8 → snail_lang-0.4.0}/python/snail/runtime/compact_try.py +0 -0
  63. {snail_lang-0.3.8 → snail_lang-0.4.0}/python/snail/runtime/regex.py +0 -0
  64. {snail_lang-0.3.8 → snail_lang-0.4.0}/python/snail/runtime/subprocess.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.4.0"
489
489
 
490
490
  [[package]]
491
491
  name = "snail-core"
492
- version = "0.3.8"
492
+ version = "0.4.0"
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.4.0"
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.4.0"
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.4.0"
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.4.0"
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.4.0"
544
544
  dependencies = [
545
545
  "pyo3",
546
546
  "snail-core",
@@ -1,6 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: snail-lang
3
- Version: 0.3.8
3
+ Version: 0.4.0
4
+ Requires-Dist: jmespath>=1.0.1
4
5
  Requires-Dist: maturin>=1.5 ; extra == 'dev'
5
6
  Requires-Dist: pytest ; extra == 'dev'
6
7
  Provides-Extra: dev
@@ -12,23 +13,27 @@ Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
12
13
  <p align="center">
13
14
  <img src="logo.png" alt="Snail logo" width="200">
14
15
  </p>
16
+ <p align="center"><em>What do you get when you shove a snake in a shell?</em></p>
15
17
 
16
18
  <h1 align="center">Snail</h1>
17
- <p align="center"><em>What do you get when you shove a snake in a shell?</em></p>
18
19
 
19
- <h1>Snail, while I hope it is useful to myself and others, is my attempt at
20
- improving my knowledge of AI code developement. Things are probably broken
21
- in interesting and horrible ways.</h1>
20
+ **Snail** is a programming language that compiles to Python, combining Python's familiarity and extensive libraries with Perl/awk-inspired syntax for quick scripts and one-liners.
21
+
22
+ ## Installing Snail
23
+
24
+ Install [uv](https://docs.astral.sh/uv/getting-started/installation/) and then run:
22
25
 
23
- ---
26
+ ```bash
27
+ uv tool install -p 3.12 snail-lang
28
+ ```
24
29
 
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.
30
+ That installs the `snail` CLI for your user; try it with `snail "print('hello')"` once the install completes.
26
31
 
27
32
  ## ✨ What Makes Snail Unique
28
33
 
29
34
  ### Curly Braces, Not Indentation
30
35
 
31
- Write Python logic without worrying about tabs vs spaces:
36
+ Write Python logic without worrying about whitespace:
32
37
 
33
38
  ```snail
34
39
  def process(items) {
@@ -39,69 +44,48 @@ def process(items) {
39
44
  }
40
45
  ```
41
46
 
42
- ### Built-in Subprocess Pipelines
47
+ Note, since it is jarring to write python with semicolons everywhere,
48
+ semicolons are optional. You can separate statements with newlines.
43
49
 
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})
50
+ ### Awk Mode
50
51
 
51
- # Pipe data through commands
52
- result = "foo\nbar\nbaz" | $(grep bar) | $(cat -n)
52
+ Process files line-by-line with familiar awk semantics:
53
53
 
54
- # Check command status
55
- @(make build)? # returns exit code on failure instead of raising
54
+ ```snail-awk("5\n4\n3\n2\n1\nbanana\n")
55
+ BEGIN { total = 0 }
56
+ /^[0-9]+/ { total = total + int($1) }
57
+ END { print("Sum:", total); assert total == 15}
56
58
  ```
57
59
 
60
+ Built-in variables: `$0` (line), `$1`, `$2` etc (access fields), `$n` (line number), `$fn` (per-file line number), `$p` (file path), `$m` (last match).
61
+
62
+
58
63
  ### Compact Error Handling
59
64
 
60
65
  The `?` operator makes error handling terse yet expressive:
61
66
 
62
67
  ```snail
63
- # Swallow exception, get the error object
64
- err = risky_operation()?
68
+ # Swallow exception, return None
69
+ err = risky()?
70
+
71
+ # Swallow exception, return exception object
72
+ err = risky():$e?
65
73
 
66
74
  # Provide a fallback value (exception available as $e)
67
- value = js(data):{}?
68
- details = fetch_url(url):"Error: {$e}"?
75
+ value = js("malformed json"):{}?
76
+ details = fetch_url("foo.com"):"default html"?
77
+ exception_info = fetch_url("example.com"):$e.http_response_code?
69
78
 
70
79
  # Access attributes directly
71
- name = risky()?.__class__.__name__
72
- args = risky()?.args[0]
80
+ name = risky("")?.__class__.__name__
81
+ args = risky("becomes a list"):[1,2,3]?[0]
73
82
  ```
74
83
 
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)
87
- ```
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
84
  ### Pipeline Operator
103
85
 
104
- The `|` operator enables data pipelining through pipeline-aware callables:
86
+ The `|` operator enables data pipelining as syntactic sugar for nested
87
+ function calls. `x | y | z` becomes `z(y(x))`. This lets you stay in a
88
+ shell mindset.
105
89
 
106
90
  ```snail
107
91
  # Pipe data to subprocess stdin
@@ -115,8 +99,11 @@ class Doubler {
115
99
  def __call__(self, x) { return x * 2 }
116
100
  }
117
101
  doubled = 21 | Doubler() # yields 42
102
+ ```
118
103
 
119
- # Use placeholders to control where piped values land in calls
104
+ Arbitrary callables make up pipelines, even if they have multiple parameters.
105
+ Snail supports this via placeholders.
106
+ ```snail
120
107
  greeting = "World" | greet("Hello ", _) # greet("Hello ", "World")
121
108
  excited = "World" | greet(_, "!") # greet("World", "!")
122
109
  formal = "World" | greet("Hello ", suffix=_) # greet("Hello ", "World")
@@ -128,99 +115,83 @@ the piped value at that position (including keyword arguments). Only one
128
115
  placeholder is allowed in a piped call. Outside of pipeline calls, `_` remains a
129
116
  normal identifier.
130
117
 
118
+ ### Built-in Subprocess
119
+
120
+ Shell commands are first-class citizens with capturing and non-capturing
121
+ forms.
122
+
123
+ ```snail
124
+ # Capture command output with interpolation
125
+ greeting = $(echo hello {name})
126
+
127
+ # Pipe data through commands
128
+ result = "foo\nbar\nbaz" | $(grep bar) | $(cat -n)
129
+
130
+ # Check command status
131
+ @(make build)? # returns exit code on failure instead of raising
132
+ ```
133
+
134
+
135
+ ### Regex Literals
136
+
137
+ Snail supports first class patterns. Think of them as an infinte set.
138
+
139
+ ```snail
140
+ if bad_email in /^[\w.]+@[\w.]+$/ {
141
+ print("Valid email")
142
+ }
143
+
144
+ # Compiled regex for reuse
145
+ pattern = /\d{3}-\d{4}/
146
+ match = pattern.search(phone)
147
+ ```
148
+
149
+ NOTE: this feature is WIP.
150
+
131
151
  ### JSON Queries with JMESPath
132
152
 
133
153
  Parse and query JSON data with the `js()` function and structured pipeline accessor:
134
154
 
135
155
  ```snail
136
156
  # 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]
157
+
158
+ # JSON query with JMESPath
159
+ data = js($(curl -s https://api.github.com/repos/sudonym1/snail))
160
+ counts = data | $[stargazers_count]
140
161
 
141
162
  # Inline parsing and querying
142
- result = js('{"foo": 12}') | $[foo]
163
+ result = js('{{"foo": 12}}') | $[foo]
143
164
 
144
165
  # JSONL parsing returns a list
145
- names = js('{"name": "Ada"}\n{"name": "Lin"}') | $[[*].name]
166
+ names = js('{{"name": "Ada"}}\n{{"name": "Lin"}}') | $[[*].name]
146
167
  ```
147
168
 
148
169
  ### Full Python Interoperability
149
170
 
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
- ```
171
+ Snail compiles to Python AST—import any Python module, use any library, in any
172
+ environment. Assuming that you are using Python 3.10 or later.
159
173
 
160
174
  ## 🚀 Quick Start
161
175
 
162
176
  ```bash
163
- # Install from PyPI
164
- pip install snail-lang
177
+ # One-liner: arithmetic + interpolation
178
+ snail 'name="Snail"; print("{name} says: {6 * 7}")'
179
+
180
+ # JSON query with JMESPath
181
+ snail 'js($(curl -s https://api.github.com/repos/sudonym1/snail)) | $[stargazers_count]'
165
182
 
166
- # Run a one-liner
167
- snail "print('Hello, Snail!')"
183
+ # Compact error handling with fallback
184
+ snail 'result = int("oops"):"bad int {$e}"?; print(result)'
168
185
 
169
- # Execute a script
170
- snail -f script.snail
186
+ # Regex match and capture
187
+ snail 'm = "user@example.com" in /^[\\w.]+@([\\w.]+)$/; if m { print(m[1]) }'
171
188
 
172
- # Awk mode for text processing
173
- cat data.txt | snail --awk '/error/ { print($l) }'
189
+ # Awk mode: print line numbers for matches
190
+ rg -n "TODO" README.md | snail --awk '/TODO/ { print("{$n}: {$0}") }'
174
191
  ```
175
192
 
176
193
  ## 🏗️ Architecture
177
194
 
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
195
  **Key Components:**
225
196
 
226
197
  - **Parser**: Uses [Pest](https://pest.rs/) parser generator with PEG grammar defined in `src/snail.pest`
@@ -263,93 +234,17 @@ Installation per platform:
263
234
  - **macOS**: `brew install python@3.12` (or use the system Python 3)
264
235
  - **Windows**: Download from [python.org](https://www.python.org/downloads/)
265
236
 
266
- **No Python packages required**: Snail vendors jmespath under `snail.vendor`.
267
-
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
237
+ ### Build, Test, and Install
297
238
 
298
239
  ```bash
299
240
  # Clone the repository
300
241
  git clone https://github.com/sudonym1/snail.git
301
242
  cd snail
302
243
 
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)"
244
+ make test
245
+ make install
350
246
  ```
351
247
 
352
- ## 📋 Project Status
353
248
 
354
- See [docs/PLANNING.md](docs/PLANNING.md) for the development roadmap.
249
+ **Note on Proptests**: The `snail-proptest` crate contains property-based tests that are skipped by default to keep development iteration fast.
355
250
 
@@ -0,0 +1,237 @@
1
+ <p align="center">
2
+ <img src="logo.png" alt="Snail logo" width="200">
3
+ </p>
4
+ <p align="center"><em>What do you get when you shove a snake in a shell?</em></p>
5
+
6
+ <h1 align="center">Snail</h1>
7
+
8
+ **Snail** is a programming language that compiles to Python, combining Python's familiarity and extensive libraries with Perl/awk-inspired syntax for quick scripts and one-liners.
9
+
10
+ ## Installing Snail
11
+
12
+ Install [uv](https://docs.astral.sh/uv/getting-started/installation/) and then run:
13
+
14
+ ```bash
15
+ uv tool install -p 3.12 snail-lang
16
+ ```
17
+
18
+ That installs the `snail` CLI for your user; try it with `snail "print('hello')"` once the install completes.
19
+
20
+ ## ✨ What Makes Snail Unique
21
+
22
+ ### Curly Braces, Not Indentation
23
+
24
+ Write Python logic without worrying about whitespace:
25
+
26
+ ```snail
27
+ def process(items) {
28
+ for item in items {
29
+ if item > 0 { print(item) }
30
+ else { continue }
31
+ }
32
+ }
33
+ ```
34
+
35
+ Note, since it is jarring to write python with semicolons everywhere,
36
+ semicolons are optional. You can separate statements with newlines.
37
+
38
+ ### Awk Mode
39
+
40
+ Process files line-by-line with familiar awk semantics:
41
+
42
+ ```snail-awk("5\n4\n3\n2\n1\nbanana\n")
43
+ BEGIN { total = 0 }
44
+ /^[0-9]+/ { total = total + int($1) }
45
+ END { print("Sum:", total); assert total == 15}
46
+ ```
47
+
48
+ Built-in variables: `$0` (line), `$1`, `$2` etc (access fields), `$n` (line number), `$fn` (per-file line number), `$p` (file path), `$m` (last match).
49
+
50
+
51
+ ### Compact Error Handling
52
+
53
+ The `?` operator makes error handling terse yet expressive:
54
+
55
+ ```snail
56
+ # Swallow exception, return None
57
+ err = risky()?
58
+
59
+ # Swallow exception, return exception object
60
+ err = risky():$e?
61
+
62
+ # Provide a fallback value (exception available as $e)
63
+ value = js("malformed json"):{}?
64
+ details = fetch_url("foo.com"):"default html"?
65
+ exception_info = fetch_url("example.com"):$e.http_response_code?
66
+
67
+ # Access attributes directly
68
+ name = risky("")?.__class__.__name__
69
+ args = risky("becomes a list"):[1,2,3]?[0]
70
+ ```
71
+
72
+ ### Pipeline Operator
73
+
74
+ The `|` operator enables data pipelining as syntactic sugar for nested
75
+ function calls. `x | y | z` becomes `z(y(x))`. This lets you stay in a
76
+ shell mindset.
77
+
78
+ ```snail
79
+ # Pipe data to subprocess stdin
80
+ result = "hello\nworld" | $(grep hello)
81
+
82
+ # Chain multiple transformations
83
+ output = "foo\nbar" | $(grep foo) | $(wc -l)
84
+
85
+ # Custom pipeline handlers
86
+ class Doubler {
87
+ def __call__(self, x) { return x * 2 }
88
+ }
89
+ doubled = 21 | Doubler() # yields 42
90
+ ```
91
+
92
+ Arbitrary callables make up pipelines, even if they have multiple parameters.
93
+ Snail supports this via placeholders.
94
+ ```snail
95
+ greeting = "World" | greet("Hello ", _) # greet("Hello ", "World")
96
+ excited = "World" | greet(_, "!") # greet("World", "!")
97
+ formal = "World" | greet("Hello ", suffix=_) # greet("Hello ", "World")
98
+ ```
99
+
100
+ When a pipeline targets a call expression, the left-hand value is passed to the
101
+ resulting callable. If the call includes a single `_` placeholder, Snail substitutes
102
+ the piped value at that position (including keyword arguments). Only one
103
+ placeholder is allowed in a piped call. Outside of pipeline calls, `_` remains a
104
+ normal identifier.
105
+
106
+ ### Built-in Subprocess
107
+
108
+ Shell commands are first-class citizens with capturing and non-capturing
109
+ forms.
110
+
111
+ ```snail
112
+ # Capture command output with interpolation
113
+ greeting = $(echo hello {name})
114
+
115
+ # Pipe data through commands
116
+ result = "foo\nbar\nbaz" | $(grep bar) | $(cat -n)
117
+
118
+ # Check command status
119
+ @(make build)? # returns exit code on failure instead of raising
120
+ ```
121
+
122
+
123
+ ### Regex Literals
124
+
125
+ Snail supports first class patterns. Think of them as an infinte set.
126
+
127
+ ```snail
128
+ if bad_email in /^[\w.]+@[\w.]+$/ {
129
+ print("Valid email")
130
+ }
131
+
132
+ # Compiled regex for reuse
133
+ pattern = /\d{3}-\d{4}/
134
+ match = pattern.search(phone)
135
+ ```
136
+
137
+ NOTE: this feature is WIP.
138
+
139
+ ### JSON Queries with JMESPath
140
+
141
+ Parse and query JSON data with the `js()` function and structured pipeline accessor:
142
+
143
+ ```snail
144
+ # Parse JSON and query with $[jmespath]
145
+
146
+ # JSON query with JMESPath
147
+ data = js($(curl -s https://api.github.com/repos/sudonym1/snail))
148
+ counts = data | $[stargazers_count]
149
+
150
+ # Inline parsing and querying
151
+ result = js('{{"foo": 12}}') | $[foo]
152
+
153
+ # JSONL parsing returns a list
154
+ names = js('{{"name": "Ada"}}\n{{"name": "Lin"}}') | $[[*].name]
155
+ ```
156
+
157
+ ### Full Python Interoperability
158
+
159
+ Snail compiles to Python AST—import any Python module, use any library, in any
160
+ environment. Assuming that you are using Python 3.10 or later.
161
+
162
+ ## 🚀 Quick Start
163
+
164
+ ```bash
165
+ # One-liner: arithmetic + interpolation
166
+ snail 'name="Snail"; print("{name} says: {6 * 7}")'
167
+
168
+ # JSON query with JMESPath
169
+ snail 'js($(curl -s https://api.github.com/repos/sudonym1/snail)) | $[stargazers_count]'
170
+
171
+ # Compact error handling with fallback
172
+ snail 'result = int("oops"):"bad int {$e}"?; print(result)'
173
+
174
+ # Regex match and capture
175
+ snail 'm = "user@example.com" in /^[\\w.]+@([\\w.]+)$/; if m { print(m[1]) }'
176
+
177
+ # Awk mode: print line numbers for matches
178
+ rg -n "TODO" README.md | snail --awk '/TODO/ { print("{$n}: {$0}") }'
179
+ ```
180
+
181
+ ## 🏗️ Architecture
182
+
183
+ **Key Components:**
184
+
185
+ - **Parser**: Uses [Pest](https://pest.rs/) parser generator with PEG grammar defined in `src/snail.pest`
186
+ - **AST**: Separate representations for regular Snail (`Program`) and awk mode (`AwkProgram`) with source spans for error reporting
187
+ - **Lowering**: Transforms Snail AST into Python AST, emitting helper calls backed by `snail.runtime`
188
+ - `?` operator → `__snail_compact_try`
189
+ - `$(cmd)` subprocess capture → `__SnailSubprocessCapture`
190
+ - `@(cmd)` subprocess status → `__SnailSubprocessStatus`
191
+ - Regex literals → `__snail_regex_search` and `__snail_regex_compile`
192
+ - **Execution**: Compiles Python AST directly for in-process execution
193
+ - **CLI**: Python wrapper (`python/snail/cli.py`) that executes via the extension module
194
+
195
+ ## 📚 Documentation
196
+
197
+ - **[Language Reference](docs/REFERENCE.md)** — Complete syntax and semantics
198
+ - **[examples/all_syntax.snail](examples/all_syntax.snail)** — Every feature in one file
199
+ - **[examples/awk.snail](examples/awk.snail)** — Awk mode examples
200
+
201
+ ## 🔌 Editor Support
202
+
203
+ Vim/Neovim plugin with syntax highlighting, formatting, and run commands:
204
+
205
+ ```vim
206
+ Plug 'sudonym1/snail', { 'rtp': 'extras/vim' }
207
+ ```
208
+
209
+ See [extras/vim/README.md](extras/vim/README.md) for details. Tree-sitter grammar available in `extras/tree-sitter-snail/`.
210
+
211
+ ## 🛠️ Building from Source
212
+
213
+ ### Prerequisites
214
+
215
+ **Python 3.10+** (required at runtime)
216
+
217
+ Snail runs in-process via a Pyo3 extension module, so it uses the active Python environment.
218
+
219
+ Installation per platform:
220
+ - **Ubuntu/Debian**: `sudo apt install python3 python3-dev`
221
+ - **Fedora/RHEL**: `sudo dnf install python3 python3-devel`
222
+ - **macOS**: `brew install python@3.12` (or use the system Python 3)
223
+ - **Windows**: Download from [python.org](https://www.python.org/downloads/)
224
+
225
+ ### Build, Test, and Install
226
+
227
+ ```bash
228
+ # Clone the repository
229
+ git clone https://github.com/sudonym1/snail.git
230
+ cd snail
231
+
232
+ make test
233
+ make install
234
+ ```
235
+
236
+
237
+ **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.4.0"
4
4
  edition = "2024"
5
5
  readme = "README.md"
6
6