Skip to main content

Snail programming language interpreter

Project description

Snail logo

What do you get when you shove a snake in a shell?

Snail

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.

Installing Snail

Install uv and then run:

uv tool install -p 3.12 snail-lang

That installs the snail CLI for your user; try it with snail "print('hello')" once the install completes.

✨ What Makes Snail Unique

Curly Braces, Not Indentation

Write Python logic without worrying about whitespace:

def process(items) {
    for item in items {
        if item > 0 { print(item) }
        else { continue }
    }
}

Note, since it is jarring to write python with semicolons everywhere, semicolons are optional. You can separate statements with newlines.

Awk Mode

Process files line-by-line with familiar awk semantics:

BEGIN { total = 0 }
/^[0-9]+/ { total = total + int($1) }
END { print("Sum:", total); assert total == 15}

Built-in variables: $0 (line), $1, $2 etc (access fields), $n (line number), $fn (per-file line number), $p (file path), $m (last match).

Compact Error Handling

The ? operator makes error handling terse yet expressive:

# Swallow exception, return None
err = risky()?

# Swallow exception, return exception object
err = risky():$e?

# Provide a fallback value (exception available as $e)
value = js("malformed json"):{"error": "invalid json"}?
details = fetch_url("foo.com"):"default html"?
exception_info = fetch_url("example.com"):$e.http_response_code?

# Access attributes directly
name = risky("")?.__class__.__name__
args = risky("becomes a list"):[1,2,3]?[0]

Destructuring + if let / while let

Unpack tuples and lists directly, including Python-style rest bindings:

x, *xs = [1, 2, 3]

if let [head, *tail] = [1, 2, 3]; head > 0 {
    print(head, tail)
}

if let/while let only enter the block when the destructuring succeeds. A guard after ; lets you add a boolean check that runs after the bindings are created.

Note that this syntax is more powerful than the walrus operator as that does not allow for destructuring.

Pipeline Operator

The | operator enables data pipelining as syntactic sugar for nested function calls. x | y | z becomes z(y(x)). This lets you stay in a shell mindset.

# Pipe data to subprocess stdin
result = "hello\nworld" | $(grep hello)

# Chain multiple transformations
output = "foo\nbar" | $(grep foo) | $(wc -l)

# Custom pipeline handlers
class Doubler {
    def __call__(self, x) { return x * 2 }
}
doubled = 21 | Doubler()  # yields 42

Arbitrary callables make up pipelines, even if they have multiple parameters. Snail supports this via placeholders.

greeting = "World" | greet("Hello ", _)  # greet("Hello ", "World")
excited = "World" | greet(_, "!")        # greet("World", "!")
formal = "World" | greet("Hello ", suffix=_)  # greet("Hello ", "World")

When a pipeline targets a call expression, the left-hand value is passed to the resulting callable. If the call includes a single _ placeholder, Snail substitutes the piped value at that position (including keyword arguments). Only one placeholder is allowed in a piped call. Outside of pipeline calls, _ remains a normal identifier.

Built-in Subprocess

Shell commands are first-class citizens with capturing and non-capturing forms.

# Capture command output with interpolation
greeting = $(echo hello {name})

# Pipe data through commands
result = "foo\nbar\nbaz" | $(grep bar) | $(cat -n)

# Check command status
@(make build)?  # returns exit code on failure instead of raising

Regex Literals

Snail supports first class patterns. Think of them as an infinte set.

if bad_email in /^[\w.]+@[\w.]+$/ {
    print("Valid email")
}

# Compiled regex for reuse
pattern = /\d{3}-\d{4}/
match = pattern.search(phone)
match2 = "555-1212" in pattern

Snail regexes don't return a match object, rather they return a tuple containing all of the match groups, including group 0. Both search and in return the same tuple (or () when there is no match).

JSON Queries with JMESPath

Parse and query JSON data with the js() function and structured pipeline accessor:

# Parse JSON and query with $[jmespath]

# JSON query with JMESPath
data = js($(curl -s https://api.github.com/repos/sudonym1/snail))
counts = data | $[stargazers_count]

# Inline parsing and querying
result = js('{{"foo": 12}}') | $[foo]

# JSONL parsing returns a list
names = js('{{"name": "Ada"}}\n{{"name": "Lin"}}') | $[[*].name]

Full Python Interoperability

Snail compiles to Python AST—import any Python module, use any library, in any environment. Assuming that you are using Python 3.10 or later.

🚀 Quick Start

# One-liner: arithmetic + interpolation
snail 'name="Snail"; print("{name} says: {6 * 7}")'

# JSON query with JMESPath
snail 'js($(curl -s https://api.github.com/repos/sudonym1/snail)) | $[stargazers_count]'

# Compact error handling with fallback
snail 'result = int("oops"):"bad int {$e}"?; print(result)'

# Regex match and capture
snail 'if let [_, user, domain] = "user@example.com" in /^[\w.]+@([\w.]+)$/ { print(domain) }'

# Awk mode: print line numbers for matches
rg -n "TODO" README.md | snail --awk '/TODO/ { print("{$n}: {$0}") }'

📚 Documentation

Documentation is WIP

🔌 Editor Support

Vim/Neovim plugin with syntax highlighting, formatting, and run commands:

Plug 'sudonym1/snail', { 'rtp': 'extras/vim' }

See extras/vim/README.md for details. Tree-sitter grammar available in extras/tree-sitter-snail/.

🛠️ Building from Source

Prerequisites

Python 3.10+ (required at runtime)

Snail runs in-process via a Pyo3 extension module, so it uses the active Python environment.

Installation per platform:

  • Ubuntu/Debian: sudo apt install python3 python3-dev
  • Fedora/RHEL: sudo dnf install python3 python3-devel
  • macOS: brew install python@3.12 (or use the system Python 3)
  • Windows: Download from python.org

Build, Test, and Install

# Clone the repository
git clone https://github.com/sudonym1/snail.git
cd snail

make test
make install

Note on Proptests: The snail-proptest crate contains property-based tests that are skipped by default to keep development iteration fast.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

snail_lang-0.5.2.tar.gz (64.5 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

snail_lang-0.5.2-cp310-abi3-win_amd64.whl (537.7 kB view details)

Uploaded CPython 3.10+Windows x86-64

snail_lang-0.5.2-cp310-abi3-manylinux_2_34_x86_64.whl (4.5 MB view details)

Uploaded CPython 3.10+manylinux: glibc 2.34+ x86-64

snail_lang-0.5.2-cp310-abi3-macosx_11_0_arm64.whl (568.6 kB view details)

Uploaded CPython 3.10+macOS 11.0+ ARM64

File details

Details for the file snail_lang-0.5.2.tar.gz.

File metadata

  • Download URL: snail_lang-0.5.2.tar.gz
  • Upload date:
  • Size: 64.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for snail_lang-0.5.2.tar.gz
Algorithm Hash digest
SHA256 fa2c5cf4f320d9db3d18f2b635b153456ef8c31cfaf27229b560cfd347c821e7
MD5 2ec400b1e2028ccd5bfe5313da5770ae
BLAKE2b-256 3183bba7e89a7ae28a3c7a2fb009aa2c1b3ef6cfa1a87922c124e1e1646de4e5

See more details on using hashes here.

Provenance

The following attestation bundles were made for snail_lang-0.5.2.tar.gz:

Publisher: release.yml on sudonym1/snail

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file snail_lang-0.5.2-cp310-abi3-win_amd64.whl.

File metadata

  • Download URL: snail_lang-0.5.2-cp310-abi3-win_amd64.whl
  • Upload date:
  • Size: 537.7 kB
  • Tags: CPython 3.10+, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for snail_lang-0.5.2-cp310-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 c1807c396a2282fc3a77d92faf7b7e823c22897632e4e1e0a7e14295dcb5248d
MD5 49200bf5e6ab8dd289c1bb62f424aaff
BLAKE2b-256 e7f992ada0265a5e82e7103871a611f3a76a3faab3de5e25df5ae815e4599d50

See more details on using hashes here.

Provenance

The following attestation bundles were made for snail_lang-0.5.2-cp310-abi3-win_amd64.whl:

Publisher: release.yml on sudonym1/snail

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file snail_lang-0.5.2-cp310-abi3-manylinux_2_34_x86_64.whl.

File metadata

File hashes

Hashes for snail_lang-0.5.2-cp310-abi3-manylinux_2_34_x86_64.whl
Algorithm Hash digest
SHA256 b5397b56d4f4e3386fca2a074f5defb54e4fca90bc06b215a5de0fa9783f5842
MD5 2e0b17f5f550ccb00df3137739352b08
BLAKE2b-256 39408076361c5f17c40b186bf2595df6cb5dc9cea3ed9c7fd2b2f6f727e0263e

See more details on using hashes here.

Provenance

The following attestation bundles were made for snail_lang-0.5.2-cp310-abi3-manylinux_2_34_x86_64.whl:

Publisher: release.yml on sudonym1/snail

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file snail_lang-0.5.2-cp310-abi3-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for snail_lang-0.5.2-cp310-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 7b246762adc9044ac2894538df4f207edec338d4e3a52547e9cddbf459e14fcc
MD5 c6325b901ff488ec5b7c210670156f5c
BLAKE2b-256 2d54115169fd1f6a48419fb2104d49e46edb78c9635789214f67c84838b62060

See more details on using hashes here.

Provenance

The following attestation bundles were made for snail_lang-0.5.2-cp310-abi3-macosx_11_0_arm64.whl:

Publisher: release.yml on sudonym1/snail

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page