hissp.compiler module

The Hissp data-structure language compiler and associated helper functions.

hissp.compiler.NS = <ContextVar name='NS' default=()>

Sometimes macros need the current namespace when expanding, instead of its defining namespace. Rather than pass in an implicit argument to all macros, it’s available here. readerless uses this automatically.

exception hissp.compiler.CompileError

Bases: SyntaxError

Catch-all exception for compilation failures.

exception hissp.compiler.PostCompileWarning

Bases: Warning

Form compiled to Python, but execution of it failed.

class hissp.compiler.Compiler(qualname='__main__', ns=None, evaluate=True)

Bases: object

The Hissp recursive-descent compiler.

Translates the Hissp data-structure language into a functional subset of Python.

static new_ns(name, doc=None, package=None)

Creates and initializes a dict namespace like a module, with the given __name__, __doc__, and __package__; __builtins__; an empty __annotations__; and whatever else Python currently adds to new module objects.

compile(forms: Iterable) str

Compile multiple forms, and execute them if evaluate mode enabled.

form(form) str

Compile Hissp form to the equivalent Python code in a string. tuple and str have special evaluation rules, otherwise it’s an atom that represents itself.

tuple(form: Tuple) str

Compile call, macro, or special forms.

special(form: Tuple) str

Try to compile as special form, else invocation.

function(form: Tuple) str

Compile the anonymous function special form.

(lambda (<parameters>)


The parameters tuple is divided into (<single> : <paired>)

Parameter types are the same as Python’s. For example,

>>> readerless(
... ('lambda', ('a',':/','b',
...             ':', 'e',1, 'f',2,
...             ':*','args', 'h',4, 'i',':?', 'j',1,
...             ':**','kwargs',),
...   42,),
... )
'(lambda a,/,b,e=(1),f=(2),*args,h=(4),i,j=(1),**kwargs:(42))'

The special control words :* and :** designate the remainder of the positional and keyword parameters, respectively. Note this body evaluates expressions in sequence, for side effects.

>>> print(readerless(
... ('lambda', (':',':*','args',':**','kwargs',),
...   ('print','args',),
...   ('print','kwargs',),),
... ))
(lambda *args,**kwargs:(

You can omit the right of a pair with :? (except the final **kwargs). Also note that the body can be empty.

>>> readerless(
... ('lambda', (':','a',1, ':/',':?', ':*',':?', 'b',':?', 'c',2,),),
... )
'(lambda a=(1),/,*,b,c=(2):())'

The : may be omitted if there are no paired parameters.

>>> readerless(('lambda', ('a','b','c',':',),),)
'(lambda a,b,c:())'
>>> readerless(('lambda', ('a','b','c',),),)
'(lambda a,b,c:())'
>>> readerless(('lambda', (':',),),)
'(lambda :())'
>>> readerless(('lambda', (),),)
'(lambda :())'

The : is required if there are any paired parameters, even if there are no single parameters:

>>> readerless(('lambda', (':',':**','kwargs',),),)
'(lambda **kwargs:())'
parameters(parameters: Iterable) Iterable[str]

Process parameters to compile function.

body(body: list) str

Compile body of function.

invocation(form: Tuple) str

Try to compile as macro, else normal call.

macro(form: Tuple) Union[str, Sentinel]

Macroexpand and start over with form, if it’s a macro.


Sets NS during macroexpansions.

call(form: Iterable) str

Compile call form.

Any tuple that is not quoted, (), or a special form or macro is a run-time call.

Like Python, it has three parts: (<callable> <args> : <kwargs>). For example:

>>> print(readerless(
... ('print',1,2,3,
...          ':','sep',('quote',":",), 'end',('quote',"\n\n",),)
... ))

Either <args> or <kwargs> may be empty:

>>> readerless(('foo',':',),)
>>> print(readerless(('foo','bar',':',),))
>>> print(readerless(('foo',':','bar','baz',),))

The : is optional if the <kwargs> part is empty:

>>> readerless(('foo',),)
>>> print(readerless(('foo','bar',),),)

The <kwargs> part has implicit pairs; there must be an even number.

Use the control words :* and :** for iterable and mapping unpacking:

>>> print(readerless(
... ('print',':',':*',[1,2], 'a',3, ':*',[4], ':**',{'sep':':','end':'\n\n'},),
... ))
  *[1, 2],
  **{'sep': ':', 'end': '\n\n'})

Unlike other control words, these can be repeated, but (as in Python) a ‘*’ is not allowed to follow ‘**’.

Method calls are similar to function calls: (.<method name> <object> <args> : <kwargs>) Like Clojure, a method on the first object is assumed if the function name starts with a dot:

>>> readerless(('.conjugate', 1j,),)
>>> eval(_)
>>> readerless(('.decode', b'\xfffoo', ':', 'errors',('quote','ignore',),),)
"b'\\xfffoo'.decode(\n  errors='ignore')"
>>> eval(_)
str(code: str) str

Compile code strings. Expands qualified identifiers and module handles into imports. Otherwise, injects as raw Python directly into the output.


Compile qualified identifier into import and attribute.


Compile module identifier to import.

atom(form) hissp.compiler.Compiler.str

Compile forms that evaluate to themselves.

Emits a literal if possible, otherwise falls back to pickle:

>>> readerless(-4.2j)
>>> print(readerless(float('nan')))
__import__('pickle').loads(  # nan
>>> readerless([{'foo':2},(),1j,2.0,{3}])
"[{'foo': 2}, (), 1j, 2.0, {3}]"
>>> spam = []
>>> spam.append(spam)  # ref cycle can't be a literal
>>> print(readerless(spam))
__import__('pickle').loads(  # [[...]]
>>> spam = [[]] * 3  # duplicated refs
>>> print(readerless(spam))
__import__('pickle').loads(  # [[], [], []]
pickle(form) hissp.compiler.Compiler.str

Compile to pickle.loads. The final fallback for atom.

eval(form: hissp.compiler.Compiler.str) Tuple[hissp.compiler.Compiler.str, ...]

Execute compiled form, but only if evaluate mode is enabled.

hissp.compiler.readerless(form, ns=None)

Compile a Hissp form to Python without evaluating it. Uses the current NS for context, unless an alternative is provided. (Creates a temporary namespace if neither is available.) Returns the Python in a string.