README.md

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 like-a
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 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 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 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

An indentation skin for C (drop braces, blocks by indentation) 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. 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.

Описание
Python in three syntax skins: indentation, braces, S-expressions. Exact AST round-trip.
Конвейеры
0 успешных
0 с ошибкой
Разработчики