Metadata-Version: 2.4
Name: like-a
Version: 0.1.0
Summary: Python in three syntax skins: indentation, braces, S-expressions
License: MIT
Requires-Python: >=3.12
Description-Content-Type: text/markdown
License-File: LICENSE
Dynamic: license-file

# 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)
```

```lisp
(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 `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).

## 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.
