hissp


Namehissp JSON
Version 0.5.0 PyPI version JSON
download
home_pagehttps://github.com/gilch/hissp
SummaryIt's Python with a Lissp.
upload_time2024-11-11 03:27:31
maintainerNone
docs_urlNone
authorMatthew Egan Odendahl
requires_python>=3.10
licenseApache-2.0
keywords lisp macro metaprogramming compiler interpreter dsl ast transpiler emacs clojure scheme language minimal repl metaprogramming macros extensible s-expressions code-generation no-dependencies quasiquote backquote syntax-quote template hissp lissp destructuring
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage
            <!--
Copyright 2019, 2020, 2021, 2022, 2023, 2024 Matthew Egan Odendahl
SPDX-License-Identifier: Apache-2.0
-->
[![Gitter](https://badges.gitter.im/hissp-lang/community.svg)](https://gitter.im/hissp-lang/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
[![Documentation Status](https://readthedocs.org/projects/hissp/badge/?version=latest)](https://hissp.readthedocs.io/en/latest/?badge=latest)
[![codecov](https://codecov.io/gh/gilch/hissp/branch/master/graph/badge.svg)](https://codecov.io/gh/gilch/hissp)
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
<!-- Hidden doctest adds bundled macros for REPL-consistent behavior.
#> (.update (globals) : _macro_ (types..SimpleNamespace : :** (vars hissp.._macro_)))
>>> globals().update(
...   _macro_=__import__('types').SimpleNamespace(
...             **vars(
...                 __import__('hissp')._macro_)))

-->
# ![Hissp](https://raw.githubusercontent.com/gilch/hissp/master/docs/hissp.svg)

It's Python with a *Lissp*.

Hissp is a modular Lisp implementation that compiles to a functional subset of
Python—Syntactic macro metaprogramming with full access to the Python ecosystem!

<!-- markdown-toc start - Don't edit this section. Run M-x markdown-toc-refresh-toc -->
**Table of Contents**

- [Installation](#installation)
- [Examples!](#examples)
    - [Quick Start: Readerless Mode](#quick-start-readerless-mode)
        - [Special Forms](#special-forms)
        - [Macros](#macros)
    - [The Lissp Reader](#the-lissp-reader)
        - [A Small Lissp Application](#a-small-lissp-application)
    - [Alternate Readers](#alternate-readers)
        - [Hebigo](#hebigo)
        - [Garden of EDN](#garden-of-edn)
- [Features and Design](#features-and-design)
    - [Radical Extensibility](#radical-extensibility)
    - [Minimal implementation](#minimal-implementation)
    - [Interoperability](#interoperability)
    - [Useful error messages](#useful-error-messages)
    - [Syntax compatible with Emacs' `lisp-mode` and Parlinter](#syntax-compatible-with-emacs-lisp-mode-and-parlinter)
    - [Standalone output](#standalone-output)
    - [Reproducible builds](#reproducible-builds)
    - [REPL-driven development](#repl-driven-development)
    - [Same-module macro helpers](#same-module-macro-helpers)
    - [Modularity](#modularity)
    - [Thorough documentation](#thorough-documentation)

<!-- markdown-toc end -->

## Installation
A Hissp install requires Python 3.10+.
(The compiled Python output it generates may work on older versions.)

Install the latest PyPI release with
```
python -m pip install --upgrade hissp
```
Or install the bleeding-edge version directly from GitHub with
```
python -m pip install --upgrade git+https://github.com/gilch/hissp
```
Confirm install with
```
python -m hissp --help
lissp -c "__hello__."
```

## Examples!

### Quick Start: Readerless Mode
Hissp is a *metaprogramming* intermediate language composed of simple Python data structures,
easily generated programmatically,
```python
>>> hissp_code = (
... ('lambda',('name',)
...  ,('print',('quote','Hello'),'name',),)
... )

```
which are compiled to Python code,
```python
>>> from hissp import readerless
>>> python_code = readerless(hissp_code)
>>> print(python_code)
(lambda name:
    print(
      'Hello',
      name)
)

```
and evaluated by Python.
```python
>>> greeter = eval(python_code)
>>> greeter('World')
Hello World
>>> greeter('Bob')
Hello Bob

```
To a first approximation,
tuples represent calls
and strings represent Python code in Hissp.
(Take everything else literally.)

#### Special Forms
Like Python, argument expressions are evaluated before being passed to the function,
however, the quote and lambda forms are special cases in the compiler
which are exceptions to this rule.

Strings also have a few special cases:
* control words, which start with `:` (and may have various special interpretations in certain contexts);
* method calls, which start with `.`, and must be the first element in a tuple representing a call;
* and module handles, which end with `.` (and do imports).
```python
>>> adv_hissp_code = (
... ('lambda'  # Anonymous function special form.
...  # Parameters.
...  ,(':'  # Control word: remaining parameters are paired with a target.
...    ,'name'  # Target: Raw Python: Parameter identifier.
...    # Default value for name.
...    ,('quote'  # Quote special form: string, not identifier. 
...      ,'world'),)
...  # Body.
...  ,('print'  # Function call form, using the identifier for the builtin.
...    ,('quote','Hello,'),)
...  ,('print'
...    ,':'  # Control word: Remaining arguments are paired with a target.
...    ,':*'  # Target: Control word for unpacking.
...    ,('.upper','name',)  # Method calls start with a dot.
...    ,'sep'  # Target: Keyword argument.
...    ,':'  # Control words compile to strings, not raw Python.
...    ,'file'  # Target: Keyword argument.
...    # Module handles like `sys.` end in a dot.
...    ,'sys..stdout',),)  # print already defaults to stdout though.
... )
...
>>> print(readerless(adv_hissp_code))
(lambda name='world':
   (print(
      'Hello,'),
    print(
      *name.upper(),
      sep=':',
      file=__import__('sys').stdout))  [-1]
)
>>> greetier = eval(readerless(adv_hissp_code))
>>> greetier()
Hello,
W:O:R:L:D
>>> greetier('alice')
Hello,
A:L:I:C:E

```

#### Macros
The ability to make lambdas and call out to arbitrary Python helper functions entails that Hissp can do anything Python can.
For example, control flow via higher-order functions.
```python
>>> any(map(lambda s: print(s), "abc"))  # HOF loop.
a
b
c
False
>>> def branch(condition, consequent, alternate):  # Conditional HOF.
...    return (consequent if condition else alternate)()  # Pick one to call.
...
>>> branch(1, lambda: print('yes'), lambda: print('no'))  # Now just a function call.
yes
>>> branch(0, lambda: print('yes'), lambda: print('no'))
no

```
This approach works fine in Hissp,
but we can express that more succinctly via metaprogramming.
Unlike functions,
the special forms don't (always) evaluate their arguments first.
Macros can rewrite forms in terms of these,
extending that ability to custom tuple forms.
```python
>>> class _macro_:  # This name is magic in Hissp.
...     def thunk(*body):  # No self. _macro_ is just used as a namespace.
...         # Python code for writing Hissp code. Macros are metaprograms.
...         return ('lambda',(),*body,)  # Delayed evaluation.
...     def if_else(condition, consequent, alternate):
...         # Delegates both to a helper function and another macro.
...         return ('branch',condition,('thunk',consequent,),('thunk',alternate,),)
...
>>> expansion = readerless(
...     ('if_else','0==1'  # Macro form, not a run-time call.
...      ,('print',('quote','yes',),)  # Side effect not evaluated!
...      ,('print',('quote','no',),),),
... )  # It finds _macro_ in the calling frame's globals.
>>> print(expansion)
# if_else
branch(
  0==1,
  # thunk
  (lambda :
      print(
        'yes')
  ),
  # thunk
  (lambda :
      print(
        'no')
  ))
>>> eval(expansion)
no

```
A number of useful macros come bundled with Hissp.

### The Lissp Reader
The Hissp data-structure language can be written directly in Python using the "readerless mode" demonstrated above,
or it can be read in from a lightweight textual language called *Lissp* that represents the Hissp
a little more neatly.
```python
>>> lissp_code = """
... (lambda (name)
...   (print 'Hello name))
... """

```
As you can see, this results in exactly the same Hissp code as our earlier example.
```python
>>> from hissp.reader import Lissp
>>> next(Lissp().reads(lissp_code))
('lambda', ('name',), ('print', ('quote', 'Hello'), 'name'))
>>> _ == hissp_code
True

```

Hissp comes with a basic REPL (read-eval-print loop, or interactive command-line interface)
which compiles Hissp (read from Lissp) to Python and passes that to the Python REPL for execution.

Lissp can also be read from ``.lissp`` files,
which compile to Python modules.

#### A Small Lissp Application
This is a Lissp web app for converting between Celsius and Fahrenheit,
which demonstrates a number of language features.
Run as the main script or enter it into the Lissp REPL.
Requires [Bottle.](https://bottlepy.org/docs/dev/)
```Racket
hissp..prelude#:

(define enjoin en#X#(.join "" (map str X)))

(defun tag (tag : :* contents)
  (enjoin "<"tag">"(enjoin : :* contents)"</"!##0(.split tag)">"))

(defmacro script (: :* forms)
  `',(tag "script type='text/python'" "\n"
      (.join "\n" (map hissp.compiler..readerless forms))))

((bottle..route "/") ; https://bottlepy.org
 O#(enjoin
    (let (s (tag "script src='https://cdn.jsdelivr.net/npm/brython@3/brython{}.js'"))
      (enjoin (.format s ".min") (.format s "_stdlib")))
    (tag "body onload='brython()'" ; Browser Python: https://brython.info
     (script
       (define getE X#(.getElementById browser..document X))
       (define getf@v X#(float @##'value (getE X)))
       (define set@v XY#(setattr (getE Y) 'value X))
       (attach browser..window
         : Celsius O#(-> (getf@v 'Celsius) (X#|X*1.8+32|) (set@v 'Fahrenheit))
         Fahrenheit O#(-> (getf@v 'Fahrenheit) (X#|(X-32)/1.8|) (set@v 'Celsius))))
     (let (row (enjoin (tag "input id='{0}' onkeyup='{0}()'")
                       (tag "label for='{0}'" "°{1}")))
       (enjoin (.format row "Fahrenheit" "F")"<br>"(.format row "Celsius" "C"))))))

(bottle..run : host "localhost"  port 8080  debug True)
```

This example uses *munged names*.
Munging expands the set of characters allowed in identifier names.
For example, the `getf@v` is an alias for `getfQzAT_v`.

It also uses *tags*, which are metaprograms run by the reader.
Normal tags end in one or more `#` characters indicating arity,
but there are also built in unary tagging tokens (including `'` and `,`)
which do not.
For example, the `O#` (`_macro_.OQzHASH_`) rewrites its argument to a lambda
with zero parameters.

Consult the [Hissp documentation](https://hissp.readthedocs.io/)
for an explanation of each form.

### Alternate Readers
Hissp is modular, and the reader included for Lissp is not the only one.

#### Hebigo
Here's a native unit test class from the separate
[Hebigo](https://github.com/gilch/hebigo) prototype,
a Hissp reader and macro suite implementing a language designed to resemble Python:
```python
class: TestOr: TestCase
  def: .test_null: self
    self.assertEqual: () or:
  def: .test_one: self x
    :@ given: st.from_type: type
    self.assertIs: x or: x
  def: .test_two: self x y
    :@ given:
      st.from_type: type
      st.from_type: type
    self.assertIs: (x or y) or: x y
  def: .test_shortcut: self
    or: 1 (0/0)
    or: 0 1 (0/0)
    or: 1 (0/0) (0/0)
  def: .test_three: self x y z
    :@ given:
      st.from_type: type
      st.from_type: type
      st.from_type: type
    self.assertIs: (x or y or z) or: x y z
```

The same Hissp macros work in readerless mode, Lissp, and Hebigo, and can be written in any of these.
Given Hebigo's macros, the class above could be written in the equivalent way in Lissp:

```Racket
(class_ (TestOr TestCase)
  (def_ (.test_null self)
    (self.assertEqual () (or_)))
  (def_ (.test_one self x)
    :@ (given (st.from_type type))
    (self.assertIs x (or_ x)))
  (def_ (.test_two self x y)
    :@ (given (st.from_type type)
              (st.from_type type))
    (self.assertIs |x or y| (or_ x y)))
  (def_ (.test_shortcut self)
    (or_ 1 |0/0|)
    (or_ 0 1 |0/0|)
    (or_ 1 |0/0| |0/0|))
  (def_ (.test_three self x y z)
    :@ (given (st.from_type type)
              (st.from_type type)
              (st.from_type type))
    (self.assertIs |x or y or z| (or_ x y z))))
```

Hebigo looks very different from Lissp, but they are both Hissp!
If you quote this Hebigo code and print it out,
you get Hissp code, just like you would with Lissp.

In Hebigo's REPL, that looks like
```
In [1]: pprint..pp:quote:class: TestOr: TestCase
   ...:   def: .test_null: self
   ...:     self.assertEqual: () or:
   ...:   def: .test_one: self x
   ...:     :@ given: st.from_type: type
   ...:     self.assertIs: x or: x
   ...:   def: .test_two: self x y
   ...:     :@ given:
   ...:       st.from_type: type
   ...:       st.from_type: type
   ...:     self.assertIs: (x or y) or: x y
   ...:   def: .test_shortcut: self
   ...:     or: 1 (0/0)
   ...:     or: 0 1 (0/0)
   ...:     or: 1 (0/0) (0/0)
   ...:   def: .test_three: self x y z
   ...:     :@ given:
   ...:       st.from_type: type
   ...:       st.from_type: type
   ...:       st.from_type: type
   ...:     self.assertIs: (x or y or z) or: x y z
   ...: 
('hebi.basic.._macro_.class_',
 ('TestOr', 'TestCase'),
 ('hebi.basic.._macro_.def_',
  ('.test_null', 'self'),
  ('self.assertEqual', '()', ('hebi.basic.._macro_.or_',))),
 ('hebi.basic.._macro_.def_',
  ('.test_one', 'self', 'x'),
  ':@',
  ('given', ('st.from_type', 'type')),
  ('self.assertIs', 'x', ('hebi.basic.._macro_.or_', 'x'))),
 ('hebi.basic.._macro_.def_',
  ('.test_two', 'self', 'x', 'y'),
  ':@',
  ('given', ('st.from_type', 'type'), ('st.from_type', 'type')),
  ('self.assertIs', '((x or y))', ('hebi.basic.._macro_.or_', 'x', 'y'))),
 ('hebi.basic.._macro_.def_',
  ('.test_shortcut', 'self'),
  ('hebi.basic.._macro_.or_', 1, '((0/0))'),
  ('hebi.basic.._macro_.or_', 0, 1, '((0/0))'),
  ('hebi.basic.._macro_.or_', 1, '((0/0))', '((0/0))')),
 ('hebi.basic.._macro_.def_',
  ('.test_three', 'self', 'x', 'y', 'z'),
  ':@',
  ('given',
   ('st.from_type', 'type'),
   ('st.from_type', 'type'),
   ('st.from_type', 'type')),
  ('self.assertIs',
   '((x or y or z))',
   ('hebi.basic.._macro_.or_', 'x', 'y', 'z'))))
```

#### Garden of EDN
Extensible Data Notation (EDN) is a subset of Clojure used for data exchange,
as JSON is to JavaScript, only more extensible.
Any Clojure editor should be able to handle EDN.

The separate [Garden of EDN](https://github.com/gilch/garden-of-edn)
prototype contains a variety of EDN readers in Python,
and two of them read EDN into Hissp.

Here's little snake game in PandoraHissp,
one of the EDN Hissp dialects,
which includes Clojure-like persistent data structures.

```EDN
0 ; from garden_of_edn import _this_file_as_main_; """#"
#hissp/prelude .

(define TICK 100)
(define WIDTH 40)
(define HEIGHT 20)
(define SNAKE (pyrsistent/dq (complex 3 2) (complex 2 2)))
(define BINDS {"w" [(complex  0 -1)]
               "a" [(complex -1  0)]
               "s" [(complex  0 +1)]
               "d" [(complex +1  0)]})

(define arrow (collections/deque))

(define root (doto (tkinter/Tk)
               (.resizable 0 0)
               (.bind "<Key>" #X(.extendleft arrow (.get BINDS X.char ())))))

(define label (doto (tkinter/Label)
                .pack
                (.configure . font "TkFixedFont"
                              justify "left"
                              height (add 1 HEIGHT)
                              width WIDTH)))

(defun wall? z
  (ors (contains #{WIDTH  -1} z.real)
       (contains #{HEIGHT -1} z.imag)))

(defun food! .
  (complex (random/randint 0 (sub WIDTH 1))
           (random/randint 0 (sub HEIGHT 1))))

(defun frame (state)
  (-<>> (product (range HEIGHT) (range WIDTH))
        (starmap #XY(complex Y X))
        (map #X(concat (cond (contains state.snake X) "O"
                             (eq X state.food) "@"
                             :else " ")
                       (if-else (eq 0 X.real) "\n" "")))
        (.join "")))

(defun move (state new-food arrow)
  (let (direction (if-else (ands arrow (ne arrow (neg state.direction)))
                    arrow state.direction))
    (let (head (add (getitem state.snake 0) direction))
      (-> state
          (.update (if-else (eq head state.food)
                     {"score" (add 1 state.score)
                      "food" new-food}
                     {"snake" (.pop state.snake)})
                   {"direction" direction})
          (.transform ["snake"] #X(.appendleft X head))))))

(defun lost? (state)
  (let (head (getitem state.snake 0))
    (ors (wall? head)
         (contains (getitem state.snake (slice 1 None))
                   head))))

(defun update! (state)
  (-<>> (if-else (lost? state)
          " GAME OVER!"
          (prog1 "" (.after root TICK update! (move state
                                                    (food!)
                                                    (when arrow (.pop arrow))))))
        (.format "Score: {}{}{}" state.score :<> (frame state))
        (.configure label . text)))

(when (eq __name__ "__main__")
  (update! {"score" 0, "direction" 1, "snake" SNAKE, "food" (food!)})
  (.mainloop root))

;; """#"
```

The first and last lines make this a valid Python file as well as EDN.

## Features and Design

Python is already a really nice language, a lot closer to Lisp than to C or Fortran.
It has dynamic types and automatic garbage collection, for example.
So why do we need Hissp?

If the only programming languages you've tried are those designed to feel familiar to C programmers,
you might think they're all the same.

I assure you, they are not.

### Radical Extensibility

While any Turing-complete language has equivalent theoretical power,
they are not equally *expressive*.
They can be higher or lower level.
You already know this.
It's why you don't write assembly language when you can avoid it.
It's not that assembly isn't powerful enough to do everything the computer can.
Ultimately, the machine only understands machine code.

The best programming languages have some kind of expressive superpower.
Features that lesser languages lack.
Lisp's superpower is *metaprogramming*,
and it's the power to copy the others.
It's not that Python can't do metaprogramming at all.
(Python is Turing complete, after all.)
You can already do all of this in Python,
and more easily than in many other languages.
But it's too difficult (compared to Lisp),
so it's done rarely and by specialists.
The use of `exec()` is frowned upon.
It's easy enough to understand, but hard to get right.
Python Abstract Syntax Tree (AST)
manipulation is a somewhat more reliable technique,
but not for the faint of heart.
Python AST is not simple, because Python isn't.

Python really is a great language to work with.
It has steadily grown in popularity over decades,
even without the advantages enjoyed by its competitors,
like a corporate sponsor (Java), killer app (Ruby),
or captive audience (JavaScript),
because it is just that good.
Describing it as "executable pseudocode" is not too far off.

But it is too complex to be good at metaprogramming.
By stripping Python down to a minimal subset,
and encoding that subset as simple data structures rather than text
(or complicated and error-prone Python AST),
Hissp makes metaprogramming as easy as
the kind of data manipulation you already do every day.
On its own, meta-power doesn't seem that impressive.
But what you make with it can be.

> A Lisp programmer who notices a common pattern in their code can write a macro to give themselves a source-level
> abstraction of that pattern. A Java programmer who notices the same pattern has to convince Sun that this particular
> abstraction is worth adding to the language. Then Sun has to publish a JSR and convene an industry-wide "expert group"
> to hash everything out. That process--according to Sun--takes an average of 18 months. After that, the compiler writers
> all have to go upgrade their compilers to support the new feature. And even once the Java programmer's favorite compiler
> supports the new version of Java, they probably still can't use the new feature until they're allowed to break source
> compatibility with older versions of Java. So an annoyance that Common Lisp programmers can resolve for themselves
> within five minutes plagues Java programmers for years.  
> — Peter Seibel (2005) *Practical Common Lisp*

Actively developed languages keep accumulating features,
Python is no execption.
Often they're helpful, but sometimes it's a misstep.
The more complex a language gets,
the more difficult it becomes to master.
(With C++ being a particularly egregious case.)

Hissp takes the opposite approach: extensibility through simplicity.
Major features that would require a new language version in lower languages
can be a library in a Lisp.
It's how Clojure got Goroutines like Go and logic programming like Prolog,
without changing the core language at all.
The Lissp reader and Hissp compiler are both extensible with metaprograms.

It's not just about getting the best features from other languages,
but all the minor powers you can make for yourself along the way.
You're not going to campaign for a new Python language feature
and wait six months for another release
just for something that might be nice to have for you special problem at the moment.
But in Hissp, *you can totally have that*.
You can program the language itself to fit your problem domain.

Once your Python project is "sufficiently complicated",
you'll start hacking in new language features just to cope.
And it will be hard,
because you'll be using a language too low-level for your needs,
even if it's a relatively high-level language like Python.

Lisp is as high level as a programming language gets,
because you can program in anything higher.

### Minimal implementation
Hissp serves as a modular component for other projects.
The language and its implementation are meant to be small and comprehensible
by a single individual.

The Hissp compiler should include what it needs to achieve its goals,
but no more.
Bloat is resisted.
A goal of Hissp is to be as small as is reasonably practical, but no smaller.
We're not code golfing here; readability still counts.
But this project has *limited scope*.
Hissp's powerful macro system means that additions to the compiler are
rarely needed.
Feature creep belongs in external libraries,
not in the compiler proper.
If you strip out the documentation and blank lines,
the entire `hissp` package only has around 1350 lines of source code left over.

Hissp compiles to an unpythonic *functional subset* of Python.
This subset has a direct and easy-to-understand correspondence to the Hissp code,
which makes it straightforward to debug, once you understand Hissp.
But it is definitely not meant to be idiomatic Python.
That would require a much more complex compiler,
because idiomatic Python is not simple.

### Interoperability
Why base a Lisp on Python when there are already lots of other Lisps?

Python has a rich selection of libraries for a variety of domains
and Hissp can mostly use them as easily as the standard library.
This gives Hissp a massive advantage over other Lisps with less selection.
If you don't care to work with the Python ecosystem,
perhaps Hissp is not the Lisp for you.

Note that the Hissp compiler is currently written for Python 3.10,
and the bundled metaprograms may assume at least that level.
(Supporting older versions is not a goal,
because that would complicate the compiler.
This may limit the available libraries.)
But because the compiler's target functional Python subset is so small,
the compiled output can usually be made to run on Python 3.5 without too much difficulty.
Watch out for positional-only arguments (new to 3.8)
and changes to the standard library.
Running on versions even older than 3.5 is not recommended,
but may likewise be possible if you carefully avoid using newer Python features.

Python code can also import and use modules written in Hissp,
because they compile to Python.

### Useful error messages
One of Python's best features.
Any errors that prevent compilation should be easy to find.
Hissp errs on the side of verbosity.

### Syntax compatible with Emacs' `lisp-mode` and Parlinter
A language is not very usable without tools.
Hissp's basic reader syntax (Lissp) should work with Emacs.

The alternative EDN readers are compatible with Clojure editors.

Hebigo was designed to work with minimal editor support.
All it really needs is the ability to cut, paste, and indent/dedent blocks of code.
Even [IDLE](https://docs.python.org/3/library/idle.html) would do.

### Standalone output
This is part of Hissp's commitment to modularity.

One can, of course, write Hissp code that depends on any Python library.
But the compiler does not depend on emitting calls out to any special
Hissp helper functions to work.
You do not need Hissp installed to run the final compiled Python output,
only Python itself.

Hissp bundles some metaprograms to get you started.
Their expansions have no external requirements either.

Applications and libraries built on Hissp need not follow this restriction;
they're free to add any dependencies usable by Python.

### Reproducible builds
A newer Python feature that Lissp respects.

Lissp's gensym format is deterministic,
yet unlikely to collide even among standalone modules compiled at different times.
If you haven't changed anything,
your code will compile the same way.

One could, of course, write randomized macros,
but that's no fault of Lissp's.

### REPL-driven development
A Lisp tradition, and Hissp is no exception.
Even though it's a compiled language,
Hissp has an interactive command-line interface like Python does.
The REPL displays the compiled Python and evaluates it.
Printed values use the normal Python reprs.
(Translating those to back to Lissp is not a goal.
Lissp is not the only Hissp reader.)

Hissp comes with utilities to write reloadable modules.
You can get a subREPL inside any module and refresh changes without restarting your session.
Updates to classes made with the bundled macros propagate to existing instances.

### Same-module macro helpers
Functions are generally preferable to macros when functions can do the job.
They're more reusable and composable.
Therefore, it makes sense for macros to delegate to functions where possible.
But such a macro should work in the same module as its helper functions.
This requires incremental compilation and evaluation of forms in Lissp modules,
like the REPL.

### Modularity
The Hissp language is made of tuples (and atoms), not text.
The S-expression reader included with the project (Lissp) is just a convenient
way to write them.
It's possible to write Hissp in "readerless mode"
by writing these tuples in Python.

Hissp's standard library is just Python's.
Additional functional libraries
(Toolz and Pyrsistent)
are recommended for a more Clojure-like experience,
but are not required.
There are only two special forms: ``quote`` and ``lambda``.
Hissp does bundle a metaprogramming suite,
but you are not obligated to use them when writing Hissp.
And if you do use them (correctly),
the compiled output is standalone;
it doesn't require an installation of Hissp.

It's possible for an external project to provide an alternative
reader with different syntax, as long as the output is Hissp code.
One example of this is [Hebigo](https://github.com/gilch/hebigo),
which has a more Python-like indentation-based syntax.

Hissp is not locked into any one Lisp paradigm.
It could work with a Clojure-like, Scheme-like, or Common-Lisp-like, etc.,
reader, function, and macro libraries.
Its bundled metaprogramming library is optional,
it produces standalone output,
and it's modular enough for alternate readers to exist.

### Thorough documentation

The API docs have usage examples demonstrating how to use every bundled metaprogram.
Every example is tested and working, because these double as part of Hissp's test suite.
They're also available via Python's `help()` facility.

There are tutorials teaching the language itself,
and how to write metaprograms with it.

The [Hissp Community Chat](https://gitter.im/hissp-lang/community)
is available if you get stuck,
or want to talk about anything on the topic of Hissp.

            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/gilch/hissp",
    "name": "hissp",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.10",
    "maintainer_email": null,
    "keywords": "lisp macro metaprogramming compiler interpreter DSL AST transpiler emacs clojure scheme language minimal REPL metaprogramming macros extensible s-expressions code-generation no-dependencies quasiquote backquote syntax-quote template Hissp Lissp destructuring",
    "author": "Matthew Egan Odendahl",
    "author_email": "gilch@users.noreply.github.com",
    "download_url": null,
    "platform": null,
    "description": "<!--\r\nCopyright 2019, 2020, 2021, 2022, 2023, 2024 Matthew Egan Odendahl\r\nSPDX-License-Identifier: Apache-2.0\r\n-->\r\n[![Gitter](https://badges.gitter.im/hissp-lang/community.svg)](https://gitter.im/hissp-lang/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)\r\n[![Documentation Status](https://readthedocs.org/projects/hissp/badge/?version=latest)](https://hissp.readthedocs.io/en/latest/?badge=latest)\r\n[![codecov](https://codecov.io/gh/gilch/hissp/branch/master/graph/badge.svg)](https://codecov.io/gh/gilch/hissp)\r\n[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)\r\n<!-- Hidden doctest adds bundled macros for REPL-consistent behavior.\r\n#> (.update (globals) : _macro_ (types..SimpleNamespace : :** (vars hissp.._macro_)))\r\n>>> globals().update(\r\n...   _macro_=__import__('types').SimpleNamespace(\r\n...             **vars(\r\n...                 __import__('hissp')._macro_)))\r\n\r\n-->\r\n# ![Hissp](https://raw.githubusercontent.com/gilch/hissp/master/docs/hissp.svg)\r\n\r\nIt's Python with a *Lissp*.\r\n\r\nHissp is a modular Lisp implementation that compiles to a functional subset of\r\nPython\u2014Syntactic macro metaprogramming with full access to the Python ecosystem!\r\n\r\n<!-- markdown-toc start - Don't edit this section. Run M-x markdown-toc-refresh-toc -->\r\n**Table of Contents**\r\n\r\n- [Installation](#installation)\r\n- [Examples!](#examples)\r\n    - [Quick Start: Readerless Mode](#quick-start-readerless-mode)\r\n        - [Special Forms](#special-forms)\r\n        - [Macros](#macros)\r\n    - [The Lissp Reader](#the-lissp-reader)\r\n        - [A Small Lissp Application](#a-small-lissp-application)\r\n    - [Alternate Readers](#alternate-readers)\r\n        - [Hebigo](#hebigo)\r\n        - [Garden of EDN](#garden-of-edn)\r\n- [Features and Design](#features-and-design)\r\n    - [Radical Extensibility](#radical-extensibility)\r\n    - [Minimal implementation](#minimal-implementation)\r\n    - [Interoperability](#interoperability)\r\n    - [Useful error messages](#useful-error-messages)\r\n    - [Syntax compatible with Emacs' `lisp-mode` and Parlinter](#syntax-compatible-with-emacs-lisp-mode-and-parlinter)\r\n    - [Standalone output](#standalone-output)\r\n    - [Reproducible builds](#reproducible-builds)\r\n    - [REPL-driven development](#repl-driven-development)\r\n    - [Same-module macro helpers](#same-module-macro-helpers)\r\n    - [Modularity](#modularity)\r\n    - [Thorough documentation](#thorough-documentation)\r\n\r\n<!-- markdown-toc end -->\r\n\r\n## Installation\r\nA Hissp install requires Python 3.10+.\r\n(The compiled Python output it generates may work on older versions.)\r\n\r\nInstall the latest PyPI release with\r\n```\r\npython -m pip install --upgrade hissp\r\n```\r\nOr install the bleeding-edge version directly from GitHub with\r\n```\r\npython -m pip install --upgrade git+https://github.com/gilch/hissp\r\n```\r\nConfirm install with\r\n```\r\npython -m hissp --help\r\nlissp -c \"__hello__.\"\r\n```\r\n\r\n## Examples!\r\n\r\n### Quick Start: Readerless Mode\r\nHissp is a *metaprogramming* intermediate language composed of simple Python data structures,\r\neasily generated programmatically,\r\n```python\r\n>>> hissp_code = (\r\n... ('lambda',('name',)\r\n...  ,('print',('quote','Hello'),'name',),)\r\n... )\r\n\r\n```\r\nwhich are compiled to Python code,\r\n```python\r\n>>> from hissp import readerless\r\n>>> python_code = readerless(hissp_code)\r\n>>> print(python_code)\r\n(lambda name:\r\n    print(\r\n      'Hello',\r\n      name)\r\n)\r\n\r\n```\r\nand evaluated by Python.\r\n```python\r\n>>> greeter = eval(python_code)\r\n>>> greeter('World')\r\nHello World\r\n>>> greeter('Bob')\r\nHello Bob\r\n\r\n```\r\nTo a first approximation,\r\ntuples represent calls\r\nand strings represent Python code in Hissp.\r\n(Take everything else literally.)\r\n\r\n#### Special Forms\r\nLike Python, argument expressions are evaluated before being passed to the function,\r\nhowever, the quote and lambda forms are special cases in the compiler\r\nwhich are exceptions to this rule.\r\n\r\nStrings also have a few special cases:\r\n* control words, which start with `:` (and may have various special interpretations in certain contexts);\r\n* method calls, which start with `.`, and must be the first element in a tuple representing a call;\r\n* and module handles, which end with `.` (and do imports).\r\n```python\r\n>>> adv_hissp_code = (\r\n... ('lambda'  # Anonymous function special form.\r\n...  # Parameters.\r\n...  ,(':'  # Control word: remaining parameters are paired with a target.\r\n...    ,'name'  # Target: Raw Python: Parameter identifier.\r\n...    # Default value for name.\r\n...    ,('quote'  # Quote special form: string, not identifier. \r\n...      ,'world'),)\r\n...  # Body.\r\n...  ,('print'  # Function call form, using the identifier for the builtin.\r\n...    ,('quote','Hello,'),)\r\n...  ,('print'\r\n...    ,':'  # Control word: Remaining arguments are paired with a target.\r\n...    ,':*'  # Target: Control word for unpacking.\r\n...    ,('.upper','name',)  # Method calls start with a dot.\r\n...    ,'sep'  # Target: Keyword argument.\r\n...    ,':'  # Control words compile to strings, not raw Python.\r\n...    ,'file'  # Target: Keyword argument.\r\n...    # Module handles like `sys.` end in a dot.\r\n...    ,'sys..stdout',),)  # print already defaults to stdout though.\r\n... )\r\n...\r\n>>> print(readerless(adv_hissp_code))\r\n(lambda name='world':\r\n   (print(\r\n      'Hello,'),\r\n    print(\r\n      *name.upper(),\r\n      sep=':',\r\n      file=__import__('sys').stdout))  [-1]\r\n)\r\n>>> greetier = eval(readerless(adv_hissp_code))\r\n>>> greetier()\r\nHello,\r\nW:O:R:L:D\r\n>>> greetier('alice')\r\nHello,\r\nA:L:I:C:E\r\n\r\n```\r\n\r\n#### Macros\r\nThe ability to make lambdas and call out to arbitrary Python helper functions entails that Hissp can do anything Python can.\r\nFor example, control flow via higher-order functions.\r\n```python\r\n>>> any(map(lambda s: print(s), \"abc\"))  # HOF loop.\r\na\r\nb\r\nc\r\nFalse\r\n>>> def branch(condition, consequent, alternate):  # Conditional HOF.\r\n...    return (consequent if condition else alternate)()  # Pick one to call.\r\n...\r\n>>> branch(1, lambda: print('yes'), lambda: print('no'))  # Now just a function call.\r\nyes\r\n>>> branch(0, lambda: print('yes'), lambda: print('no'))\r\nno\r\n\r\n```\r\nThis approach works fine in Hissp,\r\nbut we can express that more succinctly via metaprogramming.\r\nUnlike functions,\r\nthe special forms don't (always) evaluate their arguments first.\r\nMacros can rewrite forms in terms of these,\r\nextending that ability to custom tuple forms.\r\n```python\r\n>>> class _macro_:  # This name is magic in Hissp.\r\n...     def thunk(*body):  # No self. _macro_ is just used as a namespace.\r\n...         # Python code for writing Hissp code. Macros are metaprograms.\r\n...         return ('lambda',(),*body,)  # Delayed evaluation.\r\n...     def if_else(condition, consequent, alternate):\r\n...         # Delegates both to a helper function and another macro.\r\n...         return ('branch',condition,('thunk',consequent,),('thunk',alternate,),)\r\n...\r\n>>> expansion = readerless(\r\n...     ('if_else','0==1'  # Macro form, not a run-time call.\r\n...      ,('print',('quote','yes',),)  # Side effect not evaluated!\r\n...      ,('print',('quote','no',),),),\r\n... )  # It finds _macro_ in the calling frame's globals.\r\n>>> print(expansion)\r\n# if_else\r\nbranch(\r\n  0==1,\r\n  # thunk\r\n  (lambda :\r\n      print(\r\n        'yes')\r\n  ),\r\n  # thunk\r\n  (lambda :\r\n      print(\r\n        'no')\r\n  ))\r\n>>> eval(expansion)\r\nno\r\n\r\n```\r\nA number of useful macros come bundled with Hissp.\r\n\r\n### The Lissp Reader\r\nThe Hissp data-structure language can be written directly in Python using the \"readerless mode\" demonstrated above,\r\nor it can be read in from a lightweight textual language called *Lissp* that represents the Hissp\r\na little more neatly.\r\n```python\r\n>>> lissp_code = \"\"\"\r\n... (lambda (name)\r\n...   (print 'Hello name))\r\n... \"\"\"\r\n\r\n```\r\nAs you can see, this results in exactly the same Hissp code as our earlier example.\r\n```python\r\n>>> from hissp.reader import Lissp\r\n>>> next(Lissp().reads(lissp_code))\r\n('lambda', ('name',), ('print', ('quote', 'Hello'), 'name'))\r\n>>> _ == hissp_code\r\nTrue\r\n\r\n```\r\n\r\nHissp comes with a basic REPL (read-eval-print loop, or interactive command-line interface)\r\nwhich compiles Hissp (read from Lissp) to Python and passes that to the Python REPL for execution.\r\n\r\nLissp can also be read from ``.lissp`` files,\r\nwhich compile to Python modules.\r\n\r\n#### A Small Lissp Application\r\nThis is a Lissp web app for converting between Celsius and Fahrenheit,\r\nwhich demonstrates a number of language features.\r\nRun as the main script or enter it into the Lissp REPL.\r\nRequires [Bottle.](https://bottlepy.org/docs/dev/)\r\n```Racket\r\nhissp..prelude#:\r\n\r\n(define enjoin en#X#(.join \"\" (map str X)))\r\n\r\n(defun tag (tag : :* contents)\r\n  (enjoin \"<\"tag\">\"(enjoin : :* contents)\"</\"!##0(.split tag)\">\"))\r\n\r\n(defmacro script (: :* forms)\r\n  `',(tag \"script type='text/python'\" \"\\n\"\r\n      (.join \"\\n\" (map hissp.compiler..readerless forms))))\r\n\r\n((bottle..route \"/\") ; https://bottlepy.org\r\n O#(enjoin\r\n    (let (s (tag \"script src='https://cdn.jsdelivr.net/npm/brython@3/brython{}.js'\"))\r\n      (enjoin (.format s \".min\") (.format s \"_stdlib\")))\r\n    (tag \"body onload='brython()'\" ; Browser Python: https://brython.info\r\n     (script\r\n       (define getE X#(.getElementById browser..document X))\r\n       (define getf@v X#(float @##'value (getE X)))\r\n       (define set@v XY#(setattr (getE Y) 'value X))\r\n       (attach browser..window\r\n         : Celsius O#(-> (getf@v 'Celsius) (X#|X*1.8+32|) (set@v 'Fahrenheit))\r\n         Fahrenheit O#(-> (getf@v 'Fahrenheit) (X#|(X-32)/1.8|) (set@v 'Celsius))))\r\n     (let (row (enjoin (tag \"input id='{0}' onkeyup='{0}()'\")\r\n                       (tag \"label for='{0}'\" \"\u00b0{1}\")))\r\n       (enjoin (.format row \"Fahrenheit\" \"F\")\"<br>\"(.format row \"Celsius\" \"C\"))))))\r\n\r\n(bottle..run : host \"localhost\"  port 8080  debug True)\r\n```\r\n\r\nThis example uses *munged names*.\r\nMunging expands the set of characters allowed in identifier names.\r\nFor example, the `getf@v` is an alias for `getfQzAT_v`.\r\n\r\nIt also uses *tags*, which are metaprograms run by the reader.\r\nNormal tags end in one or more `#` characters indicating arity,\r\nbut there are also built in unary tagging tokens (including `'` and `,`)\r\nwhich do not.\r\nFor example, the `O#` (`_macro_.OQzHASH_`) rewrites its argument to a lambda\r\nwith zero parameters.\r\n\r\nConsult the [Hissp documentation](https://hissp.readthedocs.io/)\r\nfor an explanation of each form.\r\n\r\n### Alternate Readers\r\nHissp is modular, and the reader included for Lissp is not the only one.\r\n\r\n#### Hebigo\r\nHere's a native unit test class from the separate\r\n[Hebigo](https://github.com/gilch/hebigo) prototype,\r\na Hissp reader and macro suite implementing a language designed to resemble Python:\r\n```python\r\nclass: TestOr: TestCase\r\n  def: .test_null: self\r\n    self.assertEqual: () or:\r\n  def: .test_one: self x\r\n    :@ given: st.from_type: type\r\n    self.assertIs: x or: x\r\n  def: .test_two: self x y\r\n    :@ given:\r\n      st.from_type: type\r\n      st.from_type: type\r\n    self.assertIs: (x or y) or: x y\r\n  def: .test_shortcut: self\r\n    or: 1 (0/0)\r\n    or: 0 1 (0/0)\r\n    or: 1 (0/0) (0/0)\r\n  def: .test_three: self x y z\r\n    :@ given:\r\n      st.from_type: type\r\n      st.from_type: type\r\n      st.from_type: type\r\n    self.assertIs: (x or y or z) or: x y z\r\n```\r\n\r\nThe same Hissp macros work in readerless mode, Lissp, and Hebigo, and can be written in any of these.\r\nGiven Hebigo's macros, the class above could be written in the equivalent way in Lissp:\r\n\r\n```Racket\r\n(class_ (TestOr TestCase)\r\n  (def_ (.test_null self)\r\n    (self.assertEqual () (or_)))\r\n  (def_ (.test_one self x)\r\n    :@ (given (st.from_type type))\r\n    (self.assertIs x (or_ x)))\r\n  (def_ (.test_two self x y)\r\n    :@ (given (st.from_type type)\r\n              (st.from_type type))\r\n    (self.assertIs |x or y| (or_ x y)))\r\n  (def_ (.test_shortcut self)\r\n    (or_ 1 |0/0|)\r\n    (or_ 0 1 |0/0|)\r\n    (or_ 1 |0/0| |0/0|))\r\n  (def_ (.test_three self x y z)\r\n    :@ (given (st.from_type type)\r\n              (st.from_type type)\r\n              (st.from_type type))\r\n    (self.assertIs |x or y or z| (or_ x y z))))\r\n```\r\n\r\nHebigo looks very different from Lissp, but they are both Hissp!\r\nIf you quote this Hebigo code and print it out,\r\nyou get Hissp code, just like you would with Lissp.\r\n\r\nIn Hebigo's REPL, that looks like\r\n```\r\nIn [1]: pprint..pp:quote:class: TestOr: TestCase\r\n   ...:   def: .test_null: self\r\n   ...:     self.assertEqual: () or:\r\n   ...:   def: .test_one: self x\r\n   ...:     :@ given: st.from_type: type\r\n   ...:     self.assertIs: x or: x\r\n   ...:   def: .test_two: self x y\r\n   ...:     :@ given:\r\n   ...:       st.from_type: type\r\n   ...:       st.from_type: type\r\n   ...:     self.assertIs: (x or y) or: x y\r\n   ...:   def: .test_shortcut: self\r\n   ...:     or: 1 (0/0)\r\n   ...:     or: 0 1 (0/0)\r\n   ...:     or: 1 (0/0) (0/0)\r\n   ...:   def: .test_three: self x y z\r\n   ...:     :@ given:\r\n   ...:       st.from_type: type\r\n   ...:       st.from_type: type\r\n   ...:       st.from_type: type\r\n   ...:     self.assertIs: (x or y or z) or: x y z\r\n   ...: \r\n('hebi.basic.._macro_.class_',\r\n ('TestOr', 'TestCase'),\r\n ('hebi.basic.._macro_.def_',\r\n  ('.test_null', 'self'),\r\n  ('self.assertEqual', '()', ('hebi.basic.._macro_.or_',))),\r\n ('hebi.basic.._macro_.def_',\r\n  ('.test_one', 'self', 'x'),\r\n  ':@',\r\n  ('given', ('st.from_type', 'type')),\r\n  ('self.assertIs', 'x', ('hebi.basic.._macro_.or_', 'x'))),\r\n ('hebi.basic.._macro_.def_',\r\n  ('.test_two', 'self', 'x', 'y'),\r\n  ':@',\r\n  ('given', ('st.from_type', 'type'), ('st.from_type', 'type')),\r\n  ('self.assertIs', '((x or y))', ('hebi.basic.._macro_.or_', 'x', 'y'))),\r\n ('hebi.basic.._macro_.def_',\r\n  ('.test_shortcut', 'self'),\r\n  ('hebi.basic.._macro_.or_', 1, '((0/0))'),\r\n  ('hebi.basic.._macro_.or_', 0, 1, '((0/0))'),\r\n  ('hebi.basic.._macro_.or_', 1, '((0/0))', '((0/0))')),\r\n ('hebi.basic.._macro_.def_',\r\n  ('.test_three', 'self', 'x', 'y', 'z'),\r\n  ':@',\r\n  ('given',\r\n   ('st.from_type', 'type'),\r\n   ('st.from_type', 'type'),\r\n   ('st.from_type', 'type')),\r\n  ('self.assertIs',\r\n   '((x or y or z))',\r\n   ('hebi.basic.._macro_.or_', 'x', 'y', 'z'))))\r\n```\r\n\r\n#### Garden of EDN\r\nExtensible Data Notation (EDN) is a subset of Clojure used for data exchange,\r\nas JSON is to JavaScript, only more extensible.\r\nAny Clojure editor should be able to handle EDN.\r\n\r\nThe separate [Garden of EDN](https://github.com/gilch/garden-of-edn)\r\nprototype contains a variety of EDN readers in Python,\r\nand two of them read EDN into Hissp.\r\n\r\nHere's little snake game in PandoraHissp,\r\none of the EDN Hissp dialects,\r\nwhich includes Clojure-like persistent data structures.\r\n\r\n```EDN\r\n0 ; from garden_of_edn import _this_file_as_main_; \"\"\"#\"\r\n#hissp/prelude .\r\n\r\n(define TICK 100)\r\n(define WIDTH 40)\r\n(define HEIGHT 20)\r\n(define SNAKE (pyrsistent/dq (complex 3 2) (complex 2 2)))\r\n(define BINDS {\"w\" [(complex  0 -1)]\r\n               \"a\" [(complex -1  0)]\r\n               \"s\" [(complex  0 +1)]\r\n               \"d\" [(complex +1  0)]})\r\n\r\n(define arrow (collections/deque))\r\n\r\n(define root (doto (tkinter/Tk)\r\n               (.resizable 0 0)\r\n               (.bind \"<Key>\" #X(.extendleft arrow (.get BINDS X.char ())))))\r\n\r\n(define label (doto (tkinter/Label)\r\n                .pack\r\n                (.configure . font \"TkFixedFont\"\r\n                              justify \"left\"\r\n                              height (add 1 HEIGHT)\r\n                              width WIDTH)))\r\n\r\n(defun wall? z\r\n  (ors (contains #{WIDTH  -1} z.real)\r\n       (contains #{HEIGHT -1} z.imag)))\r\n\r\n(defun food! .\r\n  (complex (random/randint 0 (sub WIDTH 1))\r\n           (random/randint 0 (sub HEIGHT 1))))\r\n\r\n(defun frame (state)\r\n  (-<>> (product (range HEIGHT) (range WIDTH))\r\n        (starmap #XY(complex Y X))\r\n        (map #X(concat (cond (contains state.snake X) \"O\"\r\n                             (eq X state.food) \"@\"\r\n                             :else \" \")\r\n                       (if-else (eq 0 X.real) \"\\n\" \"\")))\r\n        (.join \"\")))\r\n\r\n(defun move (state new-food arrow)\r\n  (let (direction (if-else (ands arrow (ne arrow (neg state.direction)))\r\n                    arrow state.direction))\r\n    (let (head (add (getitem state.snake 0) direction))\r\n      (-> state\r\n          (.update (if-else (eq head state.food)\r\n                     {\"score\" (add 1 state.score)\r\n                      \"food\" new-food}\r\n                     {\"snake\" (.pop state.snake)})\r\n                   {\"direction\" direction})\r\n          (.transform [\"snake\"] #X(.appendleft X head))))))\r\n\r\n(defun lost? (state)\r\n  (let (head (getitem state.snake 0))\r\n    (ors (wall? head)\r\n         (contains (getitem state.snake (slice 1 None))\r\n                   head))))\r\n\r\n(defun update! (state)\r\n  (-<>> (if-else (lost? state)\r\n          \" GAME OVER!\"\r\n          (prog1 \"\" (.after root TICK update! (move state\r\n                                                    (food!)\r\n                                                    (when arrow (.pop arrow))))))\r\n        (.format \"Score: {}{}{}\" state.score :<> (frame state))\r\n        (.configure label . text)))\r\n\r\n(when (eq __name__ \"__main__\")\r\n  (update! {\"score\" 0, \"direction\" 1, \"snake\" SNAKE, \"food\" (food!)})\r\n  (.mainloop root))\r\n\r\n;; \"\"\"#\"\r\n```\r\n\r\nThe first and last lines make this a valid Python file as well as EDN.\r\n\r\n## Features and Design\r\n\r\nPython is already a really nice language, a lot closer to Lisp than to C or Fortran.\r\nIt has dynamic types and automatic garbage collection, for example.\r\nSo why do we need Hissp?\r\n\r\nIf the only programming languages you've tried are those designed to feel familiar to C programmers,\r\nyou might think they're all the same.\r\n\r\nI assure you, they are not.\r\n\r\n### Radical Extensibility\r\n\r\nWhile any Turing-complete language has equivalent theoretical power,\r\nthey are not equally *expressive*.\r\nThey can be higher or lower level.\r\nYou already know this.\r\nIt's why you don't write assembly language when you can avoid it.\r\nIt's not that assembly isn't powerful enough to do everything the computer can.\r\nUltimately, the machine only understands machine code.\r\n\r\nThe best programming languages have some kind of expressive superpower.\r\nFeatures that lesser languages lack.\r\nLisp's superpower is *metaprogramming*,\r\nand it's the power to copy the others.\r\nIt's not that Python can't do metaprogramming at all.\r\n(Python is Turing complete, after all.)\r\nYou can already do all of this in Python,\r\nand more easily than in many other languages.\r\nBut it's too difficult (compared to Lisp),\r\nso it's done rarely and by specialists.\r\nThe use of `exec()` is frowned upon.\r\nIt's easy enough to understand, but hard to get right.\r\nPython Abstract Syntax Tree (AST)\r\nmanipulation is a somewhat more reliable technique,\r\nbut not for the faint of heart.\r\nPython AST is not simple, because Python isn't.\r\n\r\nPython really is a great language to work with.\r\nIt has steadily grown in popularity over decades,\r\neven without the advantages enjoyed by its competitors,\r\nlike a corporate sponsor (Java), killer app (Ruby),\r\nor captive audience (JavaScript),\r\nbecause it is just that good.\r\nDescribing it as \"executable pseudocode\" is not too far off.\r\n\r\nBut it is too complex to be good at metaprogramming.\r\nBy stripping Python down to a minimal subset,\r\nand encoding that subset as simple data structures rather than text\r\n(or complicated and error-prone Python AST),\r\nHissp makes metaprogramming as easy as\r\nthe kind of data manipulation you already do every day.\r\nOn its own, meta-power doesn't seem that impressive.\r\nBut what you make with it can be.\r\n\r\n> A Lisp programmer who notices a common pattern in their code can write a macro to give themselves a source-level\r\n> abstraction of that pattern. A Java programmer who notices the same pattern has to convince Sun that this particular\r\n> abstraction is worth adding to the language. Then Sun has to publish a JSR and convene an industry-wide \"expert group\"\r\n> to hash everything out. That process--according to Sun--takes an average of 18 months. After that, the compiler writers\r\n> all have to go upgrade their compilers to support the new feature. And even once the Java programmer's favorite compiler\r\n> supports the new version of Java, they probably still can't use the new feature until they're allowed to break source\r\n> compatibility with older versions of Java. So an annoyance that Common Lisp programmers can resolve for themselves\r\n> within five minutes plagues Java programmers for years.  \r\n> \u2014 Peter Seibel (2005) *Practical Common Lisp*\r\n\r\nActively developed languages keep accumulating features,\r\nPython is no execption.\r\nOften they're helpful, but sometimes it's a misstep.\r\nThe more complex a language gets,\r\nthe more difficult it becomes to master.\r\n(With C++ being a particularly egregious case.)\r\n\r\nHissp takes the opposite approach: extensibility through simplicity.\r\nMajor features that would require a new language version in lower languages\r\ncan be a library in a Lisp.\r\nIt's how Clojure got Goroutines like Go and logic programming like Prolog,\r\nwithout changing the core language at all.\r\nThe Lissp reader and Hissp compiler are both extensible with metaprograms.\r\n\r\nIt's not just about getting the best features from other languages,\r\nbut all the minor powers you can make for yourself along the way.\r\nYou're not going to campaign for a new Python language feature\r\nand wait six months for another release\r\njust for something that might be nice to have for you special problem at the moment.\r\nBut in Hissp, *you can totally have that*.\r\nYou can program the language itself to fit your problem domain.\r\n\r\nOnce your Python project is \"sufficiently complicated\",\r\nyou'll start hacking in new language features just to cope.\r\nAnd it will be hard,\r\nbecause you'll be using a language too low-level for your needs,\r\neven if it's a relatively high-level language like Python.\r\n\r\nLisp is as high level as a programming language gets,\r\nbecause you can program in anything higher.\r\n\r\n### Minimal implementation\r\nHissp serves as a modular component for other projects.\r\nThe language and its implementation are meant to be small and comprehensible\r\nby a single individual.\r\n\r\nThe Hissp compiler should include what it needs to achieve its goals,\r\nbut no more.\r\nBloat is resisted.\r\nA goal of Hissp is to be as small as is reasonably practical, but no smaller.\r\nWe're not code golfing here; readability still counts.\r\nBut this project has *limited scope*.\r\nHissp's powerful macro system means that additions to the compiler are\r\nrarely needed.\r\nFeature creep belongs in external libraries,\r\nnot in the compiler proper.\r\nIf you strip out the documentation and blank lines,\r\nthe entire `hissp` package only has around 1350 lines of source code left over.\r\n\r\nHissp compiles to an unpythonic *functional subset* of Python.\r\nThis subset has a direct and easy-to-understand correspondence to the Hissp code,\r\nwhich makes it straightforward to debug, once you understand Hissp.\r\nBut it is definitely not meant to be idiomatic Python.\r\nThat would require a much more complex compiler,\r\nbecause idiomatic Python is not simple.\r\n\r\n### Interoperability\r\nWhy base a Lisp on Python when there are already lots of other Lisps?\r\n\r\nPython has a rich selection of libraries for a variety of domains\r\nand Hissp can mostly use them as easily as the standard library.\r\nThis gives Hissp a massive advantage over other Lisps with less selection.\r\nIf you don't care to work with the Python ecosystem,\r\nperhaps Hissp is not the Lisp for you.\r\n\r\nNote that the Hissp compiler is currently written for Python 3.10,\r\nand the bundled metaprograms may assume at least that level.\r\n(Supporting older versions is not a goal,\r\nbecause that would complicate the compiler.\r\nThis may limit the available libraries.)\r\nBut because the compiler's target functional Python subset is so small,\r\nthe compiled output can usually be made to run on Python 3.5 without too much difficulty.\r\nWatch out for positional-only arguments (new to 3.8)\r\nand changes to the standard library.\r\nRunning on versions even older than 3.5 is not recommended,\r\nbut may likewise be possible if you carefully avoid using newer Python features.\r\n\r\nPython code can also import and use modules written in Hissp,\r\nbecause they compile to Python.\r\n\r\n### Useful error messages\r\nOne of Python's best features.\r\nAny errors that prevent compilation should be easy to find.\r\nHissp errs on the side of verbosity.\r\n\r\n### Syntax compatible with Emacs' `lisp-mode` and Parlinter\r\nA language is not very usable without tools.\r\nHissp's basic reader syntax (Lissp) should work with Emacs.\r\n\r\nThe alternative EDN readers are compatible with Clojure editors.\r\n\r\nHebigo was designed to work with minimal editor support.\r\nAll it really needs is the ability to cut, paste, and indent/dedent blocks of code.\r\nEven [IDLE](https://docs.python.org/3/library/idle.html) would do.\r\n\r\n### Standalone output\r\nThis is part of Hissp's commitment to modularity.\r\n\r\nOne can, of course, write Hissp code that depends on any Python library.\r\nBut the compiler does not depend on emitting calls out to any special\r\nHissp helper functions to work.\r\nYou do not need Hissp installed to run the final compiled Python output,\r\nonly Python itself.\r\n\r\nHissp bundles some metaprograms to get you started.\r\nTheir expansions have no external requirements either.\r\n\r\nApplications and libraries built on Hissp need not follow this restriction;\r\nthey're free to add any dependencies usable by Python.\r\n\r\n### Reproducible builds\r\nA newer Python feature that Lissp respects.\r\n\r\nLissp's gensym format is deterministic,\r\nyet unlikely to collide even among standalone modules compiled at different times.\r\nIf you haven't changed anything,\r\nyour code will compile the same way.\r\n\r\nOne could, of course, write randomized macros,\r\nbut that's no fault of Lissp's.\r\n\r\n### REPL-driven development\r\nA Lisp tradition, and Hissp is no exception.\r\nEven though it's a compiled language,\r\nHissp has an interactive command-line interface like Python does.\r\nThe REPL displays the compiled Python and evaluates it.\r\nPrinted values use the normal Python reprs.\r\n(Translating those to back to Lissp is not a goal.\r\nLissp is not the only Hissp reader.)\r\n\r\nHissp comes with utilities to write reloadable modules.\r\nYou can get a subREPL inside any module and refresh changes without restarting your session.\r\nUpdates to classes made with the bundled macros propagate to existing instances.\r\n\r\n### Same-module macro helpers\r\nFunctions are generally preferable to macros when functions can do the job.\r\nThey're more reusable and composable.\r\nTherefore, it makes sense for macros to delegate to functions where possible.\r\nBut such a macro should work in the same module as its helper functions.\r\nThis requires incremental compilation and evaluation of forms in Lissp modules,\r\nlike the REPL.\r\n\r\n### Modularity\r\nThe Hissp language is made of tuples (and atoms), not text.\r\nThe S-expression reader included with the project (Lissp) is just a convenient\r\nway to write them.\r\nIt's possible to write Hissp in \"readerless mode\"\r\nby writing these tuples in Python.\r\n\r\nHissp's standard library is just Python's.\r\nAdditional functional libraries\r\n(Toolz and Pyrsistent)\r\nare recommended for a more Clojure-like experience,\r\nbut are not required.\r\nThere are only two special forms: ``quote`` and ``lambda``.\r\nHissp does bundle a metaprogramming suite,\r\nbut you are not obligated to use them when writing Hissp.\r\nAnd if you do use them (correctly),\r\nthe compiled output is standalone;\r\nit doesn't require an installation of Hissp.\r\n\r\nIt's possible for an external project to provide an alternative\r\nreader with different syntax, as long as the output is Hissp code.\r\nOne example of this is [Hebigo](https://github.com/gilch/hebigo),\r\nwhich has a more Python-like indentation-based syntax.\r\n\r\nHissp is not locked into any one Lisp paradigm.\r\nIt could work with a Clojure-like, Scheme-like, or Common-Lisp-like, etc.,\r\nreader, function, and macro libraries.\r\nIts bundled metaprogramming library is optional,\r\nit produces standalone output,\r\nand it's modular enough for alternate readers to exist.\r\n\r\n### Thorough documentation\r\n\r\nThe API docs have usage examples demonstrating how to use every bundled metaprogram.\r\nEvery example is tested and working, because these double as part of Hissp's test suite.\r\nThey're also available via Python's `help()` facility.\r\n\r\nThere are tutorials teaching the language itself,\r\nand how to write metaprograms with it.\r\n\r\nThe [Hissp Community Chat](https://gitter.im/hissp-lang/community)\r\nis available if you get stuck,\r\nor want to talk about anything on the topic of Hissp.\r\n",
    "bugtrack_url": null,
    "license": "Apache-2.0",
    "summary": "It's Python with a Lissp.",
    "version": "0.5.0",
    "project_urls": {
        "Homepage": "https://github.com/gilch/hissp"
    },
    "split_keywords": [
        "lisp",
        "macro",
        "metaprogramming",
        "compiler",
        "interpreter",
        "dsl",
        "ast",
        "transpiler",
        "emacs",
        "clojure",
        "scheme",
        "language",
        "minimal",
        "repl",
        "metaprogramming",
        "macros",
        "extensible",
        "s-expressions",
        "code-generation",
        "no-dependencies",
        "quasiquote",
        "backquote",
        "syntax-quote",
        "template",
        "hissp",
        "lissp",
        "destructuring"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "31672a5cb7b099833e39fbef2036dd5bfece78ae252d4a888bf73593f4706784",
                "md5": "fb916bc7e6f19688e89f53144d8cd08b",
                "sha256": "17600514161c2f72031a77ba13fa6b8c1e541ef864b606744537edc9915f1067"
            },
            "downloads": -1,
            "filename": "hissp-0.5.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "fb916bc7e6f19688e89f53144d8cd08b",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.10",
            "size": 116902,
            "upload_time": "2024-11-11T03:27:31",
            "upload_time_iso_8601": "2024-11-11T03:27:31.954326Z",
            "url": "https://files.pythonhosted.org/packages/31/67/2a5cb7b099833e39fbef2036dd5bfece78ae252d4a888bf73593f4706784/hissp-0.5.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-11-11 03:27:31",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "gilch",
    "github_project": "hissp",
    "travis_ci": false,
    "coveralls": true,
    "github_actions": true,
    "lcname": "hissp"
}
        
Elapsed time: 1.92340s