as
One Python, three syntaxes. as converts Python source between the
indentation skin you know and two it can wear: C-style braces and Lisp-style
S-expressions. Same semantics, different look — hence the name: as lisp,
as brace, 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))))
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 as
as lisp foo.py # Python → S-expressions (stdout)
as brace foo.py -o foo.cpy
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). Use --from to override.
The importable package is aslang (from aslang import convert) because
as is a Python keyword — the distribution and the CLI keep the two-letter
name.
The three skins
| skin | extension | blocks | collections |
|---|---|---|---|
| py | .py |
indentation | (), [], {}, {k: v} |
| brace | .cpy |
if cond { ... } |
unchanged (Python expressions) |
| 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.
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 converts text directly on the token stream: comments, formatting and even docstrings survive byte-for-byte outside the changed block lines.
- 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).
Note on the name
Yes, as shadows GNU as (the assembler) if ~/.local/bin precedes
/usr/bin in your PATH. If you build C, keep your PATH straight or
alias. The two-letter namespace was simply too good to leave unused —
import json as j, as lisp foo.py.
Development
pip install -e .
pytest # 92 tests: unit + stdlib corpus + self-application
The tool digests its own source in the test suite: every module of aslang
is converted to both skins, back, and the result must be AST-identical —
and must still execute.
MIT license.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file like_a-0.1.0.tar.gz.
File metadata
- Download URL: like_a-0.1.0.tar.gz
- Upload date:
- Size: 27.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.12.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
decddbeca3d5e4af775e4b92928d67220973281a42961fefd368953cc9218e94
|
|
| MD5 |
6c4c79a2c788f4ab83e1abc979601873
|
|
| BLAKE2b-256 |
b0e87e04f563e73b058ca16bc7eddb8e0272160f6279471f5a0f42e4406c76bb
|
File details
Details for the file like_a-0.1.0-py3-none-any.whl.
File metadata
- Download URL: like_a-0.1.0-py3-none-any.whl
- Upload date:
- Size: 24.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.12.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cace60b5ffe9fdd1566ab774621883b57313cc6fa02d7de68cb1d0bc3676b98d
|
|
| MD5 |
4dace51ab2a207a9d5bdc823b799ada2
|
|
| BLAKE2b-256 |
9f1964cf4c5dfdce9a86750756b380f7f691314aefddd0d9d882da79c4efa89f
|