Skip to main content

Python with braces instead of significant indentation -- transpiles .spy to .py, line for line.

Project description

SmartPython

Python with braces instead of significant indentation. Write .spy, get .pyline for line, so every traceback, breakpoint and coverage report still points at the file you actually wrote. Think TypeScript → JavaScript, but the only thing that changes is how blocks are delimited.

A VS code is provided for files *.spy + a command to run a spy file + a command to transform spy -> py

# hello.spy
def greet(names) {
    for name in names {
        if name {
            print(f"hello {name}")
        } else {
            print("hello, whoever you are")
        }
    }
}
$ spy run hello.spy
$ spy build hello.spy     # writes hello.py

Install

$ pip install -e .

Requires Python 3.9+. Without installing, python -m smartpython works the same as spy.

The language

It is Python. Every rule you know still applies — the transpiler only rewrites block delimiters:

SmartPython Python
if x { y() } if x: y()
if x {} if x: + indented body
} else { else:
def f() { } def f(): pass

Indentation inside braces is yours to choose; the generated Python is indented with four spaces (--indent N to change it). A trailing : on a header is an error — braces replace it, they don't accompany it.

Both brace styles work:

def read(path)
{
    with open(path) as fh { return fh.read() }
}

Semicolons are optional and pass straight through, exactly as in Python.

Braces that aren't blocks

Dict displays, set displays, comprehensions, f-string fields and match patterns all use {} too. The scanner tells them apart by looking at the token in front of the brace: a brace after a value opens a block, a brace after an operator or keyword opens a display. So this parses the way you'd expect:

if value in {1, 2, 3} {     # first brace: a set. second: the block.
    return "small"
}

match record {
    case {"kind": kind} { return kind }
    case _ { return None }
}

Braces inside strings, f-strings (including nested quotes, {{/}} escapes and format specs) and comments are never touched.

Commands

spy build PATH...        # transpile to .py next to the source
    -o OUT               #   output file, or directory when building several
    --stdout             #   print instead of writing
    --bare               #   omit the "generated by" footer
    -f, --force          #   overwrite a hand-written .py
spy run FILE.spy [ARGS]  # transpile and execute, tracebacks mapped back
spy check PATH...        # transpile and compile, write nothing

build and check accept directories and recurse into them. - reads source from stdin; pair it with --filename NAME so errors name the real file:

$ spy check - --filename src/app.spy < src/app.spy

Editor support

A VS Code extension lives in editors/vscode — highlighting, live diagnostics as you type, a side-by-side preview of the generated Python, and build/run commands. No build step; symlink it into ~/.vscode/extensions and reload. See its README.

run installs an import hook, so a .spy file can import other .spy modules sitting next to it. To use that hook in a normal Python process:

import smartpython.importer as spy_hook
spy_hook.install()
import my_spy_module          # loads my_spy_module.spy

Line-for-line output

The rewrites are chosen so the line count never changes for idiomatic input:

if x {          ->  if x:              the brace becomes the colon
    y()         ->      y()
}               ->                     the closing brace becomes a blank line
} else {        ->  else:              dedent and re-open in place
def f() { }     ->  def f(): pass      short blocks stay inline
def f() {       ->  def f():           an empty body borrows the '}' line
}               ->      pass

A few styles can't be preserved — a nested block written inline, or code after a closing brace on the same line. Those expand, and Result.line_map records the real mapping; spy run uses it to rewrite tracebacks.

Library API

from smartpython import transpile, transpile_file, SpyError

result = transpile(source, filename="thing.spy")
result.code        # the Python source
result.identity    # True when lines map 1:1
result.line_map    # generated line -> .spy line
result.source_line(42)

SpyError carries filename, line, col and text, and renders a caret diagram via .pretty().

What it deliberately does not do

No type checking, no new operators, no // comments, no &&/||. SmartPython is exactly Python with a different block delimiter — anything else would make the generated code stop looking like the code you wrote.

Development

$ python -m pytest
$ spy run examples/tour.spy      # a tour of the syntax and the tricky cases

Known limits

  • A statement whose first token is a bare set/dict display, sitting where an Allman-style { could go, is ambiguous; the brace wins.
  • Generated files pick up a blank line wherever a } stood. That is the price of the 1:1 line mapping.
  • Continuation lines inside brackets keep their original indentation rather than being re-indented.

License

MIT

Author

Xaliphostes

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

smartpython-0.1.0.tar.gz (22.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

smartpython-0.1.0-py3-none-any.whl (17.9 kB view details)

Uploaded Python 3

File details

Details for the file smartpython-0.1.0.tar.gz.

File metadata

  • Download URL: smartpython-0.1.0.tar.gz
  • Upload date:
  • Size: 22.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.13

File hashes

Hashes for smartpython-0.1.0.tar.gz
Algorithm Hash digest
SHA256 2c988cf78428d3564fa6cf97687b79cdd06e7a42ee903065c9c355c03490e0c2
MD5 cb58e5f7372e3f3057797a0df31a989b
BLAKE2b-256 bcfc98c5cfbabec81df91afbc02f6c54f1602b42af98a80d6372b91b158d620a

See more details on using hashes here.

File details

Details for the file smartpython-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: smartpython-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 17.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.13

File hashes

Hashes for smartpython-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 547275c4091974ab84996fb73707745d1dce0c95862b3eb569d4cae93bb28908
MD5 3228e67e86b91dde748912902004b7b8
BLAKE2b-256 787ba184a776665fd00fb75ebc8a78a6c3c4698f60db845a5afc5eba96ef10cb

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page