hissp.compiler module#

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

hissp.compiler.NS = <ContextVar name='NS' default=None>#

Sometimes a macro needs 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[source]#

Bases: SyntaxError

Catch-all exception for compilation failures.

exception hissp.compiler.PostCompileWarning[source]#

Bases: Warning

Form compiled to Python, but its execution failed.

Only possible when compiling in evaluate mode and not in __main__. Would be a “Hissp Abort!” instead when in __main__, but other modules can be allowed to continue compiling for debugging purposes.

Continuing execution after a failure can be dangerous if non-main modules have side effects besides definitions. Warnings can be upgraded to errors if this is a concern. See warnings for how.

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

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) Dict[str, Any][source]#

Imports the named module, creating it if necessary.

Returns the module’s __dict__.

compile(forms: Iterable) str[source]#

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

form(form) str[source]#

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[source]#

Compile call, macro, or special forms.

special(form: Tuple) str[source]#

Try to compile as special form, else invocation.

The two special forms are quote and lambda.

A quote form evaluates to its argument, treated as literal data, not evaluated. Notice the difference in the readerless compiled output.

>>> print(readerless(('print',42,)))  # function call
print(
  (42))
>>> print(readerless(('quote',('print',42,),)))  # tuple
('print',
 (42),)
function(form: Tuple) str[source]#

Compile the anonymous function special form.

(lambda (<parameters>)

<body>)

The parameters tuple is divided into (<singles> : <pairs>)

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:(
  print(
    args),
  print(
    kwargs))[-1])

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 pair parameters, even if there are no single parameters:

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

Process parameters to compile function.

body(body: list) str[source]#

Compile body of function.

invocation(form: Tuple) str[source]#

Try to compile as macro, else normal call.

macro(form: Tuple) str | Sentinel[source]#

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

macro_context()[source]#

Sets NS during macroexpansions.

call(form: Iterable) str[source]#

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",),)
... ))
print(
  (1),
  (2),
  (3),
  sep=':',
  end='\n\n')

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

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

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

>>> readerless(('foo',),)
'foo()'
>>> print(readerless(('foo','bar',),),)
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'},),
... ))
print(
  *[1, 2],
  a=(3),
  *[4],
  **{'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> <self> <args> : <kwargs>) A method on the self argument is assumed if the function name starts with a dot:

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

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

qualified_identifier(code)[source]#

Compile qualified identifier into import and attribute.

module_identifier(code)[source]#

Compile module identifier to import.

atom(form) str[source]#

Compile forms that evaluate to themselves.

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

>>> readerless(-4.2j)
'((-0-4.2j))'
>>> print(readerless(float('nan')))
__import__('pickle').loads(  # nan
    b'Fnan\n'
    b'.'
)
>>> 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(  # [[...]]
    b'(lp0\n'
    b'g0\n'
    b'a.'
)
>>> spam = [[]] * 3  # duplicated refs
>>> print(readerless(spam))
__import__('pickle').loads(  # [[], [], []]
    b'(l(lp0\n'
    b'ag0\n'
    b'ag0\n'
    b'a.'
)
pickle(form) str[source]#

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

static linenos(form)[source]#
eval(form: str, form_number: int) Tuple[str, ...][source]#

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

hissp.compiler.readerless(form, ns=None)[source]#

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.