Skip to main content

as

One Python, four syntaxes. as converts Python source between the indentation skin you know and three it can wear: C-style braces, Lisp-style S-expressions, and a Rust-flavored vocabulary over the brace skin. Same semantics, different look — hence the name: as lisp, as brace, as rust, as py.

def quicksort(items):                  def quicksort(items) {
    if len(items) <= 1:                    if len(items) <= 1 {
        return items                           return items;
    pivot, *rest = items;                }
    lo = [x for x in rest                pivot, *rest = items;
         if x < pivot]                   lo = [x for x in rest if x < pivot];
    hi = [x for x in rest                hi = [x for x in rest if x >= pivot];
         if x >= pivot]                  return quicksort(lo) + [pivot] + quicksort(hi);
    return quicksort(lo) + [pivot]    }
        + quicksort(hi)
(defn quicksort (items)
  (if (<= (len items) 1) (return items))
  (set! '(pivot (* rest)) items)
  (set! lo (list-comp x (for [x in rest]) (when (< x pivot))))
  (set! hi (list-comp x (for [x in rest]) (when (>= x pivot))))
  (return (+ (+ (quicksort lo) [pivot]) (quicksort hi))))
fn quicksort(items) {
    if len(items) <= 1 {
        return items;
    }
    let pivot, *rest = items;
    let lo = [x for x in rest if x < pivot];
    let hi = [x for x in rest if x >= pivot];
    return quicksort(lo) + [pivot] + quicksort(hi);
}

Why

"Braces or indentation" is one of the oldest holy wars in programming. as does not pick a side — it makes the question meaningless. Write in whichever skin you like, read in whichever skin you like, and argue about taste instead of syntax. The round trip is exact: converting py -> skin -> py yields an AST identical to the original (tested on the whole as codebase and 40 stdlib modules, see Tests).

Install & use

pip install like-a
as lisp foo.py            # Python -> S-expressions (stdout)
as brace foo.py -o foo.cpy
as rust foo.py -o foo.rrs
as py foo.lpy             # S-expressions -> Python
cat foo.py | as brace - --from py

Input style is detected from the extension: .py, .cpy (braces), .lpy (lisp), .rrs (rust). Use --from to override.

The importable package is aslang (from aslang import convert) because as is a Python keyword. The CLI keeps the two-letter name (as lisp, as brace) — same phrase as like a lisp; the distribution on PyPI is like-a, since PyPI forbids Python keywords as project names.

The four skins

skin extension blocks collections
py .py indentation (), [], {}, {k: v}
brace .cpy if cond { ... } unchanged (Python expressions)
rust .rrs if cond { ... } unchanged; fn, let, else if
lisp .lpy (if cond ...) [...] list, '(...) tuple, {(k v)} dict, #{...} set

The lisp skin is more than a re-bracketing: operators go prefix ((+ a (* b c))), methods become calls ((len items), (items.append x)), comprehensions get for/when clauses, match cases carry their patterns as data. The brace skin stays deliberately close to Python — only the block structure changes. The rust skin is the brace skin plus surface vocabulary: def prints as fn, plain assignments gain let, elif becomes else if. Cosmetic Rust, not Rust — types stay Python's, print stays print, class and async pass through with Python keywords intact.

lpy form reference (selected)

(defn name (params...) [-> ret] body...)     (set! target value)
(for [x in iter] body... [(else ...)])       (while cond body...)
(if test then... [(elif ...)...] [(else ...)])
(with [(expr as name) ...] body...)
(try body... (except Type [as e] h...)...)
(match subject (case pattern [(when guard)] body...))
patterns: literal | name | _ | (as pat name) | (or p...) | [p *rest]
          {(key pat) ... (** rest)} | (Cls p... (kws (attr pat)...))
(list-comp elt (for [x in xs]) (when cond))
(fstr "x = " (fv value) "!")

Correctness

  • py <-> brace and py <-> rust convert text directly on the token stream: comments, formatting and even docstrings survive byte-for-byte outside the changed block lines (rust adds only the fn/let/else if vocabulary on top).
  • anything <-> lisp goes through the ast module: the reader builds a real AST, the printer walks it. Comments are preserved best-effort (dropped by the ast, re-attached by line numbers).
  • Round-trip proof: ast.dump(ast.parse(src)) == ast.dump(parse(convert( convert(src, py, skin), skin, py))) for every corpus file.

Compared

bython Hy Nim as
direction -> py -> py AST own lang any -> any
braces skin ✓ — ✓ ✓
S-expression skin — ✓ (own) — ✓
round-trip guaranteed — — — ✓ (AST-equal)
Python semantics ✓ mostly no ✓ exactly

Limitations

  • match is supported in all skins; typing.TypeAlias statements and try*-era syntax are not yet in the lisp skin.
  • The lisp reader accepts a fixed set of special forms; arbitrary macro machinery is out of scope (this is a converter, not a language).
  • A case body that begins with a call to a function literally named when needs parentheses discipline (the (when ...) guard marker wins).

Possible next

The rust skin (0.1.1) started as a joke feature request in the article comments; the C indentation skin is the serious one, and it is deliberately out of scope for now: in C, braces are load-bearing (enum {A, B}, aggregate initializers, GNU ({ ... }) statements), so separating block braces from literal braces needs a real parser (libclang/tree-sitter), not token rewriting - and the round trip would be AST-level, not byte-level. Demand cuts both ways: some like everything about Nim except its Python-style indentation, and Nim has no brace form (its braces are taken by set/table literals and pragmas)

  • so skins could run in both directions. No slight to Nim: braces-for-data is a design choice, the canonical indentation form stays canonical - skins would add contributors, not churn. Interest-gated: if people ask for it, it may happen.

Note on the name

Yes, as shadows GNU as (the assembler) if ~/.local/bin precedes /usr/bin in your PATH. gcc invokes as via PATH — shell aliases don't help. If you build C/C++, either keep /usr/bin ahead of ~/.local/bin for build shells, pass -B /usr/bin to gcc, or pip uninstall like-a (C beats curly braces sometimes). The two-letter namespace was simply too good to leave unused — import json as j, as lisp foo.py.

Development

pip install -e .
pytest                  # 138 tests: unit + stdlib corpus + self-application

The tool digests its own source in the test suite: every module of aslang is converted to every skin, back, and the result must be AST-identical — and must still execute.

MIT license.

Metadata

Release files for like-a 0.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for like-a 0.1.1
File Size Uploaded Provenance
like_a-0.1.1.tar.gz 31.6 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for like-a 0.1.1
File Interpreter ABI Platform
like_a-0.1.1-py3-none-any.whl Python 3 none any Details

Total release size: 59.4 kB

Release files / like_a-0.1.1.tar.gz

Download URL like_a-0.1.1.tar.gz
Size 31.6 kB
Tags Source
SHA-256 checksum
How to use checksums
a9ef2d2143b77010110fc8143c98b62509ca26b9271eacb96d1ecd72d6fde469
BLAKE2b-256 checksum
How to use checksums
7766cf143e3fbc3b4ac9f768b0ec92e8b727f1f09f0a6bcbee02b4428acb21f4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.13

Release files / like_a-0.1.1-py3-none-any.whl

Download URL like_a-0.1.1-py3-none-any.whl
Size 27.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
b89531a107c0af5adc11cb3d9f652dcaf4e942f3e8a798c25b1a72cff765b7a1
BLAKE2b-256 checksum
How to use checksums
6a3bab5a82db66a85b4cd7288bd8caa16920129a22740f55c589fcd136377fc8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.13

Security for like-a 0.1.1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Release history Release notifications | RSS feed

This release

0.1.1 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page