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 ifvocabulary on top). - anything <-> lisp goes through the
astmodule: the reader builds a real AST, the printer walks it. Comments are preserved best-effort (dropped by theast, 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
matchis supported in all skins;typing.TypeAliasstatements andtry*-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
whenneeds 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)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| like_a-0.1.1.tar.gz | 31.6 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| 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.